- README rewritten: Docker and native quickstarts, try-it flow, gate commands, promote-a-stub steps. - CONTRIBUTING rules 9-13 (pii_read on PII reads, __audit_redact__ for encrypted columns, retention policy per table, models.py registry, vendored-code change discipline); PR checklist updated. - docs/nist-mapping.md: per-control file:line evidence and remaining gap. - NIST_800_53_CONTROL_MAPPING.md v0.3: Phase 0 notes on 28 rows (12 implemented, 12 partial, 4 progress notes on hybrid/tailored rows) plus a status-shift table. - PHASE0_PLAN: 0.9 complete; Definition of Done 12/14 checked with how each was verified. Open: first green Gitea CI run, Docker/Postgres boot. - POA&M and status handoff updated; status TL;DR no longer claims an unverified Docker boot. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
5.9 KiB
YPMS — Youth Program Management System
DC Department of Employment Services / Office of Youth Programs — in-house build replacing seven legacy tools (MBSYEP intake, TMS timekeeping, PF3 admin, manual payroll scripts, MicroStrategy, and two supporting apps) with a single Python / FastAPI / Postgres system on Azure Government.
Status: Phase 0 (foundations) — framework, auth, RBAC, audit, Registrant vertical slice, 501 stubs for every other entity, retention scaffold, CI. See PHASE0_STATUS.md.
System owner + ISSO + AO: Chris Tonjes, DOES CIO.
Full context: OVERVIEW.md, ARCHITECTURE.md, PHASE0_PLAN.md.
Quickstart — Docker (canonical)
Requires Docker Desktop (or equivalent). Ports 8000 (app), 5433 (Postgres), 8081 (Adminer) must be free.
cp .env.example .env
docker compose up --build
Compose runs alembic upgrade head, seeds roles + permissions + the bootstrap admin (chris / change-me-immediately, or SEED_ADMIN_PASSWORD), then starts the app.
- App: http://localhost:8000 — OpenAPI at http://localhost:8000/docs
- Adminer: http://localhost:8081 — System PostgreSQL, server
db, user/password/dbypms - Stop with
Ctrl+C; wipe the local DB withdocker compose down -v.
Quickstart — native, no Docker
Uses SQLite in place of Postgres. Fine for poking at the API; Postgres-specific behaviour needs the Docker path.
python3.12 -m venv .venv && source .venv/bin/activate
pip install -e '.[dev]'
export DATABASE_URL=sqlite:///./ypms-dev.db
export JWT_SECRET=dev-jwt-secret-change-me-32-bytes-min # >= 32 bytes or the app won't start
export COLUMN_ENCRYPTION_KEY=dev-col-enc-key-change-me-32bytes-______
export SEED_ADMIN_PASSWORD=pick-a-local-password
alembic upgrade head
python -m ypms.seed.run
uvicorn ypms.main:app --reload
Try it
curl http://localhost:8000/healthz # {"status":"ok"}
curl http://localhost:8000/readyz # {"status":"ok"} once the DB is reachable
TOKEN=$(curl -s -X POST http://localhost:8000/auth/login \
-d 'username=chris&password=change-me-immediately' | python3 -c 'import json,sys;print(json.load(sys.stdin)["access_token"])')
curl -s -X POST http://localhost:8000/registrants \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"first_name":"Test","last_name":"Youth","dob":"2010-05-01","ssn":"123-45-6789"}'
# → ssn_masked "XXX-XX-6789"; the SSN itself is never returned
curl -s -o /dev/null -w '%{http_code}\n' http://localhost:8000/guardians -H "Authorization: Bearer $TOKEN"
# → 501 (stub: RBAC checked and audited, no logic yet)
Change the bootstrap password right away: POST /auth/change-password.
The retention job is report-only in Phase 0:
python -m ypms.framework.retention # logs "0 records to purge" and writes an audit row
Tests and gates
pytest # SQLite in-memory, ~10s
pytest --cov=src/ypms --cov-report=term-missing --cov-fail-under=80
ruff check src tests
mypy src
bandit -q -r src -c pyproject.toml
pip-audit --strict . # runtime dependencies
CI runs all of these plus gitleaks and a CycloneDX SBOM on Gitea Actions — .gitea/workflows/ci.yml.
Repo shape
Full tree in ARCHITECTURE.md §5.1. High points:
src/ypms/main.py— app factory; refuses to start if any table lacks a retention policy; opaque 500 handlersrc/ypms/settings.py— pydantic-settings config (secret-length floors enforced)src/ypms/framework/— vendored from OWC, plus YPMS additions:pii_audit.py,stub_router.py,retention.pysrc/ypms/security/— RBAC,column_crypto.py(AES-256-GCM app-layer encryption)src/ypms/auth/— login, JWT (PyJWT), refresh, change-password, login rate limitsrc/ypms/youth/— Registrant, the real vertical-slice entitysrc/ypms/stubs.py,src/ypms/stub_models.py— the 26 entities still at 501src/ypms/retention_classes.py— PIA §9 retention policy catalogsrc/ypms/models.py— the one list of model modulessrc/ypms/seed/— roles, permissions matrix, bootstrap adminmigrations/— Alembic; merged revisions pinned inIMMUTABLE.sha256docs/coverage/— per-entity requirement traces;docs/nist-mapping.md— control evidence index
Adding an entity (promoting a stub to real)
- Move the class from
src/ypms/stub_models.pyintosrc/ypms/<domain>/models.py; keep__tablename__andretention_policy. Add the module tosrc/ypms/models.py. - Add columns in a new migration; append its hash to
migrations/IMMUTABLE.sha256. - Encrypted columns: write through
security.column_crypto, list them in__audit_redact__. - Schemas in
<domain>/schemas.py; the read schema masks anything sensitive. - Router:
framework.router.make_routerfor plain CRUD, or a hand-built router onCRUDService(seeyouth/router.py) when you need PII-read audit or row scoping. Register it inmain.pyand remove the entity fromsrc/ypms/stubs.py. - Check the permissions matrix rows for the entity — they're policy; review them as such.
- Tests, then a coverage doc at
docs/coverage/<entity>.md(seeregistrant.md).
Contributing
See CONTRIBUTING.md for the hard rules.
Compliance
SYSTEM_SECURITY_PLAN.md— NIST 800-53 Moderate SSPNIST_800_53_CONTROL_MAPPING.mdv0.3 — full control map with Phase 0 evidencedocs/nist-mapping.md— Phase 0 control evidence index (file:line)PRIVACY_IMPACT_ASSESSMENT.md— PIA covering minors, SSN, banking, FERPA, CFSAPOAM.md— plan of action and milestones
License / usage
Internal DOES system. Not for external distribution.