# 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`](PHASE0_STATUS.md). **System owner + ISSO + AO:** Chris Tonjes, DOES CIO. **Full context:** [`OVERVIEW.md`](OVERVIEW.md), [`ARCHITECTURE.md`](ARCHITECTURE.md), [`PHASE0_PLAN.md`](PHASE0_PLAN.md). ## Quickstart — Docker (canonical) Requires Docker Desktop (or equivalent). Ports 8000 (app), 5433 (Postgres), 8081 (Adminer) must be free. ```bash 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/db `ypms` - Stop with `Ctrl+C`; wipe the local DB with `docker 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. ```bash 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 ```bash 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: ```bash python -m ypms.framework.retention # logs "0 records to purge" and writes an audit row ``` ## Tests and gates ```bash 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`](.gitea/workflows/ci.yml). ## Repo shape Full tree in [`ARCHITECTURE.md`](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//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 `/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/.md` (see `registrant.md`). ## Contributing See [`CONTRIBUTING.md`](CONTRIBUTING.md) for the hard rules. ## Compliance - [`SYSTEM_SECURITY_PLAN.md`](SYSTEM_SECURITY_PLAN.md) — NIST 800-53 Moderate SSP - [`NIST_800_53_CONTROL_MAPPING.md`](NIST_800_53_CONTROL_MAPPING.md) v0.3 — full control map with Phase 0 evidence - [`docs/nist-mapping.md`](docs/nist-mapping.md) — Phase 0 control evidence index (file:line) - [`PRIVACY_IMPACT_ASSESSMENT.md`](PRIVACY_IMPACT_ASSESSMENT.md) — PIA covering minors, SSN, banking, FERPA, CFSA - [`POAM.md`](POAM.md) — plan of action and milestones ## License / usage Internal DOES system. Not for external distribution.