Phase 0 milestone 0.9: docs + acceptance
ci / test (push) Has been cancelled

- 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>
This commit is contained in:
Chris TonjesandClaude Opus 5.5 committed 2026-10-01 14:37:40 -04:00
1 parent 99a50dc86c
commit e3c4cb0cdc
7 files changed
+241 -111

No files matched your search

+18 -7
View File
@@ -4,22 +4,32 @@ Hard rules — mirrored from OWC's `CONTRIBUTING.md` because the two systems sha
## Repository conventions ## Repository conventions
1. **Migrations are immutable once merged.** Add a new Alembic revision, never edit `0001_baseline.py` or its successors. If a schema change requires undoing an older migration, the fix is a new revision, not a rewrite. Every migration is a paper trail an auditor can walk. 1. **Migrations are immutable once merged.** Add a new Alembic revision, never edit `0001_baseline.py` or its successors. If a schema change requires undoing an older migration, the fix is a new revision, not a rewrite. Every migration is a paper trail an auditor can walk. Every revision's SHA-256 is pinned in `migrations/IMMUTABLE.sha256` — add a line with each new migration; a test fails if a pinned file changes. Migrations never import app code.
2. **Don't add domain logic to `src/ypms/framework/` or `src/ypms/security/`.** Those are reused by every entity; entity-specific code belongs in `src/ypms/<entity>/`. If a framework enhancement is genuinely needed, propose it in its own PR with a written reason. 2. **Don't add domain logic to `src/ypms/framework/` or `src/ypms/security/`.** Those are reused by every entity; entity-specific code belongs in `src/ypms/<entity>/`. If a framework enhancement is genuinely needed, propose it in its own PR with a written reason.
3. **Every state-mutating route emits an audit row.** Enforced by `CRUDService` and the router factory once the framework lands. Don't write handlers that bypass either. 3. **Every state-mutating route emits an audit row.** Enforced by `CRUDService`, the router factory, and the stub router. Don't write handlers that bypass them.
4. **501 stubs still fire RBAC + audit.** Deferred actions must route through `require_permission` and emit an audit event. Permission gaps surface at Phase 0 review, not at feature-completion. 4. **501 stubs still fire RBAC + audit.** Deferred actions must route through `require_permission` and emit an audit event. Permission gaps surface at Phase 0 review, not at feature-completion.
5. **Tests required.** `src/ypms/framework/` and `src/ypms/security/` need ≥ 80% coverage. Everything else: at least one happy-path test. 5. **Tests required.** `src/ypms/framework/` and `src/ypms/security/` need ≥ 80% coverage. Everything else: at least one happy-path test.
6. **No `--no-verify`, `# noqa`, or `# type: ignore` without an inline comment naming the specific reason.** If you need to silence a check, say why in the code. 6. **No `--no-verify`, `# noqa`, `# type: ignore`, `# nosec`, or `gitleaks:allow` without an inline comment naming the specific reason.** Suppressions are per-finding, never blanket. Don't lower a CI gate to get green.
7. **Sensitive fields never in logs.** SSN, banking, tokens, passwords, encryption keys — route through `sanitize_for_log` before emitting or don't emit. See `src/ypms/logging.py`. 7. **Sensitive fields never in logs.** SSN, banking, tokens, passwords, encryption keys — route through `sanitize_for_log` before emitting or don't emit. See `src/ypms/logging.py`.
8. **No commits attributed to a person who didn't write them.** Set `user.name` and `user.email` in `git config` to yourself before committing. Co-author trailers are welcome and encouraged for pairing. 8. **No commits attributed to a person who didn't write them.** Set `user.name` and `user.email` in `git config` to yourself before committing. Co-author trailers are welcome and encouraged for pairing.
9. **Reads of sensitive PII emit `pii_read`.** Any GET, list, or history route returning SSN-bearing, banking, FERPA, CFSA, or immigration data calls `framework.pii_audit` after RBAC passes (NIST AU-14).
10. **Encrypted columns go through `security.column_crypto` and into `__audit_redact__`.** Otherwise their ciphertext lands in `audit_log` and `version_history`. Sensitive values are write-only on the model and masked in the read schema.
11. **Every mapped table declares a retention policy.** The app refuses to start otherwise. Don't invent durations — use a `retention_classes.py` class, or an unset one citing the POA&M item that owns the decision.
12. **New model modules go in `src/ypms/models.py`.** Alembic, the retention job, and the tests all read that one list.
13. **Changes to vendored OWC code need a written reason in the module docstring** and get added to the "Upstream to OWC" list in the current phase status doc.
## Change control ## Change control
- Every substantive change ties to a POA&M ID, a phase-plan milestone, or an issue. Include the reference in the PR description. - Every substantive change ties to a POA&M ID, a phase-plan milestone, or an issue. Include the reference in the PR description.
@@ -37,9 +47,10 @@ Hard rules — mirrored from OWC's `CONTRIBUTING.md` because the two systems sha
- [ ] Tests pass locally (`pytest`). - [ ] Tests pass locally (`pytest`).
- [ ] Coverage doesn't drop below 80% on `framework/` or `security/`. - [ ] Coverage doesn't drop below 80% on `framework/` or `security/`.
- [ ] `ruff check`, `mypy` clean. - [ ] `ruff check`, `mypy` clean.
- [ ] SAST (`bandit`) and SCA (`pip-audit`) clean, or documented waivers. - [ ] SAST (`bandit`), SCA (`pip-audit --strict .`), and secret scan (gitleaks) clean, or per-finding waivers with reasons.
- [ ] Migration added (if schema changed); no edits to older migrations. - [ ] Migration added (if schema changed) and pinned in `migrations/IMMUTABLE.sha256`; no edits to older migrations.
- [ ] Any new endpoint has RBAC + audit + a test. - [ ] New tables have a retention policy; new model modules are in `src/ypms/models.py`.
- [ ] Any new endpoint has RBAC + audit + a test; PII reads emit `pii_read`.
- [ ] Any new sensitive field routes through the app-layer encryption helpers. - [ ] Any new sensitive field routes through the app-layer encryption helpers.
- [ ] `docs/nist-mapping.md` updated if the change wires a new NIST control. - [ ] `docs/nist-mapping.md` updated if the change wires a new NIST control.
- [ ] POA&M item updated if the change closes or advances one. - [ ] POA&M item updated if the change closes or advances one.
@@ -49,4 +60,4 @@ Hard rules — mirrored from OWC's `CONTRIBUTING.md` because the two systems sha
- Squash-merge is the default. Merge commit messages should read like release notes for that PR. - Squash-merge is the default. Merge commit messages should read like release notes for that PR.
- Force-pushes to `main` are not allowed. Force-pushes to feature branches during review are fine. - Force-pushes to `main` are not allowed. Force-pushes to feature branches during review are fine.
- CI must be green. - CI (Gitea Actions, `.gitea/workflows/ci.yml`) must be green.
+46 -30
View File
@@ -2,8 +2,8 @@
**System:** Youth Program Management System (YPMS) **System:** Youth Program Management System (YPMS)
**System Owner:** District of Columbia Department of Employment Services (DOES) **System Owner:** District of Columbia Department of Employment Services (DOES)
**Document version:** 0.2 (draft — in-house build reframe) **Document version:** 0.3 (draft — Phase 0 evidence populated)
**Date:** 2026-09-30 **Date:** 2026-10-01
**Author:** DOES CIO **Author:** DOES CIO
**Companion to:** [`SYSTEM_SECURITY_PLAN.md`](SYSTEM_SECURITY_PLAN.md) **Companion to:** [`SYSTEM_SECURITY_PLAN.md`](SYSTEM_SECURITY_PLAN.md)
@@ -85,6 +85,22 @@ The Moderate baseline contains ~261 controls including enhancements; the ~80 not
Status distribution will shift from 📝 to ✅/🟡/❌ as the codebase materializes; refresh at every phase gate. Status distribution will shift from 📝 to ✅/🟡/❌ as the codebase materializes; refresh at every phase gate.
### Phase 0 status shift (2026-10-01)
Rows touched by Phase 0 code. Each carries a dated **Phase 0** note with file:line evidence and the remaining gap; the same list with links is in [`docs/nist-mapping.md`](docs/nist-mapping.md). Rows whose status is 🤝 or ⚠️ keep it and gain a progress note.
| Family | ✅ Implemented | 🟡 Partial | Note only (🤝 / ⚠️ kept) |
|---|---:|---:|---:|
| AC | 5 | 2 | — |
| AU | 4 | 1 | 1 |
| CM | — | 1 | 1 |
| IA | 1 | 3 | — |
| RA | — | 1 | — |
| SA | — | 1 | — |
| SC | — | 3 | 1 |
| SI | 2 | — | 1 |
| **Totals** | **12** | **12** | **4** |
--- ---
## 4. Detailed Control Mapping ## 4. Detailed Control Mapping
@@ -94,23 +110,23 @@ Status distribution will shift from 📝 to ✅/🟡/❌ as the codebase materia
| Control | Status | Owner | RFP Ref | Implementation & Evidence | | Control | Status | Owner | RFP Ref | Implementation & Evidence |
|---|---|---|---|---| |---|---|---|---|---|
| **AC-1 Policy and Procedures** | 📋 | D | — | DOES OCTO IT Security Policy applies (`C.5.7.53`). DOES ISSO owns YPMS access-control procedure document. **Evidence:** signed DOES Access Control Policy, RoB (`ypms/RULES_OF_BEHAVIOR.md`). | | **AC-1 Policy and Procedures** | 📋 | D | — | DOES OCTO IT Security Policy applies (`C.5.7.53`). DOES ISSO owns YPMS access-control procedure document. **Evidence:** signed DOES Access Control Policy, RoB (`ypms/RULES_OF_BEHAVIOR.md`). |
| **AC-2 Account Management** | 📝 | O | `C.5.7.4`, `C.5.7.5`, `C.5.7.7`, `C.5.7.29`, `C.5.7.47` | Admin enable/disable; auto-disable on inactivity; 12–64 char alphanumeric IDs; group + individual assignment via Entra ID; no re-issue of deleted or previously-assigned IDs. **Evidence:** account-lifecycle event samples from Sentinel; documented account-provisioning workflow in `ypms/RULES_OF_BEHAVIOR.md`; quarterly access-recert records; DOES OIT personnel-access roster per source RFP `C.5.8.16`, `C.5.8.17` adopted as internal policy. | | **AC-2 Account Management** | 🟡 | O | `C.5.7.4`, `C.5.7.5`, `C.5.7.7`, `C.5.7.29`, `C.5.7.47` | Admin enable/disable; auto-disable on inactivity; 12–64 char alphanumeric IDs; group + individual assignment via Entra ID; no re-issue of deleted or previously-assigned IDs. **Evidence:** account-lifecycle event samples from Sentinel; documented account-provisioning workflow in `ypms/RULES_OF_BEHAVIOR.md`; quarterly access-recert records; DOES OIT personnel-access roster per source RFP `C.5.8.16`, `C.5.8.17` adopted as internal policy. **Phase 0 (2026-10-01):** User model with active flag; inactive accounts rejected at authentication. Evidence: `src/ypms/users/models.py:48`, `src/ypms/auth/deps.py:43`. Gap: No admin enable/disable endpoint (admin module, Phase 8); inactivity auto-disable is YPMS-POAM-071. |
| **AC-2(1) Automated Account Management** | 📝 | O | `C.5.7.4`, `C.5.7.31` | Automated inactivity-disable; real-time and scheduled access-right changes. **Evidence:** configuration screenshot of inactivity threshold; audit log showing an auto-disable event. | | **AC-2(1) Automated Account Management** | 📝 | O | `C.5.7.4`, `C.5.7.31` | Automated inactivity-disable; real-time and scheduled access-right changes. **Evidence:** configuration screenshot of inactivity threshold; audit log showing an auto-disable event. |
| **AC-2(3) Disable Inactive Accounts** | 📝 | O | `C.5.7.4` | Organization-defined inactivity threshold (`[TBD]` — recommend 90 days for staff, 180 days for youth accounts). **Evidence:** configured threshold value + audit sample. | | **AC-2(3) Disable Inactive Accounts** | 📝 | O | `C.5.7.4` | Organization-defined inactivity threshold (`[TBD]` — recommend 90 days for staff, 180 days for youth accounts). **Evidence:** configured threshold value + audit sample. |
| **AC-2(4) Automated Audit Actions** | 📝 | O | `C.5.7.36`, `C.5.7.39` | All account create/modify/enable/disable/remove actions auto-logged. **Evidence:** log samples. | | **AC-2(4) Automated Audit Actions** | 📝 | O | `C.5.7.36`, `C.5.7.39` | All account create/modify/enable/disable/remove actions auto-logged. **Evidence:** log samples. |
| **AC-3 Access Enforcement** | 📝 | O | `C.5.7.21`, `C.5.7.27`, `C.5.7.28`, `C.5.7.31`, `C.5.7.48` | Server-side authorization on every action; admin-only view/change of access rights; enforce authorizations per access-control policies. **Evidence:** access-control matrix showing every role × object; penetration-test evidence that unauthenticated and lower-role users cannot reach protected functions. | | **AC-3 Access Enforcement** | ✅ | O | `C.5.7.21`, `C.5.7.27`, `C.5.7.28`, `C.5.7.31`, `C.5.7.48` | Server-side authorization on every action; admin-only view/change of access rights; enforce authorizations per access-control policies. **Evidence:** access-control matrix showing every role × object; penetration-test evidence that unauthenticated and lower-role users cannot reach protected functions. **Phase 0 (2026-10-01):** `require_permission` runs on every protected route, including all 208 stub routes; portal-only callers restricted to their own Registrant row. Evidence: `src/ypms/security/rbac.py:46`, `src/ypms/youth/router.py:67`, `tests/test_stubs.py`. Gap: Row-level scoping beyond Registrant is Phase 2+. |
| **AC-4 Information Flow Enforcement** | 📝 | O | `C.5.7.23` | Trusted / transition / untrusted zone segmentation; untrusted systems cannot reach trusted-zone systems directly. **Evidence:** network architecture diagram (from the DOES OIT architecture document — `YPMS-POAM-040`); Azure NSG / firewall rule export. | | **AC-4 Information Flow Enforcement** | 📝 | O | `C.5.7.23` | Trusted / transition / untrusted zone segmentation; untrusted systems cannot reach trusted-zone systems directly. **Evidence:** network architecture diagram (from the DOES OIT architecture document — `YPMS-POAM-040`); Azure NSG / firewall rule export. |
| **AC-6 Least Privilege** | 📝 | O | `C.5.7.32`, `C.5.7.33`, `C.5.7.34` | RBAC over defined objects; one login per staff, role-mediated access; admin maintains role-based security. CFSA foster/ward data and OSSE FERPA records reachable only by role-flagged users. **Evidence:** role catalogue with data-class access mapping. | | **AC-6 Least Privilege** | ✅ | O | `C.5.7.32`, `C.5.7.33`, `C.5.7.34` | RBAC over defined objects; one login per staff, role-mediated access; admin maintains role-based security. CFSA foster/ward data and OSSE FERPA records reachable only by role-flagged users. **Evidence:** role catalogue with data-class access mapping. **Phase 0 (2026-10-01):** Data-driven RBAC (8 roles × 30 entities × 8 actions); FERPA/CFSA entities limited to ELIGIBILITY_EXAMINER; super-role explicit. Evidence: `src/ypms/seed/permissions_matrix.json`, `src/ypms/security/rbac.py:22`, `tests/test_stubs.py`. Gap: Four matrix review items open (PHASE0_STATUS.md). |
| **AC-6(1) Authorize Access to Security Functions** | 📝 | O | `C.5.7.28` | Only administrators can view/change access rights. **Evidence:** role catalogue. | | **AC-6(1) Authorize Access to Security Functions** | ✅ | O | `C.5.7.28` | Only administrators can view/change access rights. **Evidence:** role catalogue. **Phase 0 (2026-10-01):** Only DOES_SYSTEM_ADMIN holds RBAC-table rights (User/Role not granted to any other role). Evidence: `src/ypms/seed/permissions_matrix.json`. |
| **AC-6(2) Non-Privileged Access for Non-Security Functions** | 📝 | O | `C.5.7.33` | Staff use non-privileged accounts by default for routine work. **Evidence:** account-inventory review. | | **AC-6(2) Non-Privileged Access for Non-Security Functions** | 📝 | O | `C.5.7.33` | Staff use non-privileged accounts by default for routine work. **Evidence:** account-inventory review. |
| **AC-6(5) Privileged Accounts** | 📝 | O | | Named, approved list of privileged users. **Evidence:** privileged-user roster + quarterly recert. | | **AC-6(5) Privileged Accounts** | 📝 | O | | Named, approved list of privileged users. **Evidence:** privileged-user roster + quarterly recert. |
| **AC-6(9) Log Use of Privileged Functions** | 📝 | O | `C.5.7.36` | Auto-log all admin actions including account create/modify/enable/disable/remove. **Evidence:** log samples. | | **AC-6(9) Log Use of Privileged Functions** | 📝 | O | `C.5.7.36` | Auto-log all admin actions including account create/modify/enable/disable/remove. **Evidence:** log samples. |
| **AC-6(10) Prohibit Non-Privileged Users from Executing Privileged Functions** | 📝 | O | `C.5.7.27`, `C.5.7.32` | Enforced server-side. **Evidence:** pentest evidence. | | **AC-6(10) Prohibit Non-Privileged Users from Executing Privileged Functions** | ✅ | O | `C.5.7.27`, `C.5.7.32` | Enforced server-side. **Evidence:** pentest evidence. **Phase 0 (2026-10-01):** Delete and Restore (super-role only today) and Registrant VersionHistory (OYP_STAFF and ELIGIBILITY_EXAMINER only) enforced server-side; 403 asserted for every other role. Evidence: `src/ypms/security/rbac.py:29`, `tests/test_stubs.py`, `tests/youth/test_registrant_api.py`. |
| **AC-7 Unsuccessful Logon Attempts** | 📝 | O | `C.5.7.10`, `C.5.7.11` | Lockout after 5 attempts; user notified on revocation. **Evidence:** configured threshold; audit sample of a lockout event and notification. | | **AC-7 Unsuccessful Logon Attempts** | 🟡 | O | `C.5.7.10`, `C.5.7.11` | Lockout after 5 attempts; user notified on revocation. **Evidence:** configured threshold; audit sample of a lockout event and notification. **Phase 0 (2026-10-01):** 5 failed logins per source IP per 15 minutes → 429; every failure (bad credentials, unknown user, inactive, throttled) audited as `login_failed`. Evidence: `src/ypms/auth/rate_limit.py:47`, `src/ypms/auth/router.py:86`, `src/ypms/auth/router.py:65`. Gap: Per-account lockout (`locked_until`) and user notification not built; in-memory limiter is per-process. |
| **AC-8 System Use Notification** | 📝 | O | `C.5.7.37` | Notification banner before granting access. **Evidence:** screenshot of banner; DOES-approved banner text on file. | | **AC-8 System Use Notification** | 📝 | O | `C.5.7.37` | Notification banner before granting access. **Evidence:** screenshot of banner; DOES-approved banner text on file. |
| **AC-11 Session Lock** | 📝 | O | `C.5.7.3` | Idle-timeout session lock with re-authentication. **Evidence:** configured timeout value. | | **AC-11 Session Lock** | 📝 | O | `C.5.7.3` | Idle-timeout session lock with re-authentication. **Evidence:** configured timeout value. |
| **AC-11(1) Pattern-Hiding Displays** | 📝 | O | | Screen obscured on lock. **Evidence:** UX walkthrough. | | **AC-11(1) Pattern-Hiding Displays** | 📝 | O | | Screen obscured on lock. **Evidence:** UX walkthrough. |
| **AC-12 Session Termination** | 📝 | O | `C.5.7.3` | Idle session termination with re-authentication. **Evidence:** configured timeout value; log sample of a session-terminated event. | | **AC-12 Session Termination** | ✅ | O | `C.5.7.3` | Idle session termination with re-authentication. **Evidence:** configured timeout value; log sample of a session-terminated event. **Phase 0 (2026-10-01):** Access tokens expire (default 60 min, `exp` claim verified); refresh tokens cannot access resources. Evidence: `src/ypms/auth/security.py:59`, `src/ypms/auth/deps.py:35`. Gap: Idle-timeout UI behaviour lands with the portal (Phase 3). |
| **AC-14 Permitted Actions Without Identification or Authentication** | 📝 | O | | Limited to public content (program info, job postings, application intake as unauthenticated entry). **Evidence:** enumeration of unauthenticated endpoints. | | **AC-14 Permitted Actions Without Identification or Authentication** | 📝 | O | | Limited to public content (program info, job postings, application intake as unauthenticated entry). **Evidence:** enumeration of unauthenticated endpoints. |
| **AC-17 Remote Access** | 🤝 | O, D | | All access is remote by architecture. MFA required for staff and admin via Entra ID conditional access (see IA-2). **Evidence:** Entra CA policy export; endpoint-compliance policy (source RFP `C.5.6.30`). | | **AC-17 Remote Access** | 🤝 | O, D | | All access is remote by architecture. MFA required for staff and admin via Entra ID conditional access (see IA-2). **Evidence:** Entra CA policy export; endpoint-compliance policy (source RFP `C.5.6.30`). |
| **AC-17(1) Monitoring and Control** | 📝 | O | `C.5.6.9` #19 | SIEM monitoring of remote access. **Evidence:** SIEM feed. | | **AC-17(1) Monitoring and Control** | 📝 | O | `C.5.6.9` #19 | SIEM monitoring of remote access. **Evidence:** SIEM feed. |
@@ -135,18 +151,18 @@ Status distribution will shift from 📝 to ✅/🟡/❌ as the codebase materia
| Control | Status | Owner | RFP Ref | Implementation & Evidence | | Control | Status | Owner | RFP Ref | Implementation & Evidence |
|---|---|---|---|---| |---|---|---|---|---|
| **AU-1 Policy and Procedures** | 📋 | D | `C.5.7.53` | DOES OCTO logging policy applies. **Evidence:** policy. | | **AU-1 Policy and Procedures** | 📋 | D | `C.5.7.53` | DOES OCTO logging policy applies. **Evidence:** policy. |
| **AU-2 Event Logging** | 📝 | O | `C.5.7.39`, `C.5.6.9` #4 | Comprehensive event set: root/admin actions, audit-log access/modification, successful and unsuccessful logon attempts, password changes, system-level object create/delete, all PII/sensitive-data interactions, role changes, shutdown/restart, file access authorized and unauthorized, user logon events, plus DOES-defined additions. **Evidence:** event-catalog document; SIEM sample stream. | | **AU-2 Event Logging** | 🟡 | O | `C.5.7.39`, `C.5.6.9` #4 | Comprehensive event set: root/admin actions, audit-log access/modification, successful and unsuccessful logon attempts, password changes, system-level object create/delete, all PII/sensitive-data interactions, role changes, shutdown/restart, file access authorized and unauthorized, user logon events, plus DOES-defined additions. **Evidence:** event-catalog document; SIEM sample stream. **Phase 0 (2026-10-01):** Audited today: login success/failure, password change, every create/update/delete/restore, every PII read, every stub invocation, each retention run. Evidence: `src/ypms/audit/service.py:17`, `src/ypms/framework/crud.py:111`, `src/ypms/auth/router.py:65`, `src/ypms/auth/router.py:104`. Gap: Event catalogue items not yet applicable (role changes, shutdown, file access) arrive with their features. |
| **AU-3 Content of Audit Records** | 📝 | O | `C.5.7.15`, `C.5.7.48`, `C.5.7.49`, `C.5.6.9` #8, #11, #12, #13 | What/when/where/source/outcome/identity; sufficient to reconstruct events; unique transaction ID per transaction; before-and-after transaction history; communication send/access logging. **Evidence:** log-record specification; log sample. | | **AU-3 Content of Audit Records** | ✅ | O | `C.5.7.15`, `C.5.7.48`, `C.5.7.49`, `C.5.6.9` #8, #11, #12, #13 | What/when/where/source/outcome/identity; sufficient to reconstruct events; unique transaction ID per transaction; before-and-after transaction history; communication send/access logging. **Evidence:** log-record specification; log sample. **Phase 0 (2026-10-01):** Each row: actor id + username, action, entity, entity id, before/after, timestamp, IP; app-layer ciphertext redacted from before/after. Evidence: `src/ypms/audit/models.py:18`, `src/ypms/framework/crud.py:46`. Gap: Request id is in logs, not yet on the audit row. |
| **AU-3(1) Additional Audit Information** | 📝 | O | | Extended fields for financial-adjustment events (payroll approval, banking change, eligibility change). **Evidence:** log sample. | | **AU-3(1) Additional Audit Information** | 📝 | O | | Extended fields for financial-adjustment events (payroll approval, banking change, eligibility change). **Evidence:** log sample. |
| **AU-4 Audit Log Storage Capacity** | 📝 | O | | Sized for `[TBD]` retention (see AU-11). **Evidence:** capacity plan. | | **AU-4 Audit Log Storage Capacity** | 📝 | O | | Sized for `[TBD]` retention (see AU-11). **Evidence:** capacity plan. |
| **AU-5 Response to Audit Logging Process Failures** | 📝 | O | `C.5.7.26`, `C.5.6.9` #23 | Alerts to admins and additional roles on security compromise; user-account lockout alerts; logging-pipeline health monitoring. **Evidence:** runbook + alert-configuration screenshot. | | **AU-5 Response to Audit Logging Process Failures** | 📝 | O | `C.5.7.26`, `C.5.6.9` #23 | Alerts to admins and additional roles on security compromise; user-account lockout alerts; logging-pipeline health monitoring. **Evidence:** runbook + alert-configuration screenshot. |
| **AU-6 Audit Record Review, Analysis, and Reporting** | 🤝 | O, D | `C.5.6.9` #19–22 | Sentinel aggregation into meaningful security intelligence; alerts on business-rule/workflow thresholds. DC OCTO SOC consumes the feed. **Evidence:** Sentinel correlation-rule inventory; SOC alert-triage runbook. | | **AU-6 Audit Record Review, Analysis, and Reporting** | 🤝 | O, D | `C.5.6.9` #19–22 | Sentinel aggregation into meaningful security intelligence; alerts on business-rule/workflow thresholds. DC OCTO SOC consumes the feed. **Evidence:** Sentinel correlation-rule inventory; SOC alert-triage runbook. |
| **AU-7 Audit Record Reduction and Report Generation** | 📝 | O | `C.5.6.9` #20 | Configurable aggregation and reporting. **Evidence:** report templates. | | **AU-7 Audit Record Reduction and Report Generation** | 📝 | O | `C.5.6.9` #20 | Configurable aggregation and reporting. **Evidence:** report templates. |
| **AU-8 Time Stamps** | 📝 | O | `C.5.7.40` | All critical system times synchronized. **Evidence:** NTP configuration; sample logs showing consistent UTC/ISO-8601 timestamps. | | **AU-8 Time Stamps** | ✅ | O | `C.5.7.40` | All critical system times synchronized. **Evidence:** NTP configuration; sample logs showing consistent UTC/ISO-8601 timestamps. **Phase 0 (2026-10-01):** All timestamps timezone-aware UTC; Postgres container pinned to UTC. Evidence: `src/ypms/audit/models.py:24`, `docker-compose.yml:8`. Gap: NTP is inherited from Azure at deployment. |
| **AU-9 Protection of Audit Information** | 📝 | O | `C.5.7.41`, `C.5.6.9` #6 | Audit information and audit tools protected from unauthorized access, modification, and deletion; role-based access to audit logs. Sentinel ingestion moves logs to a separate trust boundary. **Evidence:** ACL export on audit store; Sentinel workspace RBAC. | | **AU-9 Protection of Audit Information** | 📝 | O | `C.5.7.41`, `C.5.6.9` #6 | Audit information and audit tools protected from unauthorized access, modification, and deletion; role-based access to audit logs. Sentinel ingestion moves logs to a separate trust boundary. **Evidence:** ACL export on audit store; Sentinel workspace RBAC. |
| **AU-11 Audit Record Retention** | 🤝 | O, D | `C.5.7.42` | Organization-defined; **DOES recommendation: 1 year online, 3 years archived** (align to DOES records schedule — `[TBD]`). **Evidence:** written retention policy; archive location and format. | | **AU-11 Audit Record Retention** | 🤝 | O, D | `C.5.7.42` | Organization-defined; **DOES recommendation: 1 year online, 3 years archived** (align to DOES records schedule — `[TBD]`). **Evidence:** written retention policy; archive location and format. **Phase 0 (2026-10-01):** Retention scaffold: audit log carries the PIA §9 interim policy (archive at 1 year); report-only job. Evidence: `src/ypms/retention_classes.py`, `src/ypms/framework/retention.py:150`. Gap: Period unapproved until YPMS-POAM-054; archive tier is deployment work. |
| **AU-12 Audit Record Generation** | 📝 | O | `C.5.7.39`, `C.5.6.9` #17, #18 | Automated audit trails for all system components; system-generated security audit trail; SANS Information Logging Standard compliance. **Evidence:** component inventory × logging coverage table. | | **AU-12 Audit Record Generation** | ✅ | O | `C.5.7.39`, `C.5.6.9` #17, #18 | Automated audit trails for all system components; system-generated security audit trail; SANS Information Logging Standard compliance. **Evidence:** component inventory × logging coverage table. **Phase 0 (2026-10-01):** Audit generation is in the framework (CRUDService, stub router, auth), not per-handler. Evidence: `src/ypms/framework/crud.py:111`, `src/ypms/framework/stub_router.py:66`. |
| **AU-14 Session Audit** | 📝 | O | `C.5.6.9` #5, #7 | Searchable log of all screens accessed by user; sensitive-data screen access monitored and recorded. **Evidence:** search-tool demo; log sample. | | **AU-14 Session Audit** | ✅ | O | `C.5.6.9` #5, #7 | Searchable log of all screens accessed by user; sensitive-data screen access monitored and recorded. **Evidence:** search-tool demo; log sample. **Phase 0 (2026-10-01):** `pii_read` audit on Registrant GET, list (ids recorded), and history. Evidence: `src/ypms/framework/pii_audit.py:36`, `src/ypms/youth/router.py:99`, `src/ypms/youth/router.py:115`. Gap: Extends to Guardian/TaxDocument/Attachment as they are built. |
### CA — Assessment, Authorization, and Monitoring ### CA — Assessment, Authorization, and Monitoring
@@ -170,13 +186,13 @@ Status distribution will shift from 📝 to ✅/🟡/❌ as the codebase materia
| **CM-2 Baseline Configuration** | 📝 | O | `C.5.6.13`, IEEE 828-2012 | Application baseline in the repo (Dockerfile / compose / IaC); DOES-approved tool/software list. **Evidence:** baseline files in `ypms/`; CM plan (see CM-9). | | **CM-2 Baseline Configuration** | 📝 | O | `C.5.6.13`, IEEE 828-2012 | Application baseline in the repo (Dockerfile / compose / IaC); DOES-approved tool/software list. **Evidence:** baseline files in `ypms/`; CM plan (see CM-9). |
| **CM-2(2) Automation Support for Accuracy and Currency** | 📝 | O | | IaC (Bicep or Terraform) maintains Azure baseline. **Evidence:** tool inventory; IaC repo path. | | **CM-2(2) Automation Support for Accuracy and Currency** | 📝 | O | | IaC (Bicep or Terraform) maintains Azure baseline. **Evidence:** tool inventory; IaC repo path. |
| **CM-2(3) Retention of Previous Configurations** | 📝 | O | | Rollback-capable baseline history via git. **Evidence:** git log. | | **CM-2(3) Retention of Previous Configurations** | 📝 | O | | Rollback-capable baseline history via git. **Evidence:** git log. |
| **CM-3 Configuration Change Control** | 🤝 | O, D | `C.5.6.13`, `C.5.6.17` | DOES CIO approves changes; 30-day advance notice for maintenance windows; NOC coordination; emergency-change track. **Evidence:** change-record register; monthly change-review minutes. | | **CM-3 Configuration Change Control** | 🤝 | O, D | `C.5.6.13`, `C.5.6.17` | DOES CIO approves changes; 30-day advance notice for maintenance windows; NOC coordination; emergency-change track. **Evidence:** change-record register; monthly change-review minutes. **Phase 0 (2026-10-01):** Merged migrations pinned by SHA-256; CI fails on any edit. Every change ships through a reviewed commit. Evidence: `migrations/IMMUTABLE.sha256`, `tests/test_migration_immutability.py:31`. Gap: Maintenance-window process is organizational. |
| **CM-4 Impact Analysis** | 📝 | O | `C.5.8.12` | Required before database migrations affecting programs already in production. **Evidence:** impact-analysis template + sample; migration review discipline. | | **CM-4 Impact Analysis** | 📝 | O | `C.5.8.12` | Required before database migrations affecting programs already in production. **Evidence:** impact-analysis template + sample; migration review discipline. |
| **CM-5 Access Restrictions for Change** | 📝 | O | | Only DOES OIT delivery leads have merge rights on `main`; branch protection + code review required. **Evidence:** git branch-protection settings; Entra group ACL. | | **CM-5 Access Restrictions for Change** | 📝 | O | | Only DOES OIT delivery leads have merge rights on `main`; branch protection + code review required. **Evidence:** git branch-protection settings; Entra group ACL. |
| **CM-6 Configuration Settings** | 📝 | O | `C.5.6.5`, `C.5.6.6`, `C.5.6.7` | Admin-configurable security controls; CA configuration; DB/space/performance configuration. DOES OIT establishes secure baseline (CIS Azure Foundations benchmark + DOES overlays). **Evidence:** baseline settings export; Azure Policy assignment. | | **CM-6 Configuration Settings** | 📝 | O | `C.5.6.5`, `C.5.6.6`, `C.5.6.7` | Admin-configurable security controls; CA configuration; DB/space/performance configuration. DOES OIT establishes secure baseline (CIS Azure Foundations benchmark + DOES overlays). **Evidence:** baseline settings export; Azure Policy assignment. |
| **CM-7 Least Functionality** | 📝 | O | | Unused features disabled. **Evidence:** enabled-feature inventory. | | **CM-7 Least Functionality** | 📝 | O | | Unused features disabled. **Evidence:** enabled-feature inventory. |
| **CM-7(1) Periodic Review** | 📝 | O | | Quarterly review of enabled features. **Evidence:** review record. | | **CM-7(1) Periodic Review** | 📝 | O | | Quarterly review of enabled features. **Evidence:** review record. |
| **CM-8 System Component Inventory** | 📝 | O | `C.5.6.13` | Comprehensive tool/software list with licensing and upgrade path. **Evidence:** SBOM (CycloneDX, per release, from CI) + tool inventory. | | **CM-8 System Component Inventory** | 🟡 | O | `C.5.6.13` | Comprehensive tool/software list with licensing and upgrade path. **Evidence:** SBOM (CycloneDX, per release, from CI) + tool inventory. **Phase 0 (2026-10-01):** CycloneDX 1.6 SBOM of the runtime dependency set generated in CI. Evidence: `.gitea/workflows/ci.yml:70`. Gap: Pending first green CI run (Gitea runner); per-release retention of SBOMs not yet defined. |
| **CM-8(1) Updates During Installation and Removal** | 📝 | O | | Inventory auto-updated via CI pipeline. **Evidence:** tooling. | | **CM-8(1) Updates During Installation and Removal** | 📝 | O | | Inventory auto-updated via CI pipeline. **Evidence:** tooling. |
| **CM-9 Configuration Management Plan** | 📝 | O | | `ypms/CONFIGURATION_MANAGEMENT_PLAN.md` (to be authored; also serves source RFP §C.5.12 Change Management Plan). **Evidence:** signed plan. | | **CM-9 Configuration Management Plan** | 📝 | O | | `ypms/CONFIGURATION_MANAGEMENT_PLAN.md` (to be authored; also serves source RFP §C.5.12 Change Management Plan). **Evidence:** signed plan. |
| **CM-10 Software Usage Restrictions** | 📋 | D | `C.5.6.13` | DOES-approved software list; license terms respected. **Evidence:** approved-software list. | | **CM-10 Software Usage Restrictions** | 📋 | D | `C.5.6.13` | DOES-approved software list; license terms respected. **Evidence:** approved-software list. |
@@ -205,15 +221,15 @@ Status distribution will shift from 📝 to ✅/🟡/❌ as the codebase materia
| Control | Status | Owner | RFP Ref | Implementation & Evidence | | Control | Status | Owner | RFP Ref | Implementation & Evidence |
|---|---|---|---|---| |---|---|---|---|---|
| **IA-1 Policy and Procedures** | 📋 | D | | DOES identity policy. **Evidence:** policy. | | **IA-1 Policy and Procedures** | 📋 | D | | DOES identity policy. **Evidence:** policy. |
| **IA-2 Identification and Authentication (Organizational Users)** | 📝 | O | `C.5.7.38`, `C.5.7.36` | Unique user ID per user; admin actions tied to individual identity. **Evidence:** IdP integration record. | | **IA-2 Identification and Authentication (Organizational Users)** | ✅ | O | `C.5.7.38`, `C.5.7.36` | Unique user ID per user; admin actions tied to individual identity. **Evidence:** IdP integration record. **Phase 0 (2026-10-01):** Every non-public route requires a valid bearer token bound to a unique, active user. Evidence: `src/ypms/auth/deps.py:25`. Gap: MFA enhancements (IA-2(1)/(2)) arrive with Entra ID in Phase 1. |
| **IA-2(1) MFA to Privileged Accounts** | ⚠️ | O | `C.5.7.6` | Source RFP `C.5.7.6` permits "unique identifier + shared secret answer" — **insufficient by NIST 800-63B AAL2**. **SSP tailors above the source RFP: MFA required for all privileged and staff accounts** via Entra ID conditional access. **Evidence:** Entra CA policy export; sample log showing MFA challenge (`YPMS-POAM-030`). | | **IA-2(1) MFA to Privileged Accounts** | ⚠️ | O | `C.5.7.6` | Source RFP `C.5.7.6` permits "unique identifier + shared secret answer" — **insufficient by NIST 800-63B AAL2**. **SSP tailors above the source RFP: MFA required for all privileged and staff accounts** via Entra ID conditional access. **Evidence:** Entra CA policy export; sample log showing MFA challenge (`YPMS-POAM-030`). |
| **IA-2(2) MFA to Non-Privileged Accounts** | ⚠️ | O | | Same tailoring: MFA for all staff-role accounts. **Evidence:** as IA-2(1). | | **IA-2(2) MFA to Non-Privileged Accounts** | ⚠️ | O | | Same tailoring: MFA for all staff-role accounts. **Evidence:** as IA-2(1). |
| **IA-2(8) Access to Accounts — Replay Resistant** | 📝 | O | | Modern MFA protocols. **Evidence:** protocol inventory. | | **IA-2(8) Access to Accounts — Replay Resistant** | 📝 | O | | Modern MFA protocols. **Evidence:** protocol inventory. |
| **IA-2(12) Acceptance of PIV Credentials** | 📋 | D | | DC OCTO enterprise: no PIV in scope; DOES employee credentials via DOES SSO. **Evidence:** IdP config. | | **IA-2(12) Acceptance of PIV Credentials** | 📋 | D | | DC OCTO enterprise: no PIV in scope; DOES employee credentials via DOES SSO. **Evidence:** IdP config. |
| **IA-3 Device Identification and Authentication** | 📝 | O | | Device certificates for managed endpoints. **Evidence:** MDM cert deployment. | | **IA-3 Device Identification and Authentication** | 📝 | O | | Device certificates for managed endpoints. **Evidence:** MDM cert deployment. |
| **IA-4 Identifier Management** | 📝 | O | `C.5.7.7`, `C.5.7.38`, `C.5.7.47` | 12-alphanumeric minimum; unique per user; no re-issue of deleted IDs. **Evidence:** IdP configuration; audit sample. | | **IA-4 Identifier Management** | 🟡 | O | `C.5.7.7`, `C.5.7.38`, `C.5.7.47` | 12-alphanumeric minimum; unique per user; no re-issue of deleted IDs. **Evidence:** IdP configuration; audit sample. **Phase 0 (2026-10-01):** Usernames unique (DB constraint); no user-delete path exists, so identifiers are never re-issued. Evidence: `migrations/versions/0002_framework_tables.py:54`. Gap: 12-character minimum identifier and explicit tombstoning arrive with registration (Phase 1). |
| **IA-5 Authenticator Management** | 📝 | O | `C.5.7.8`, `C.5.7.12`, `C.5.7.13`, `C.5.7.14`, `C.5.7.45`, `C.5.7.46` | User-established and changeable passwords; admin reset with forced change; expiration; email/SMS notification on change; complexity/reuse/length policy; masked entry. **Evidence:** password-policy configuration; user-communication template. | | **IA-5 Authenticator Management** | 🟡 | O | `C.5.7.8`, `C.5.7.12`, `C.5.7.13`, `C.5.7.14`, `C.5.7.45`, `C.5.7.46` | User-established and changeable passwords; admin reset with forced change; expiration; email/SMS notification on change; complexity/reuse/length policy; masked entry. **Evidence:** password-policy configuration; user-communication template. **Phase 0 (2026-10-01):** Users change their own password (12-character floor, current password required); hashes only. Evidence: `src/ypms/auth/router.py:138`, `src/ypms/auth/router.py:147`. Gap: Password-change notification (C.5.7.14) and admin reset arrive with notifications/admin. |
| **IA-5(1) Password-Based Authentication** | 📝 | O | `C.5.7.9`, `C.5.7.45` | FIPS 140-2 crypto (SSP tailors to FIPS 140-3 forward alignment); policy per RFP. **Evidence:** cryptographic-module cert; policy document. | | **IA-5(1) Password-Based Authentication** | 🟡 | O | `C.5.7.9`, `C.5.7.45` | FIPS 140-2 crypto (SSP tailors to FIPS 140-3 forward alignment); policy per RFP. **Evidence:** cryptographic-module cert; policy document. **Phase 0 (2026-10-01):** PBKDF2-SHA256 (NIST SP 800-132) via passlib. Evidence: `src/ypms/auth/security.py:30`. Gap: FIPS 140-3 validated module is deployment-dependent (YPMS-POAM-032). |
| **IA-6 Authenticator Feedback** | 📝 | O | `C.5.7.46` | Masked password entry. **Evidence:** UI walkthrough. | | **IA-6 Authenticator Feedback** | 📝 | O | `C.5.7.46` | Masked password entry. **Evidence:** UI walkthrough. |
| **IA-7 Cryptographic Module Authentication** | 📝 | O | `C.5.7.9` | FIPS 140-2 validated modules (SSP tailors: FIPS 140-3 where available). **Evidence:** module cert; usage inventory. | | **IA-7 Cryptographic Module Authentication** | 📝 | O | `C.5.7.9` | FIPS 140-2 validated modules (SSP tailors: FIPS 140-3 where available). **Evidence:** module cert; usage inventory. |
| **IA-8 Identification and Authentication (Non-Organizational Users)** | ❓ | O | `C.5.3.2`, `C.5.7.30` | Youth and host authentication; LDAP-compliant; social-media login option. **Open decision (SSP §17.D):** identity model for minors — recommend social-media login for browsing/pre-application only, with IAL2-equivalent identity proofing (Login.gov or SSA verification) before banking/payroll actions. **Evidence:** identity-flow design; risk-acceptance memo. | | **IA-8 Identification and Authentication (Non-Organizational Users)** | ❓ | O | `C.5.3.2`, `C.5.7.30` | Youth and host authentication; LDAP-compliant; social-media login option. **Open decision (SSP §17.D):** identity model for minors — recommend social-media login for browsing/pre-application only, with IAL2-equivalent identity proofing (Login.gov or SSA verification) before banking/payroll actions. **Evidence:** identity-flow design; risk-acceptance memo. |
@@ -349,7 +365,7 @@ Status distribution will shift from 📝 to ✅/🟡/❌ as the codebase materia
| **RA-2 Security Categorization** | 📋 | D | | `ypms/FIPS_199_MEMO.md`. | | **RA-2 Security Categorization** | 📋 | D | | `ypms/FIPS_199_MEMO.md`. |
| **RA-3 Risk Assessment** | 🤝 | O, D | | Initial RA before ATO; annually thereafter; on major change. **Evidence:** RA report. | | **RA-3 Risk Assessment** | 🤝 | O, D | | Initial RA before ATO; annually thereafter; on major change. **Evidence:** RA report. |
| **RA-3(1) Supply Chain Risk Assessment** | 📝 | O | | Third-party and fourth-party disclosure in architecture document. **Evidence:** SBOM + third/fourth-party register. | | **RA-3(1) Supply Chain Risk Assessment** | 📝 | O | | Third-party and fourth-party disclosure in architecture document. **Evidence:** SBOM + third/fourth-party register. |
| **RA-5 Vulnerability Monitoring and Scanning** | 📝 | O | `C.5.7.52` | Cyber-threat detection and remediation; authenticated and unauthenticated scans monthly; container image scans in CI on push (`YPMS-POAM-062`). **Evidence:** scan schedule + reports. | | **RA-5 Vulnerability Monitoring and Scanning** | 🟡 | O | `C.5.7.52` | Cyber-threat detection and remediation; authenticated and unauthenticated scans monthly; container image scans in CI on push (`YPMS-POAM-062`). **Evidence:** scan schedule + reports. **Phase 0 (2026-10-01):** pip-audit on runtime dependencies gates CI; an unfixable `ecdsa` advisory was removed by changing JWT library. Evidence: `.gitea/workflows/ci.yml:59`. Gap: Pending first green CI run; infrastructure scanning is deployment work. |
| **RA-5(2) Update Vulnerabilities to be Scanned** | 📝 | O | | Feed updates. **Evidence:** feed configuration. | | **RA-5(2) Update Vulnerabilities to be Scanned** | 📝 | O | | Feed updates. **Evidence:** feed configuration. |
| **RA-5(5) Privileged Access** | 📝 | O | | Authenticated scans with privileged access. **Evidence:** scan configuration. | | **RA-5(5) Privileged Access** | 📝 | O | | Authenticated scans with privileged access. **Evidence:** scan configuration. |
| **RA-7 Risk Response** | 📋 | D | | POA&M-driven. **Evidence:** POA&M. | | **RA-7 Risk Response** | 📋 | D | | POA&M-driven. **Evidence:** POA&M. |
@@ -368,7 +384,7 @@ Status distribution will shift from 📝 to ✅/🟡/❌ as the codebase materia
| **SA-8 Security and Privacy Engineering Principles** | 📝 | O | | SSDLC adherence. **Evidence:** SSDLC document. | | **SA-8 Security and Privacy Engineering Principles** | 📝 | O | | SSDLC adherence. **Evidence:** SSDLC document. |
| **SA-9 External System Services** | 📝 | O | `C.5.6.3` | Payroll, LMS, address verification, social-media IdPs, DC MAR — each with security assurance. **Evidence:** vendor register + SIG-Lite / SOC 2 collection. | | **SA-9 External System Services** | 📝 | O | `C.5.6.3` | Payroll, LMS, address verification, social-media IdPs, DC MAR — each with security assurance. **Evidence:** vendor register + SIG-Lite / SOC 2 collection. |
| **SA-10 Developer Configuration Management** | 📝 | O | `C.5.8.4`–`C.5.8.11` | Data-model deliverables (conceptual/logical/physical ERDs, data dictionary, integrity rules, CRUD matrix, denormalization rationale). **Evidence:** deliverables in `ypms/`. | | **SA-10 Developer Configuration Management** | 📝 | O | `C.5.8.4`–`C.5.8.11` | Data-model deliverables (conceptual/logical/physical ERDs, data dictionary, integrity rules, CRUD matrix, denormalization rationale). **Evidence:** deliverables in `ypms/`. |
| **SA-11 Developer Testing and Evaluation** | 📝 | O | source RFP §C.5.10 | Unit / functional / non-functional / UAT with evidence. **Evidence:** test reports; CI test results. | | **SA-11 Developer Testing and Evaluation** | 🟡 | O | source RFP §C.5.10 | Unit / functional / non-functional / UAT with evidence. **Evidence:** test reports; CI test results. **Phase 0 (2026-10-01):** 167 automated tests, 80% coverage floor, ruff, strict mypy, bandit SAST, migration-drift test. Evidence: `.gitea/workflows/ci.yml:53`, `.gitea/workflows/ci.yml:56`. Gap: Pending first green CI run. |
| **SA-15 Development Process, Standards, and Tools** | 📝 | O | | DOES OIT SSDLC standards. **Evidence:** SSDLC document. | | **SA-15 Development Process, Standards, and Tools** | 📝 | O | | DOES OIT SSDLC standards. **Evidence:** SSDLC document. |
| **SA-22 Unsupported System Components** | 📝 | O | `C.5.6.13` | Third-party dependencies tracked in SBOM with upgrade path. **Evidence:** SBOM + upgrade-path statement. | | **SA-22 Unsupported System Components** | 📝 | O | `C.5.6.13` | Third-party dependencies tracked in SBOM with upgrade path. **Evidence:** SBOM + upgrade-path statement. |
@@ -384,11 +400,11 @@ Status distribution will shift from 📝 to ✅/🟡/❌ as the codebase materia
| **SC-7(4) External Telecommunications Services** | 📝 | O | | Documented external services with ISAs. | | **SC-7(4) External Telecommunications Services** | 📝 | O | | Documented external services with ISAs. |
| **SC-7(5) Deny by Default / Allow by Exception** | 📝 | O | | Default-deny firewall posture. **Evidence:** ruleset export. | | **SC-7(5) Deny by Default / Allow by Exception** | 📝 | O | | Default-deny firewall posture. **Evidence:** ruleset export. |
| **SC-7(7) Split Tunneling for Remote Devices** | 📝 | O | | No split tunneling from managed devices. | | **SC-7(7) Split Tunneling for Remote Devices** | 📝 | O | | No split tunneling from managed devices. |
| **SC-8 Transmission Confidentiality and Integrity** | ⚠️ | O | `C.5.6.15`, `C.5.7.25`, `C.5.7.20` | Source RFP says SSL; **SSP tailors to TLS 1.2 minimum, TLS 1.3 preferred, FIPS-approved cipher suites**. All PII encrypted in transit. **Evidence:** SSL Labs–style report; TLS config (`YPMS-POAM-031`). | | **SC-8 Transmission Confidentiality and Integrity** | ⚠️ | O | `C.5.6.15`, `C.5.7.25`, `C.5.7.20` | Source RFP says SSL; **SSP tailors to TLS 1.2 minimum, TLS 1.3 preferred, FIPS-approved cipher suites**. All PII encrypted in transit. **Evidence:** SSL Labs–style report; TLS config (`YPMS-POAM-031`). **Phase 0 (2026-10-01):** Not wired in Phase 0 (no local TLS proxy). Gap: TLS terminates at Front Door / App Gateway at deployment (YPMS-POAM-031). |
| **SC-8(1) Cryptographic Protection** | 📝 | O | `C.5.7.9` | FIPS 140-2/-3 cryptography for transmission. **Evidence:** module cert. | | **SC-8(1) Cryptographic Protection** | 📝 | O | `C.5.7.9` | FIPS 140-2/-3 cryptography for transmission. **Evidence:** module cert. |
| **SC-10 Network Disconnect** | 📝 | O | `C.5.7.3` | Idle-session network disconnect. **Evidence:** timeout config. | | **SC-10 Network Disconnect** | 📝 | O | `C.5.7.3` | Idle-session network disconnect. **Evidence:** timeout config. |
| **SC-12 Cryptographic Key Establishment and Management** | 📝 | O | `C.5.7.9` | Key management via Azure Key Vault (Gov / HSM). **Evidence:** key-management design; rotation policy (`YPMS-POAM-032`). | | **SC-12 Cryptographic Key Establishment and Management** | 🟡 | O | `C.5.7.9` | Key management via Azure Key Vault (Gov / HSM). **Evidence:** key-management design; rotation policy (`YPMS-POAM-032`). **Phase 0 (2026-10-01):** Column key derived with HKDF-SHA256; ≥32-byte key material and ≥32-byte JWT secret enforced at startup. Evidence: `src/ypms/security/column_crypto.py:49`, `src/ypms/settings.py:15`. Gap: Keys come from env vars; Key Vault (Gov/HSM) in Phase 1. |
| **SC-13 Cryptographic Protection** | 📝 | O | `C.5.7.9`, `C.5.7.25` | FIPS 140-3 cryptography (140-2 acceptable during deprecation window) at rest and in transit. **Evidence:** module cert + usage map. | | **SC-13 Cryptographic Protection** | 🟡 | O | `C.5.7.9`, `C.5.7.25` | FIPS 140-3 cryptography (140-2 acceptable during deprecation window) at rest and in transit. **Evidence:** module cert + usage map. **Phase 0 (2026-10-01):** FIPS-approved algorithms only: AES-256-GCM, HKDF-SHA256, HMAC-SHA256 (JWT), PBKDF2-SHA256. Evidence: `src/ypms/security/column_crypto.py:55`. Gap: Validated-module status is deployment-dependent (YPMS-POAM-032). |
| **SC-15 Collaborative Computing Devices** | 📝 | O | | Camera/mic access disabled by default in workspace tooling. | | **SC-15 Collaborative Computing Devices** | 📝 | O | | Camera/mic access disabled by default in workspace tooling. |
| **SC-17 Public Key Infrastructure Certificates** | 📝 | O | `C.5.6.5` | Certificate Authority configuration for integrations. **Evidence:** CA config. | | **SC-17 Public Key Infrastructure Certificates** | 📝 | O | `C.5.6.5` | Certificate Authority configuration for integrations. **Evidence:** CA config. |
| **SC-18 Mobile Code** | 📝 | O | | Only signed mobile code allowed. | | **SC-18 Mobile Code** | 📝 | O | | Only signed mobile code allowed. |
@@ -396,7 +412,7 @@ Status distribution will shift from 📝 to ✅/🟡/❌ as the codebase materia
| **SC-21 Secure Name/Address Resolution Service (Recursive)** | 🔵 | I | | Inherited. | | **SC-21 Secure Name/Address Resolution Service (Recursive)** | 🔵 | I | | Inherited. |
| **SC-22 Architecture and Provisioning for Name/Address Resolution** | 🔵 | I | | Inherited. | | **SC-22 Architecture and Provisioning for Name/Address Resolution** | 🔵 | I | | Inherited. |
| **SC-23 Session Authenticity** | 📝 | O | `C.5.7.20` | Anti-CSRF, secure cookies, per-transaction integrity for financial actions. **Evidence:** session-management design. | | **SC-23 Session Authenticity** | 📝 | O | `C.5.7.20` | Anti-CSRF, secure cookies, per-transaction integrity for financial actions. **Evidence:** session-management design. |
| **SC-28 Protection of Information at Rest** | 📝 | O | `C.5.7.25`, `C.5.7.50` | Encryption at rest for all PII (Postgres TDE + envelope encryption for blobs); SSN masked/anonymized in display (`YPMS-POAM-080`). **Evidence:** DB encryption config; SSN-handling design. | | **SC-28 Protection of Information at Rest** | 🟡 | O | `C.5.7.25`, `C.5.7.50` | Encryption at rest for all PII (Postgres TDE + envelope encryption for blobs); SSN masked/anonymized in display (`YPMS-POAM-080`). **Evidence:** DB encryption config; SSN-handling design. **Phase 0 (2026-10-01):** SSN encrypted at the app layer (AES-256-GCM, column-bound AAD); write-only on the model; masked in every response. Evidence: `src/ypms/security/column_crypto.py:55`, `src/ypms/youth/models.py:83`. Gap: Banking columns not yet built; Postgres TDE and blob encryption are deployment. |
| **SC-28(1) Cryptographic Protection** | 📝 | O | `C.5.7.9` | FIPS-approved cryptography for at-rest data via Azure Key Vault. **Evidence:** module cert. | | **SC-28(1) Cryptographic Protection** | 📝 | O | `C.5.7.9` | FIPS-approved cryptography for at-rest data via Azure Key Vault. **Evidence:** module cert. |
| **SC-39 Process Isolation** | 🔵 | I | | Inherited from IaaS + COTS platform. | | **SC-39 Process Isolation** | 🔵 | I | | Inherited from IaaS + COTS platform. |
@@ -416,9 +432,9 @@ Status distribution will shift from 📝 to ✅/🟡/❌ as the codebase materia
| **SI-7 Software, Firmware, and Information Integrity** | 📝 | O | | Signed artifacts + integrity checks on deploy. | | **SI-7 Software, Firmware, and Information Integrity** | 📝 | O | | Signed artifacts + integrity checks on deploy. |
| **SI-7(1) Integrity Checks** | 📝 | O | | On startup and periodically. | | **SI-7(1) Integrity Checks** | 📝 | O | | On startup and periodically. |
| **SI-8 Spam Protection** | 🤝 | O, D | | Inbound-email spam protection at DOES enterprise level. | | **SI-8 Spam Protection** | 🤝 | O, D | | Inbound-email spam protection at DOES enterprise level. |
| **SI-10 Information Input Validation** | 📝 | O | `C.5.7.44`, `C.5.6.2`, `C.5.6.3.b`, `C.5.6.3.d` | Character set / length / numerical range / format / content validation with trusted-source restriction; address, age, residency data checks; SSA SSN verification; DC MAR address validation. **Evidence:** input-validation rules; sample validation-failure log. | | **SI-10 Information Input Validation** | ✅ | O | `C.5.7.44`, `C.5.6.2`, `C.5.6.3.b`, `C.5.6.3.d` | Character set / length / numerical range / format / content validation with trusted-source restriction; address, age, residency data checks; SSA SSN verification; DC MAR address validation. **Evidence:** input-validation rules; sample validation-failure log. **Phase 0 (2026-10-01):** Pydantic validation on every request body; SSN format and never-issued ranges, DOB, ZIP, email, lengths. Evidence: `src/ypms/youth/schemas.py:21`. |
| **SI-11 Error Handling** | 📝 | O | `C.5.7.43`, `C.5.6.9` #2, #3 | Corrective information without adversary-exploitable disclosure; consistent error handling; all errors logged. **Evidence:** error-response inventory. | | **SI-11 Error Handling** | ✅ | O | `C.5.7.43`, `C.5.6.9` #2, #3 | Corrective information without adversary-exploitable disclosure; consistent error handling; all errors logged. **Evidence:** error-response inventory. **Phase 0 (2026-10-01):** Unhandled errors return a generic body with request id; logs record type and frames, not messages; SQL parameters hidden. Evidence: `src/ypms/main.py:56`, `src/ypms/db.py:24`, `tests/test_app_guards.py`. |
| **SI-12 Information Management and Retention** | 🤝 | O, D | | Aligned with DOES records schedule. **Evidence:** retention policy. | | **SI-12 Information Management and Retention** | 🤝 | O, D | | Aligned with DOES records schedule. **Evidence:** retention policy. **Phase 0 (2026-10-01):** Every table carries a retention policy; app refuses to boot otherwise; nightly report-only job. Evidence: `src/ypms/framework/retention.py:135`, `src/ypms/main.py:34`. Gap: Disposal execution is Phase 5; periods await YPMS-POAM-054. |
| **SI-16 Memory Protection** | 🔵 | I | | Inherited from IaaS / OS. | | **SI-16 Memory Protection** | 🔵 | I | | Inherited from IaaS / OS. |
### SR — Supply Chain Risk Management ### SR — Supply Chain Risk Management
+15 -15
View File
@@ -79,7 +79,7 @@ Every Phase 0 component maps to a source-RFP requirement or a NIST control. Tabl
| **0.6** | 501-stub routers for remaining entities | ✅ **Complete 2026-09-30.** 26 stub entities registered in `src/ypms/stubs.py`, each with a router from `framework/stub_router.py` exposing one route per matrix action (8). Every route runs the real RBAC check and writes an audit row before returning 501. `tests/test_stubs.py` sweeps entity × role × route against the matrix JSON (501 + one audit row when granted; 403 + none when not). Registrant restore moved to the matrix `Restore` action. 138 tests, 95.05% coverage. | | **0.6** | 501-stub routers for remaining entities | ✅ **Complete 2026-09-30.** 26 stub entities registered in `src/ypms/stubs.py`, each with a router from `framework/stub_router.py` exposing one route per matrix action (8). Every route runs the real RBAC check and writes an audit row before returning 501. `tests/test_stubs.py` sweeps entity × role × route against the matrix JSON (501 + one audit row when granted; 403 + none when not). Registrant restore moved to the matrix `Restore` action. 138 tests, 95.05% coverage. |
| **0.7** | Retention scaffold | ✅ **Complete 2026-10-01.** Every mapped table resolves a `RetentionPolicy` (entity models declare `retention_policy`; vendored framework tables mapped by name). PIA §9 interim periods encoded in `src/ypms/retention_classes.py`, all unapproved pending `YPMS-POAM-054`; classes without a PIA basis report as `unset`. Report-only job in `src/ypms/framework/retention.py` (`python -m ypms.framework.retention`) logs "0 records to purge" and writes a `retention_run` audit row. Stub entity models + migration `0004_stub_entity_tables`. Migration-vs-model drift test added. 156 tests, 95.91% coverage. | | **0.7** | Retention scaffold | ✅ **Complete 2026-10-01.** Every mapped table resolves a `RetentionPolicy` (entity models declare `retention_policy`; vendored framework tables mapped by name). PIA §9 interim periods encoded in `src/ypms/retention_classes.py`, all unapproved pending `YPMS-POAM-054`; classes without a PIA basis report as `unset`. Report-only job in `src/ypms/framework/retention.py` (`python -m ypms.framework.retention`) logs "0 records to purge" and writes a `retention_run` audit row. Stub entity models + migration `0004_stub_entity_tables`. Migration-vs-model drift test added. 156 tests, 95.91% coverage. |
| **0.8** | CI pipeline | 🟡 **Pipeline complete 2026-10-01; green-on-`main` pending a Gitea runner.** `.gitea/workflows/ci.yml`: ruff, mypy, pytest (80% floor, includes migration-drift test), bandit, pip-audit (runtime deps), gitleaks (pinned + sha256-verified, full history), CycloneDX SBOM artifact. Every step passes locally. Running the gates fixed a broken pip-audit invocation, removed `ecdsa` by swapping python-jose → PyJWT, and enforced a 32-byte `JWT_SECRET`. Closes when the first run on `main` is green. | | **0.8** | CI pipeline | 🟡 **Pipeline complete 2026-10-01; green-on-`main` pending a Gitea runner.** `.gitea/workflows/ci.yml`: ruff, mypy, pytest (80% floor, includes migration-drift test), bandit, pip-audit (runtime deps), gitleaks (pinned + sha256-verified, full history), CycloneDX SBOM artifact. Every step passes locally. Running the gates fixed a broken pip-audit invocation, removed `ecdsa` by swapping python-jose → PyJWT, and enforced a 32-byte `JWT_SECRET`. Closes when the first run on `main` is green. |
| **0.9** | Docs + acceptance | `README.md` with runnable quickstart; `CONTRIBUTING.md` with hard rules; `docs/nist-mapping.md` skeleton; this file marked complete. | | **0.9** | Docs + acceptance | ✅ **Complete 2026-10-01.** README rewritten (Docker + native quickstart, try-it flow, gates, add-an-entity steps); CONTRIBUTING gains rules 9–13; `docs/nist-mapping.md` with file:line evidence; control mapping v0.3 (12 ✅, 12 🟡, 4 progress notes). Closing §6 gaps added: failed-login audit (AU-2), opaque 500 handler + hidden SQL params (SI-11), startup retention check (§7 risk), migration hash pinning (CM-3). Definition of Done in §8: 12 of 14 checked; remaining two need Docker and a Gitea runner. 167 tests. |
Each milestone independently mergeable. Each milestone independently mergeable.
@@ -255,20 +255,20 @@ Everything else in the control mapping stays 📝 until later phases wire it.
Phase 0 is complete when: Phase 0 is complete when:
- [ ] `docker compose up` boots app + Postgres cleanly; `/healthz` returns 200. - [ ] `docker compose up` boots app + Postgres cleanly; `/healthz` returns 200. — **Open:** no Docker on the verifying workstation. Native boot (uvicorn + SQLite) verified; `/healthz` and `/readyz` 200.
- [ ] `alembic upgrade head` applies the baseline migration. - [x] `alembic upgrade head` applies the baseline migration. — All four revisions, SQLite; also exercised by the drift test on every CI run. Postgres run rides with the Docker item.
- [ ] `POST /auth/login` issues a JWT with `iam.assurance_level` claim. - [x] `POST /auth/login` issues a JWT with `iam.assurance_level` claim. (verified 2026-10-01: clean clone, live uvicorn, HTTP)
- [ ] `POST /registrants` creates a Registrant with masked SSN in the response, emits an audit row, requires `OYP_STAFF` or higher. - [x] `POST /registrants` creates a Registrant with masked SSN in the response, emits an audit row, requires `OYP_STAFF` or higher. (verified 2026-10-01: clean clone, live uvicorn, HTTP)
- [ ] `GET /registrants/{id}` reads the record, fires the PII-read audit decorator, masks SSN, requires appropriate role. - [x] `GET /registrants/{id}` reads the record, fires the PII-read audit decorator, masks SSN, requires appropriate role. (verified 2026-10-01: clean clone, live uvicorn, HTTP)
- [ ] `PATCH /registrants/{id}` updates fields, emits before/after in audit row, requires appropriate role. - [x] `PATCH /registrants/{id}` updates fields, emits before/after in audit row, requires appropriate role. (verified 2026-10-01: clean clone, live uvicorn, HTTP)
- [ ] `DELETE /registrants/{id}` soft-deletes, emits audit, requires appropriate role. - [x] `DELETE /registrants/{id}` soft-deletes, emits audit, requires appropriate role. (verified 2026-10-01: clean clone, live uvicorn, HTTP) Only the super-role holds `Delete` on Registrant today (matrix review item).
- [ ] Every stub entity's endpoints return 501 but still emit an audit row and enforce RBAC. - [x] Every stub entity's endpoints return 501 but still emit an audit row and enforce RBAC. — `tests/test_stubs.py` sweeps 26 entities × 8 roles × 8 routes; also seen live.
- [ ] Tests green in CI: lint, types, pytest, SAST, SCA, secret scan, SBOM. - [ ] Tests green in CI: lint, types, pytest, SAST, SCA, secret scan, SBOM. — **Open:** every gate passes locally; Gitea runs queue until an `act_runner` is registered.
- [ ] Coverage ≥ 80% on `framework/` and `security/`; every stub has one test. - [x] Coverage ≥ 80% on `framework/` and `security/`; every stub has one test. — ~96% overall; `framework/router.py` still excluded (first real caller arrives with a domain entity).
- [ ] `README.md` quickstart works on a clean checkout. - [x] `README.md` quickstart works on a clean checkout. — Native path verified from a fresh clone; Docker path rides with the Docker item.
- [ ] `CONTRIBUTING.md` published with hard rules mirrored from OWC. - [x] `CONTRIBUTING.md` published with hard rules mirrored from OWC (plus YPMS rules 9–13).
- [ ] `docs/nist-mapping.md` lists the controls wired above, with file:line evidence. - [x] `docs/nist-mapping.md` lists the controls wired above, with file:line evidence.
- [ ] Control mapping `NIST_800_53_CONTROL_MAPPING.md` updated: the Phase-0-wired controls flip from 📝 to ✅ (or 🟡 for partials); file:line evidence populated. - [x] Control mapping `NIST_800_53_CONTROL_MAPPING.md` updated: the Phase-0-wired controls flip from 📝 to ✅ (or 🟡 for partials); file:line evidence populated. — v0.3: 12 ✅, 12 🟡; SC-8 stays ⚠️ (TLS is deployment-phase).
--- ---
+30 -17
View File
@@ -1,7 +1,7 @@
# Phase 0 — Handoff status # Phase 0 — Handoff status
**Last updated:** 2026-10-01 **Last updated:** 2026-10-01
**Phase:** 0 — Foundations (in flight) **Phase:** 0 — Foundations (built; exit pending two infra checks)
**Owner:** Chris Tonjes, DOES CIO (System Owner / ISSO / AO) **Owner:** Chris Tonjes, DOES CIO (System Owner / ISSO / AO)
**Companion docs:** [`PHASE0_PLAN.md`](PHASE0_PLAN.md) (scope + milestones), [`ARCHITECTURE.md`](ARCHITECTURE.md) (design), [`README.md`](README.md) (runnable quickstart) **Companion docs:** [`PHASE0_PLAN.md`](PHASE0_PLAN.md) (scope + milestones), [`ARCHITECTURE.md`](ARCHITECTURE.md) (design), [`README.md`](README.md) (runnable quickstart)
@@ -9,7 +9,7 @@
## TL;DR ## TL;DR
Phase 0 milestones **0.1 → 0.7** are shipped; **0.8 (CI)** is complete except for its green-on-`main` exit check, which needs a Gitea Actions runner registered on `gitea.doesworks.net`. **0.9 (docs + acceptance)** is next. All ATO artifacts (SSP, control mapping, POA&M, PIA) are drafted at v0.1–v0.3 and awaiting review signatures. The repo builds, tests are green (**158 passing, 95.83% coverage**), and Docker Compose boots. `main` is pushed to Gitea. All nine Phase 0 milestones are built. **Phase 0 exits on two infrastructure checks**, neither of which can run on the dev workstation: (1) the first green Gitea Actions run on `main` — needs an `act_runner` registered on `gitea.doesworks.net`; (2) `docker compose up` against real Postgres. Definition of Done (PHASE0_PLAN §8) is 12 of 14 checked; everything else was verified end to end over HTTP from a clean clone. Tests green (**167 passing, ~96% coverage**); every CI gate passes locally. Control mapping is at v0.3 with file:line evidence (12 ✅ / 12 🟡). `main` is pushed to Gitea.
--- ---
@@ -17,15 +17,15 @@ Phase 0 milestones **0.1 → 0.7** are shipped; **0.8 (CI)** is complete except
| Dimension | Status | | Dimension | Status |
|---|---| |---|---|
| Phase | 0 (Foundations), milestones 0.1–0.7 shipped; 0.8 pending runner | | Phase | 0 (Foundations), milestones 0.1–0.9 built; exit pending CI runner + Docker/Postgres boot |
| Git branch | `main`, tracking `origin/main` (Gitea `https://gitea.doesworks.net/chris.tonjes/YPMS.git`) | | Git branch | `main`, tracking `origin/main` (Gitea `https://gitea.doesworks.net/chris.tonjes/YPMS.git`) |
| Tests | **158 passing**, **95.83% coverage** (floor 80% enforced in CI) | | Tests | **167 passing**, **~96% coverage** (floor 80% enforced in CI) |
| CI | `.gitea/workflows/ci.yml` — all gates pass locally; runs queue as "Waiting" until a runner is registered | | CI | `.gitea/workflows/ci.yml` — all gates pass locally; runs queue as "Waiting" until a runner is registered |
| Lint / types | `ruff` clean, `mypy` clean (43 files, strict) | | Lint / types | `ruff` clean, `mypy` clean (43 files, strict) |
| Runtime | FastAPI + Postgres on Docker Compose; boots to `/healthz` + `/readyz`; `/auth/*` and `/registrants/*` live; 26 stub entities return 501 behind RBAC + audit | | Runtime | Verified booting natively (uvicorn + SQLite) from a clean clone; Docker Compose + Postgres not yet run; `/auth/*` and `/registrants/*` live; 26 stub entities return 501 behind RBAC + audit |
| Migrations | 4 revisions (`0001`–`0004_stub_entity_tables`); all apply cleanly to SQLite in the drift test; live Postgres run pending (no Docker on the dev workstation) | | Migrations | 4 revisions (`0001`–`0004_stub_entity_tables`); all apply cleanly to SQLite in the drift test; live Postgres run pending (no Docker on the dev workstation) |
| Seed | 8 roles + permissions matrix + bootstrap admin (`chris` / `DOES_SYSTEM_ADMIN`) | | Seed | 8 roles + permissions matrix + bootstrap admin (`chris` / `DOES_SYSTEM_ADMIN`) |
| ATO docs | SSP v0.3, control mapping v0.2, POA&M v0.1, PIA v0.1 — all drafts, awaiting signatures | | ATO docs | SSP v0.3, control mapping **v0.3 (Phase 0 evidence)**, POA&M v0.1, PIA v0.1 — all drafts, awaiting signatures |
--- ---
@@ -115,15 +115,29 @@ Guardrails baked in: `OYP_STAFF` can't see FERPA/CFSA (that's `ELIGIBILITY_EXAMI
- Every step passes locally. Gitea has queued runs #1–#5 (and this push) as **Waiting** — no runner is online. - Every step passes locally. Gitea has queued runs #1–#5 (and this push) as **Waiting** — no runner is online.
- **To close 0.8:** register an `act_runner` against `gitea.doesworks.net` with an `ubuntu-latest` label mapped to an act-compatible image (e.g. `catthehacker/ubuntu:act-22.04`), outbound to github.com + PyPI. Then confirm the newest run on `main` is green. - **To close 0.8:** register an `act_runner` against `gitea.doesworks.net` with an `ubuntu-latest` label mapped to an act-compatible image (e.g. `catthehacker/ubuntu:act-22.04`), outbound to github.com + PyPI. Then confirm the newest run on `main` is green.
### 0.9 — Docs + acceptance ✅
- **README** rewritten: Docker and native (no-Docker) quickstarts, a try-it curl flow, all gate commands, and step-by-step "promote a stub to a real entity".
- **CONTRIBUTING** rules 9–13: PII reads emit `pii_read`; encrypted columns go in `__audit_redact__`; every table has a retention policy; model modules registered in `models.py`; vendored-code changes need a reason + upstream list entry. PR checklist and suppression rule updated.
- **`docs/nist-mapping.md`** — per-control code evidence with linked file:line and the remaining gap. **Control mapping v0.3** carries the same notes per row plus a status-shift table: 12 ✅, 12 🟡; AU-11, CM-3, SI-12 keep 🤝 and SC-8 keeps ⚠️, each with a progress note.
- **Closed gaps found while preparing evidence** (each promised by PHASE0_PLAN §6/§7 but not yet built):
- **Failed logins weren't audited** (AU-2, C.5.7.39). Now `login_failed` with reason `bad_credentials` / `unknown_user` / `inactive` / `throttled`. The typed username is stored only when it matches a real account.
- **SI-11:** app-wide handler returns `{"error":"internal_error","request_id":…}`; logs type + stack frames, never the message (driver errors echo PII); engine `hide_parameters=True`.
- **Startup retention check:** `create_app` refuses to boot if any table lacks a policy.
- **CM-3:** `migrations/IMMUTABLE.sha256` pins merged revisions; test fails on edits.
- Alembic `fileConfig(disable_existing_loggers=False)` — in-process migrations were silencing app loggers in tests.
- **End-to-end check** from a clean clone: migrate → seed → uvicorn → login, Registrant create/read/update/delete, stub 501, audit trail, ciphertext-only at rest, no SSN in the server log.
--- ---
## What's next ## What's next
### Immediate next milestone: 0.9 — Docs + acceptance ### Phase 0 exit — two infrastructure checks
`README.md` with runnable quickstart (expand the existing one), `CONTRIBUTING.md` hard rules (add the new ones: every table needs a retention policy; new model modules go in `models.py`; suppressions are per-finding), `docs/nist-mapping.md` skeleton, and flip `NIST_800_53_CONTROL_MAPPING.md` rows for the Phase-0-wired controls from 📝 to ✅/🟡 with file evidence (PHASE0_PLAN §7 acceptance). Phase 0 is done when 0.8's runner is green and 0.9's checklist is signed. 1. **CI green on `main`.** Register an `act_runner` on `gitea.doesworks.net` (label `ubuntu-latest` → an act-compatible image such as `catthehacker/ubuntu:act-22.04`; outbound to github.com and PyPI). The queued runs will start; confirm the newest on `main` passes. Closes 0.8 and the CI item in PLAN §8.
2. **Docker + Postgres boot.** On a Docker-capable machine: `cp .env.example .env && docker compose up --build`, then the README "Try it" flow. Closes the last PLAN §8 item and gives the first live Postgres run of migrations 0001–0004. If Postgres disagrees with SQLite anywhere, fix in a new migration.
Then Phase 1 (applications + identity + parental consent) starts. Then: sign off Phase 0, and draft `PHASE1_PLAN.md` (applications + identity + parental consent — ARCH §12). Decisions that should be made before Phase 1 starts: the four permission-matrix review items below, the 0002 constraint follow-up, and `YPMS-POAM-050` (youth identity model), which Phase 1 is built around.
### Upstream to OWC (accumulated) ### Upstream to OWC (accumulated)
@@ -336,9 +350,9 @@ Then the ATO artifacts as needed:
--- ---
## POA&M state (as of 2026-09-30) ## POA&M state (as of 2026-10-01)
- **62 items total** — 2 fully closed, 4 partial closures under `YPMS-POAM-040` - **62 items total** — 2 fully closed; 9 Phase 0 milestone partial closures logged under `YPMS-POAM-040`; `YPMS-POAM-042` (CI/SBOM) in progress pending the runner
- **6 open decisions** (see Open Questions table above) - **6 open decisions** (see Open Questions table above)
- **Immediate remaining action items:** - **Immediate remaining action items:**
- PIA route to Privacy Officer + OGC review - PIA route to Privacy Officer + OGC review
@@ -348,14 +362,13 @@ Then the ATO artifacts as needed:
--- ---
## Signals to watch for as we approach milestone 0.9 ## Signals to watch for at Phase 0 exit / Phase 1 start
- Control-mapping status flips need file:line evidence, not "implemented in Phase 0". Auditors trace the path.
- Don't weaken a gate to get green (no blanket bandit skips, no `--cov-fail-under` drop). Suppressions are per-finding with a reason — e.g. the inline `gitleaks:allow` on the test JWT secret in `tests/conftest.py`.
- If the first real CI run fails on something local runs didn't catch (runner image, network), fix the workflow, not the gate. - If the first real CI run fails on something local runs didn't catch (runner image, network), fix the workflow, not the gate.
- Phase 1 promotes Guardian and ConsentRecord from stubs: follow README "Adding an entity", and give both the Registrant treatment (`pii_read`, `__audit_redact__` for anything encrypted, coverage doc).
- PARENT_USER access to a child's Registrant hangs on the Guardian link — replace the Phase 0 "parents see nothing" rule in `youth/router.py`, don't layer around it.
- Keep `docs/nist-mapping.md` and the control-mapping rows in step; line numbers drift, so refresh them at each phase gate.
- Retention execution (actual disposal) is Phase 5 work, alongside the OSSE/CFSA ingest pipelines. Don't add it in passing. - Retention execution (actual disposal) is Phase 5 work, alongside the OSSE/CFSA ingest pipelines. Don't add it in passing.
- Any entity that will hold PII in Phase 1+ (Guardian, TaxDocument, Attachment) should get the Registrant treatment then — `pii_read` on reads, `__audit_redact__` on encrypted columns.
--- ---
**End of handoff v0.5.** **End of handoff v0.6.**
+1
View File
@@ -180,6 +180,7 @@ Closures retained for traceability.
| — | Phase 0 milestone 0.6 shipped (partial closure of YPMS-POAM-040) | 2026-09-30 | 501-stub routers for 26 entities; every route enforces RBAC and writes an audit row before returning 501 (source RFP C.5.7.27; NIST AC-3, AU-12). Matrix-driven test sweep covers every entity × role × action. Permission-matrix review items recorded in PHASE0_STATUS.md. 138 tests passing, 95.05% coverage. | | — | Phase 0 milestone 0.6 shipped (partial closure of YPMS-POAM-040) | 2026-09-30 | 501-stub routers for 26 entities; every route enforces RBAC and writes an audit row before returning 501 (source RFP C.5.7.27; NIST AC-3, AU-12). Matrix-driven test sweep covers every entity × role × action. Permission-matrix review items recorded in PHASE0_STATUS.md. 138 tests passing, 95.05% coverage. |
| — | Phase 0 milestone 0.7 shipped (partial closure of YPMS-POAM-040) | 2026-10-01 | Retention scaffold (NIST AU-11, SI-12; source RFP C.5.7.42): every table carries a policy; PIA §9 interim periods encoded but unapproved; report-only nightly job logs eligible counts and audits each run. Implementation is ready to consume the signed schedule from YPMS-POAM-054 (flip `approved`, cite authority). 156 tests passing, 95.91% coverage. | | — | Phase 0 milestone 0.7 shipped (partial closure of YPMS-POAM-040) | 2026-10-01 | Retention scaffold (NIST AU-11, SI-12; source RFP C.5.7.42): every table carries a policy; PIA §9 interim periods encoded but unapproved; report-only nightly job logs eligible counts and audits each run. Implementation is ready to consume the signed schedule from YPMS-POAM-054 (flip `approved`, cite authority). 156 tests passing, 95.91% coverage. |
| — | Phase 0 milestone 0.8 pipeline complete (partial closure of YPMS-POAM-040; advances YPMS-POAM-042) | 2026-10-01 | Gitea Actions workflow with lint, strict types, tests + coverage floor, migration drift test, SAST, SCA on runtime deps, full-history secret scan, CycloneDX SBOM. All gates pass locally; green-on-main pending runner registration. Removed `ecdsa` (PYSEC-2026-1325) by moving JWT to PyJWT; enforced 32-byte JWT secret (RFC 7518). 158 tests passing, 95.83% coverage. | | — | Phase 0 milestone 0.8 pipeline complete (partial closure of YPMS-POAM-040; advances YPMS-POAM-042) | 2026-10-01 | Gitea Actions workflow with lint, strict types, tests + coverage floor, migration drift test, SAST, SCA on runtime deps, full-history secret scan, CycloneDX SBOM. All gates pass locally; green-on-main pending runner registration. Removed `ecdsa` (PYSEC-2026-1325) by moving JWT to PyJWT; enforced 32-byte JWT secret (RFC 7518). 158 tests passing, 95.83% coverage. |
| — | Phase 0 milestone 0.9 shipped (partial closure of YPMS-POAM-040) | 2026-10-01 | Docs + acceptance: README, CONTRIBUTING rules 9–13, `docs/nist-mapping.md`, control mapping v0.3 with file:line evidence (12 ✅ / 12 🟡). Closed Phase 0 control gaps: failed-login audit (AU-2, C.5.7.39), opaque error handler + hidden SQL parameters (SI-11, C.5.7.43), startup retention-policy check, migration hash pinning (CM-3). End-to-end HTTP verification from a clean clone. Phase 0 exit remaining: Docker/Postgres boot check and first green Gitea CI run. 167 tests passing. |
--- ---
+81 -42
View File
@@ -2,11 +2,11 @@
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. 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:** Pre-development. Phase 0 (foundations) in flight. **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. **System owner + ISSO + AO:** Chris Tonjes, DOES CIO.
**Full context:** [`OVERVIEW.md`](OVERVIEW.md), [`ARCHITECTURE.md`](ARCHITECTURE.md), [`PHASE0_PLAN.md`](PHASE0_PLAN.md). **Full context:** [`OVERVIEW.md`](OVERVIEW.md), [`ARCHITECTURE.md`](ARCHITECTURE.md), [`PHASE0_PLAN.md`](PHASE0_PLAN.md).
## Quickstart (local) ## Quickstart — Docker (canonical)
Requires Docker Desktop (or equivalent). Ports 8000 (app), 5433 (Postgres), 8081 (Adminer) must be free. Requires Docker Desktop (or equivalent). Ports 8000 (app), 5433 (Postgres), 8081 (Adminer) must be free.
@@ -15,68 +15,107 @@ cp .env.example .env
docker compose up --build docker compose up --build
``` ```
App at http://localhost:8000. OpenAPI docs at http://localhost:8000/docs. 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 ```bash
curl http://localhost:8000/healthz # → {"status":"ok"} python3.12 -m venv .venv && source .venv/bin/activate
curl http://localhost:8000/readyz # → {"status":"ok"} once db is up
```
Adminer (DB explorer) at http://localhost:8081. Connect with:
- System: PostgreSQL
- Server: `db`
- Username / Password / Database: `ypms` / `ypms` / `ypms`
Stop with `Ctrl+C`; wipe local DB with `docker compose down -v`.
## Tests
Local (no Docker required for the tests):
```bash
python -m venv .venv && source .venv/bin/activate
pip install -e '.[dev]' pip install -e '.[dev]'
pytest
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
``` ```
Coverage: ## Try it
```bash ```bash
pytest --cov=src/ypms --cov-report=term-missing 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)
``` ```
Tests use SQLite in-memory for speed. Postgres-specific behaviour gets its own Postgres-only fixture when a migration or query needs it (see [`ARCHITECTURE.md`](ARCHITECTURE.md) §4.8). 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 ## Repo shape
Full tree in [`ARCHITECTURE.md`](ARCHITECTURE.md) §5.1. High points: Full tree in [`ARCHITECTURE.md`](ARCHITECTURE.md) §5.1. High points:
- `src/ypms/main.py` — FastAPI factory - `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 - `src/ypms/settings.py` — pydantic-settings config (secret-length floors enforced)
- `src/ypms/db.py` — SQLAlchemy engine, session, `Base` - `src/ypms/framework/` — vendored from OWC, plus YPMS additions: `pii_audit.py`, `stub_router.py`, `retention.py`
- `src/ypms/logging.py` — structlog + sensitive-field sanitizer - `src/ypms/security/` — RBAC, `column_crypto.py` (AES-256-GCM app-layer encryption)
- `src/ypms/middleware.py` — request id - `src/ypms/auth/` — login, JWT (PyJWT), refresh, change-password, login rate limit
- `src/ypms/framework/` — vendored from OWC, milestone 0.2 - `src/ypms/youth/` — Registrant, the real vertical-slice entity
- `src/ypms/security/` — auth + RBAC + column encryption, milestones 0.3–0.4 - `src/ypms/stubs.py`, `src/ypms/stub_models.py` — the 26 entities still at 501
- `src/ypms/youth/` — Registrant entity (vertical slice), milestone 0.5 - `src/ypms/retention_classes.py` — PIA §9 retention policy catalog
- Other entity packages — 501 stubs, milestone 0.6 - `src/ypms/models.py` — the one list of model modules
- `migrations/` — Alembic, immutable-once-merged - `src/ypms/seed/` — roles, permissions matrix, bootstrap admin
- `tests/` — pytest, SQLite by default - `migrations/` — Alembic; merged revisions pinned in `IMMUTABLE.sha256`
- `docs/coverage/` — per-entity requirement traces; `docs/nist-mapping.md` — control evidence index
## Adding an entity ## Adding an entity (promoting a stub to real)
Once the framework lands in milestone 0.2, adding a new entity follows the OWC add-entity pattern (model → schemas → router → main.py registration → migration → permissions seed entry → test → coverage doc). Full instructions land here at milestone 0.2 completion. 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 ## Contributing
See [`CONTRIBUTING.md`](CONTRIBUTING.md) for the hard rules. Highlights: migrations are immutable once merged, every state-mutating route emits an audit row, framework/security modules must not carry domain logic, no `--no-verify` or `# type: ignore` without a written reason. See [`CONTRIBUTING.md`](CONTRIBUTING.md) for the hard rules.
## Compliance ## Compliance
- [`SYSTEM_SECURITY_PLAN.md`](SYSTEM_SECURITY_PLAN.md) v0.3 — NIST 800-53 Moderate SSP - [`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.2 — full control-family map - [`NIST_800_53_CONTROL_MAPPING.md`](NIST_800_53_CONTROL_MAPPING.md) v0.3 — full control map with Phase 0 evidence
- [`PRIVACY_IMPACT_ASSESSMENT.md`](PRIVACY_IMPACT_ASSESSMENT.md) v0.1 — PIA covering minors, SSN, banking, FERPA, CFSA - [`docs/nist-mapping.md`](docs/nist-mapping.md) — Phase 0 control evidence index (file:line)
- [`POAM.md`](POAM.md) v0.1 — POA&M with 62 items and 2 closures as of 2026-09-30 - [`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 ## License / usage
+50
View File
@@ -0,0 +1,50 @@
# NIST 800-53 — Phase 0 code evidence
**As of:** 2026-10-01 · **Scope:** controls that Phase 0 code implements or partially implements (PHASE0_PLAN.md §6) · **Authoritative mapping:** [`NIST_800_53_CONTROL_MAPPING.md`](../NIST_800_53_CONTROL_MAPPING.md) v0.3
This is the developer-facing index: for each control, where the code is and what's still missing. The control mapping carries the same notes in each row for assessors. When a change wires or advances a control, update both (CONTRIBUTING PR checklist).
Line numbers are as of the commit that last touched this file. If they drift, the function or name in the summary is the stable anchor.
| Control | Status | What the code does | Evidence | Remaining gap |
|---|---|---|---|---|
| **AC-2** | 🟡 | User model with active flag; inactive accounts rejected at authentication. | [`src/ypms/users/models.py:48`](../src/ypms/users/models.py#L48)<br>[`src/ypms/auth/deps.py:43`](../src/ypms/auth/deps.py#L43) | No admin enable/disable endpoint (admin module, Phase 8); inactivity auto-disable is YPMS-POAM-071. |
| **AC-3** | ✅ | `require_permission` runs on every protected route, including all 208 stub routes; portal-only callers restricted to their own Registrant row. | [`src/ypms/security/rbac.py:46`](../src/ypms/security/rbac.py#L46)<br>[`src/ypms/youth/router.py:67`](../src/ypms/youth/router.py#L67)<br>[`tests/test_stubs.py`](../tests/test_stubs.py) | Row-level scoping beyond Registrant is Phase 2+. |
| **AC-6** | ✅ | Data-driven RBAC (8 roles × 30 entities × 8 actions); FERPA/CFSA entities limited to ELIGIBILITY_EXAMINER; super-role explicit. | [`src/ypms/seed/permissions_matrix.json`](../src/ypms/seed/permissions_matrix.json)<br>[`src/ypms/security/rbac.py:22`](../src/ypms/security/rbac.py#L22)<br>[`tests/test_stubs.py`](../tests/test_stubs.py) | Four matrix review items open (PHASE0_STATUS.md). |
| **AC-6(1)** | ✅ | Only DOES_SYSTEM_ADMIN holds RBAC-table rights (User/Role not granted to any other role). | [`src/ypms/seed/permissions_matrix.json`](../src/ypms/seed/permissions_matrix.json) | — |
| **AC-6(10)** | ✅ | Delete and Restore (super-role only today) and Registrant VersionHistory (OYP_STAFF and ELIGIBILITY_EXAMINER only) enforced server-side; 403 asserted for every other role. | [`src/ypms/security/rbac.py:29`](../src/ypms/security/rbac.py#L29)<br>[`tests/test_stubs.py`](../tests/test_stubs.py)<br>[`tests/youth/test_registrant_api.py`](../tests/youth/test_registrant_api.py) | — |
| **AC-7** | 🟡 | 5 failed logins per source IP per 15 minutes → 429; every failure (bad credentials, unknown user, inactive, throttled) audited as `login_failed`. | [`src/ypms/auth/rate_limit.py:47`](../src/ypms/auth/rate_limit.py#L47)<br>[`src/ypms/auth/router.py:86`](../src/ypms/auth/router.py#L86)<br>[`src/ypms/auth/router.py:65`](../src/ypms/auth/router.py#L65) | Per-account lockout (`locked_until`) and user notification not built; in-memory limiter is per-process. |
| **AC-12** | ✅ | Access tokens expire (default 60 min, `exp` claim verified); refresh tokens cannot access resources. | [`src/ypms/auth/security.py:59`](../src/ypms/auth/security.py#L59)<br>[`src/ypms/auth/deps.py:35`](../src/ypms/auth/deps.py#L35) | Idle-timeout UI behaviour lands with the portal (Phase 3). |
| **AU-2** | 🟡 | Audited today: login success/failure, password change, every create/update/delete/restore, every PII read, every stub invocation, each retention run. | [`src/ypms/audit/service.py:17`](../src/ypms/audit/service.py#L17)<br>[`src/ypms/framework/crud.py:111`](../src/ypms/framework/crud.py#L111)<br>[`src/ypms/auth/router.py:65`](../src/ypms/auth/router.py#L65)<br>[`src/ypms/auth/router.py:104`](../src/ypms/auth/router.py#L104) | Event catalogue items not yet applicable (role changes, shutdown, file access) arrive with their features. |
| **AU-3** | ✅ | Each row: actor id + username, action, entity, entity id, before/after, timestamp, IP; app-layer ciphertext redacted from before/after. | [`src/ypms/audit/models.py:18`](../src/ypms/audit/models.py#L18)<br>[`src/ypms/framework/crud.py:46`](../src/ypms/framework/crud.py#L46) | Request id is in logs, not yet on the audit row. |
| **AU-8** | ✅ | All timestamps timezone-aware UTC; Postgres container pinned to UTC. | [`src/ypms/audit/models.py:24`](../src/ypms/audit/models.py#L24)<br>[`docker-compose.yml:8`](../docker-compose.yml#L8) | NTP is inherited from Azure at deployment. |
| **AU-11** | 🤝 | Retention scaffold: audit log carries the PIA §9 interim policy (archive at 1 year); report-only job. | [`src/ypms/retention_classes.py`](../src/ypms/retention_classes.py)<br>[`src/ypms/framework/retention.py:150`](../src/ypms/framework/retention.py#L150) | Period unapproved until YPMS-POAM-054; archive tier is deployment work. |
| **AU-12** | ✅ | Audit generation is in the framework (CRUDService, stub router, auth), not per-handler. | [`src/ypms/framework/crud.py:111`](../src/ypms/framework/crud.py#L111)<br>[`src/ypms/framework/stub_router.py:66`](../src/ypms/framework/stub_router.py#L66) | — |
| **AU-14** | ✅ | `pii_read` audit on Registrant GET, list (ids recorded), and history. | [`src/ypms/framework/pii_audit.py:36`](../src/ypms/framework/pii_audit.py#L36)<br>[`src/ypms/youth/router.py:99`](../src/ypms/youth/router.py#L99)<br>[`src/ypms/youth/router.py:115`](../src/ypms/youth/router.py#L115) | Extends to Guardian/TaxDocument/Attachment as they are built. |
| **CM-3** | 🤝 | Merged migrations pinned by SHA-256; CI fails on any edit. Every change ships through a reviewed commit. | [`migrations/IMMUTABLE.sha256`](../migrations/IMMUTABLE.sha256)<br>[`tests/test_migration_immutability.py:31`](../tests/test_migration_immutability.py#L31) | Maintenance-window process is organizational. |
| **CM-8** | 🟡 | CycloneDX 1.6 SBOM of the runtime dependency set generated in CI. | [`.gitea/workflows/ci.yml:70`](../.gitea/workflows/ci.yml#L70) | Pending first green CI run (Gitea runner); per-release retention of SBOMs not yet defined. |
| **IA-2** | ✅ | Every non-public route requires a valid bearer token bound to a unique, active user. | [`src/ypms/auth/deps.py:25`](../src/ypms/auth/deps.py#L25) | MFA enhancements (IA-2(1)/(2)) arrive with Entra ID in Phase 1. |
| **IA-4** | 🟡 | Usernames unique (DB constraint); no user-delete path exists, so identifiers are never re-issued. | [`migrations/versions/0002_framework_tables.py:54`](../migrations/versions/0002_framework_tables.py#L54) | 12-character minimum identifier and explicit tombstoning arrive with registration (Phase 1). |
| **IA-5** | 🟡 | Users change their own password (12-character floor, current password required); hashes only. | [`src/ypms/auth/router.py:138`](../src/ypms/auth/router.py#L138)<br>[`src/ypms/auth/router.py:147`](../src/ypms/auth/router.py#L147) | Password-change notification (C.5.7.14) and admin reset arrive with notifications/admin. |
| **IA-5(1)** | 🟡 | PBKDF2-SHA256 (NIST SP 800-132) via passlib. | [`src/ypms/auth/security.py:30`](../src/ypms/auth/security.py#L30) | FIPS 140-3 validated module is deployment-dependent (YPMS-POAM-032). |
| **RA-5** | 🟡 | pip-audit on runtime dependencies gates CI; an unfixable `ecdsa` advisory was removed by changing JWT library. | [`.gitea/workflows/ci.yml:59`](../.gitea/workflows/ci.yml#L59) | Pending first green CI run; infrastructure scanning is deployment work. |
| **SA-11** | 🟡 | 167 automated tests, 80% coverage floor, ruff, strict mypy, bandit SAST, migration-drift test. | [`.gitea/workflows/ci.yml:53`](../.gitea/workflows/ci.yml#L53)<br>[`.gitea/workflows/ci.yml:56`](../.gitea/workflows/ci.yml#L56) | Pending first green CI run. |
| **SC-8** | ⚠️ | Not wired in Phase 0 (no local TLS proxy). | — | TLS terminates at Front Door / App Gateway at deployment (YPMS-POAM-031). |
| **SC-12** | 🟡 | Column key derived with HKDF-SHA256; ≥32-byte key material and ≥32-byte JWT secret enforced at startup. | [`src/ypms/security/column_crypto.py:49`](../src/ypms/security/column_crypto.py#L49)<br>[`src/ypms/settings.py:15`](../src/ypms/settings.py#L15) | Keys come from env vars; Key Vault (Gov/HSM) in Phase 1. |
| **SC-13** | 🟡 | FIPS-approved algorithms only: AES-256-GCM, HKDF-SHA256, HMAC-SHA256 (JWT), PBKDF2-SHA256. | [`src/ypms/security/column_crypto.py:55`](../src/ypms/security/column_crypto.py#L55) | Validated-module status is deployment-dependent (YPMS-POAM-032). |
| **SC-28** | 🟡 | SSN encrypted at the app layer (AES-256-GCM, column-bound AAD); write-only on the model; masked in every response. | [`src/ypms/security/column_crypto.py:55`](../src/ypms/security/column_crypto.py#L55)<br>[`src/ypms/youth/models.py:83`](../src/ypms/youth/models.py#L83) | Banking columns not yet built; Postgres TDE and blob encryption are deployment. |
| **SI-10** | ✅ | Pydantic validation on every request body; SSN format and never-issued ranges, DOB, ZIP, email, lengths. | [`src/ypms/youth/schemas.py:21`](../src/ypms/youth/schemas.py#L21) | — |
| **SI-11** | ✅ | Unhandled errors return a generic body with request id; logs record type and frames, not messages; SQL parameters hidden. | [`src/ypms/main.py:56`](../src/ypms/main.py#L56)<br>[`src/ypms/db.py:24`](../src/ypms/db.py#L24)<br>[`tests/test_app_guards.py`](../tests/test_app_guards.py) | — |
| **SI-12** | 🤝 | Every table carries a retention policy; app refuses to boot otherwise; nightly report-only job. | [`src/ypms/framework/retention.py:135`](../src/ypms/framework/retention.py#L135)<br>[`src/ypms/main.py:34`](../src/ypms/main.py#L34) | Disposal execution is Phase 5; periods await YPMS-POAM-054. |
## Verified end to end (2026-10-01)
Clean clone → fresh venv → `alembic upgrade head` → `python -m ypms.seed.run` → `uvicorn`, then over HTTP:
- `POST /auth/login` → JWT with `iam.assurance_level`; no token → 401.
- `POST /registrants` → 201, `ssn_masked: XXX-XX-6789`, no `ssn` field; `GET` → `pii_read` audit row; `PATCH` → version 2; `DELETE` → `deleted_at` set.
- `GET /guardians/5` → 501 with a `View` audit row.
- `audit_log`: `login`, `create`, `pii_read`, `update`, `delete`, `View` — every `ssn_encrypted` value `[REDACTED]`.
- `registrants.ssn_encrypted` holds `v1.` ciphertext only; the plaintext SSN appears nowhere in the DB or server log.
SQLite stood in for Postgres (no Docker on the verifying workstation). The Postgres run is still open.