Files
YPMS/README.md
T
Chris TonjesandClaude Opus 5.5 e3c4cb0cdc
ci / test (push) Has been cancelled
Phase 0 milestone 0.9: docs + acceptance
- 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>
2026-10-01 14:37:40 -04:00

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.

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 handler
  • src/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.py
  • src/ypms/security/ — RBAC, column_crypto.py (AES-256-GCM app-layer encryption)
  • src/ypms/auth/ — login, JWT (PyJWT), refresh, change-password, login rate limit
  • src/ypms/youth/ — Registrant, the real vertical-slice entity
  • src/ypms/stubs.py, src/ypms/stub_models.py — the 26 entities still at 501
  • src/ypms/retention_classes.py — PIA §9 retention policy catalog
  • src/ypms/models.py — the one list of model modules
  • src/ypms/seed/ — roles, permissions matrix, bootstrap admin
  • migrations/ — Alembic; merged revisions pinned in IMMUTABLE.sha256
  • docs/coverage/ — per-entity requirement traces; docs/nist-mapping.md — control evidence index

Adding an entity (promoting a stub to real)

  1. Move the class from src/ypms/stub_models.py into src/ypms/<domain>/models.py; keep __tablename__ and retention_policy. Add the module to src/ypms/models.py.
  2. Add columns in a new migration; append its hash to migrations/IMMUTABLE.sha256.
  3. Encrypted columns: write through security.column_crypto, list them in __audit_redact__.
  4. Schemas in <domain>/schemas.py; the read schema masks anything sensitive.
  5. Router: framework.router.make_router for plain CRUD, or a hand-built router on CRUDService (see youth/router.py) when you need PII-read audit or row scoping. Register it in main.py and remove the entity from src/ypms/stubs.py.
  6. Check the permissions matrix rows for the entity — they're policy; review them as such.
  7. Tests, then a coverage doc at docs/coverage/<entity>.md (see registrant.md).

Contributing

See CONTRIBUTING.md for the hard rules.

Compliance

License / usage

Internal DOES system. Not for external distribution.