Mark milestone 0.6 complete in PHASE0_PLAN, POA&M, and status handoff
ci / test (push) Has been cancelled
ci / test (push) Has been cancelled
Status handoff also records four permission-matrix review items the stub sweep surfaced (no Restore holders; admin-only Program / EligibilityRule config; admin-only IntegrationEvent; HOST_USER Delete pending row-level ownership). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
98b783b6b5
commit
7d11f2404f
3 files changed
+39
-14
No files matched your search
+1
-1
@@ -76,7 +76,7 @@ Every Phase 0 component maps to a source-RFP requirement or a NIST control. Tabl
|
||||
| **0.3** | Auth + JWT + assurance_level | ✅ **Complete 2026-09-30.** `POST /auth/login` (OAuth2 form) returns access + refresh JWTs; access token carries `sub`, `uid`, `roles`, `iam.assurance_level`, `mfa_verified`, `iat`, `exp`, `type`. `get_current_user` verifies and rejects refresh tokens. `/auth/refresh`, `/auth/me`, `/auth/change-password` (≥12 char floor) also shipped. Login lockout after 5 IP-scoped failed attempts / 15 min per NIST AC-7. 43 tests, 93.47% coverage. |
|
||||
| **0.4** | RBAC seed loaded | ✅ **Complete 2026-09-30.** 8 roles seeded from `seed/roles.py`. Permissions matrix at `seed/permissions_matrix.json` — role-first grants across 30 entities × 8 actions. `python -m ypms.seed.run` wired into docker-compose. Loader validates every cell against declared actions/entities and refuses unknown roles at seed time. 53 tests, 92.20% coverage; end-to-end `can()` decisions verified per role including admin bypass. |
|
||||
| **0.5** | Registrant vertical slice | ✅ **Complete 2026-09-30.** `Registrant` model + schemas + router (`src/ypms/youth/`) + migration `0003_registrants`. SSN AES-256-GCM encrypted at the app layer (`security/column_crypto.py`), write-only on the model, serializer emits only `XXX-XX-1234`. `pii_read` audit on GET, list, and history; every state change emits an audit row with ciphertext redacted via new `__audit_redact__` framework hook. Portal-only roles restricted to own row (Phase 0 minimum; PARENT_USER blocked until Guardian). 98 tests, 94.83% coverage. Coverage doc at `docs/coverage/registrant.md`. |
|
||||
| **0.6** | 501-stub routers for remaining entities | Every entity in ARCH §6.1 (except Registrant, User, Role, Permission, AuditRow) has a router registered in `main.py` that returns 501 while still firing RBAC + audit. Test asserts RBAC decision and audit emission for each. |
|
||||
| **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 | `retention_policy` attribute on every entity model (even 501-stub entities have a model). Nightly job in `src/ypms/framework/retention.py` runs, logs "0 records to purge" — no-op but wired. |
|
||||
| **0.8** | CI pipeline | GitHub Actions workflow with lint (ruff), types (mypy/pyright — match OWC's pick), tests, bandit, pip-audit, gitleaks, SBOM. Green build on `main`. |
|
||||
| **0.9** | Docs + acceptance | `README.md` with runnable quickstart; `CONTRIBUTING.md` with hard rules; `docs/nist-mapping.md` skeleton; this file marked complete. |
|
||||
|
||||
+37
-13
@@ -9,7 +9,7 @@
|
||||
|
||||
## TL;DR
|
||||
|
||||
Phase 0 milestones **0.1 → 0.5** are shipped. **0.6 (501-stub routers for the remaining entities)** is next; **0.7–0.9** follow. 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 (**98 passing, 94.83% coverage**), and Docker Compose boots. `main` is pushed to Gitea.
|
||||
Phase 0 milestones **0.1 → 0.6** are shipped. **0.7 (retention scaffold)** is next; **0.8–0.9** follow. 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 (**138 passing, 95.05% coverage**), and Docker Compose boots. `main` is pushed to Gitea.
|
||||
|
||||
---
|
||||
|
||||
@@ -17,11 +17,11 @@ Phase 0 milestones **0.1 → 0.5** are shipped. **0.6 (501-stub routers for the
|
||||
|
||||
| Dimension | Status |
|
||||
|---|---|
|
||||
| Phase | 0 (Foundations), milestones 0.1–0.5 shipped |
|
||||
| Phase | 0 (Foundations), milestones 0.1–0.6 shipped |
|
||||
| Git branch | `main`, tracking `origin/main` (Gitea `https://gitea.doesworks.net/chris.tonjes/YPMS.git`) |
|
||||
| Tests | **98 passing**, **94.83% coverage** (floor 80% enforced in CI) |
|
||||
| Lint / types | `ruff` clean, `mypy` clean (37 files, strict) |
|
||||
| Runtime | FastAPI + Postgres on Docker Compose; boots to `/healthz` + `/readyz`; `/auth/*` and `/registrants/*` live |
|
||||
| Tests | **138 passing**, **95.05% coverage** (floor 80% enforced in CI) |
|
||||
| Lint / types | `ruff` clean, `mypy` clean (39 files, strict) |
|
||||
| Runtime | FastAPI + Postgres on Docker Compose; boots to `/healthz` + `/readyz`; `/auth/*` and `/registrants/*` live; 26 stub entities return 501 behind RBAC + audit |
|
||||
| Migrations | 3 revisions (`0001_baseline`, `0002_framework_tables`, `0003_registrants`); 0003 rendered offline, live Postgres run pending |
|
||||
| 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 |
|
||||
@@ -90,22 +90,40 @@ Guardrails baked in: `OYP_STAFF` can't see FERPA/CFSA (that's `ELIGIBILITY_EXAMI
|
||||
- **Framework changes** (flag to OWC on re-vendor): `crud._to_dict` honors `__audit_redact__` (ciphertext → `[REDACTED]` in audit + version history) and serializes `date`; `CRUDService.history` annotation uses `builtins.list` (the `list` method shadowed it for mypy).
|
||||
- Evidence trace: [`docs/coverage/registrant.md`](docs/coverage/registrant.md) — also lists what was deferred (banking columns, SSN blind index, Search route, Guardian scoping).
|
||||
|
||||
### 0.6 — 501-stub routers ✅
|
||||
|
||||
- `src/ypms/framework/stub_router.py` — `make_stub_router(entity, prefix)`; one route per matrix action (`STUB_ROUTES`), `/search` registered before `/{id}`. Each route: real `require_permission` → audit row (`action` = matrix action, `after.status = "stub_invoked"`) → 501. Never reads a request body.
|
||||
- `src/ypms/stubs.py` — `STUB_ENTITIES` registry (entity → path), included in `main.py`. To build an entity for real: remove it here, register its real router.
|
||||
- `tests/test_stubs.py` — matrix JSON is the oracle; sweeps 26 entities × 8 roles × 8 routes. Also pins EducationRecord / FosterIndicator closed to non-examiner roles.
|
||||
- Registrant restore now requires the matrix's `Restore` action (was `Edit`).
|
||||
- Test infra: `roles` / `make_user` / `api` fixtures live in `tests/conftest.py`; `api` uses one app per session.
|
||||
|
||||
---
|
||||
|
||||
## What's next
|
||||
|
||||
### Immediate next milestone: 0.6 — 501-stub routers
|
||||
### Immediate next milestone: 0.7 — Retention scaffold
|
||||
|
||||
Every other entity in ARCH §6.1 gets a model (id + mixins) and a router registered in `main.py` that returns 501 while still firing RBAC + audit. `framework/router.py`'s `_attach_stub` is the starting point. Once real routes run through `make_router`, drop it from the coverage `omit` list in `pyproject.toml`.
|
||||
`retention_policy` on every entity model, and a nightly job in `src/ypms/framework/retention.py` that runs and logs "0 records to purge". The exit criterion requires a model for every stub entity, so 0.7 also adds id + mixins models for the 26 entities in `src/ypms/stubs.py` (plus a migration). Retention periods are blocked on `YPMS-POAM-054` (records schedule); use a placeholder policy class per PIA §9 and leave the durations unset.
|
||||
|
||||
### After 0.6
|
||||
### After 0.7
|
||||
|
||||
- **0.7** — Retention scaffold (nightly job, no-op in Phase 0)
|
||||
- **0.8** — CI pipeline hardening — SAST/SCA/SBOM configured in `.github/workflows/ci.yml` already; 0.8 is the discipline of green builds and any needed suppressions.
|
||||
- **0.9** — Docs + acceptance (README expanded, `docs/nist-mapping.md` skeleton, "Phase 0 done" acceptance checklist)
|
||||
|
||||
Then Phase 1 (applications + identity + parental consent) starts.
|
||||
|
||||
### Permission-matrix review items (surfaced by 0.6)
|
||||
|
||||
The stub sweep runs every role against every action, so these are now visible. **Not changed** — they're policy calls for the Phase 0 review:
|
||||
|
||||
| # | Finding | Effect today | Likely owner |
|
||||
|---|---|---|---|
|
||||
| 1 | No role holds `Restore` on any entity | Only DOES_SYSTEM_ADMIN can undo a soft delete | AO + OYP |
|
||||
| 2 | `Program` and `EligibilityRule` Create/Edit held by no role | Program configuration is admin-only | OYP (program-config role?) |
|
||||
| 3 | `IntegrationEvent` held by no role | No integration-monitoring view outside admin | DOES OIT ops |
|
||||
| 4 | HOST_USER holds `Delete` on `Position` and `JobPosting` | Safe only with row-level ownership filtering (Phase 2+; gotcha 7) | Revisit when host module is built |
|
||||
|
||||
---
|
||||
|
||||
## Key decisions locked in
|
||||
@@ -170,7 +188,8 @@ git -C /Users/christonjes/cc/ypms config user.email <addr>
|
||||
| `seed/` (roles, permissions, run) | 77–100% |
|
||||
| `youth/` (models, router, schemas) | 99–100% |
|
||||
| `security/column_crypto.py` | 100% |
|
||||
| Overall | **94.83%** |
|
||||
| `framework/stub_router.py`, `stubs.py` | 100% |
|
||||
| Overall | **95.05%** |
|
||||
|
||||
`framework/router.py` is still excluded from coverage — Registrant uses a hand-built router, so `make_router` gets its first real callers in 0.6. Coverage config in `pyproject.toml`.
|
||||
|
||||
@@ -250,6 +269,10 @@ The GitHub Actions workflow at `.github/workflows/ci.yml` runs the same commands
|
||||
|
||||
11. **`COLUMN_ENCRYPTION_KEY` changes make existing ciphertext unreadable.** Phase 0 has no rotation; don't change the dev key on a database you want to keep. `column_crypto._aead` is `lru_cache`d — tests that change the key must `cache_clear()` it and `get_settings`.
|
||||
|
||||
12. **The `api` test fixture shares one app across the session.** Tests must not mutate app state other than `dependency_overrides[get_db]` (which the fixture resets). If a test needs custom routes or settings, build its own app with `create_app()`.
|
||||
|
||||
13. **Adding an entity to `permissions_matrix.json` fails `test_every_matrix_entity_is_real_or_stubbed`** until it's either stubbed in `stubs.py` or has a real router (then add it to `REAL_ENTITIES` in that test).
|
||||
|
||||
---
|
||||
|
||||
## File map
|
||||
@@ -286,12 +309,13 @@ Then the ATO artifacts as needed:
|
||||
|
||||
---
|
||||
|
||||
## Signals to watch for as we approach milestone 0.6
|
||||
## Signals to watch for as we approach milestone 0.7
|
||||
|
||||
- Stub models should be id + mixins only (PHASE0_PLAN §4). Resist adding real fields — that's domain-phase work with its own migration.
|
||||
- `EducationRecord` and `FosterIndicator` stubs still carry the ELIGIBILITY_EXAMINER-only grants; confirm the 501 routes return 403 (not 501) for OYP_STAFF.
|
||||
- Don't invent retention durations. `YPMS-POAM-054` owns them; the scaffold should make "no policy set" an explicit, logged state rather than a silent default.
|
||||
- FosterIndicator and EducationRecord have a distinct two-year annual purge (ARCH §6.4) — model it as its own policy class now so the job structure doesn't need reshaping later.
|
||||
- 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.2.**
|
||||
**End of handoff v0.3.**
|
||||
@@ -177,6 +177,7 @@ Closures retained for traceability.
|
||||
| — | Phase 0 milestone 0.3 shipped (partial closure of YPMS-POAM-040) | 2026-09-30 | Local password auth + JWT access/refresh tokens with YPMS-specific claims (`uid`, `roles`, `iam.assurance_level`, `mfa_verified`). Endpoints: /auth/login, /auth/refresh, /auth/me, /auth/change-password. NIST AC-7 lockout at 5 attempts / 15 min (source RFP C.5.7.10). 43 tests passing, 93.47% coverage. |
|
||||
| — | Phase 0 milestone 0.4 shipped (partial closure of YPMS-POAM-040) | 2026-09-30 | Data-driven RBAC seed: 8 roles + permissions matrix (30 entities × 8 actions) at `src/ypms/seed/`. Bootstrap admin auto-seeded (`chris` / DOES_SYSTEM_ADMIN). Loader idempotent + validates cell references. `python -m ypms.seed.run` chained into docker-compose. 53 tests passing, 92.20% coverage. |
|
||||
| — | Phase 0 milestone 0.5 shipped (partial closure of YPMS-POAM-040) | 2026-09-30 | Registrant vertical slice: model, schemas, router, migration 0003. SSN app-layer encrypted (AES-256-GCM, column-bound AAD) and masked in the serializer (C.5.7.50 / SC-28). `pii_read` audit on every read path (C.5.6.9 #5 / AU-14). Audit + version-history snapshots redact ciphertext via `__audit_redact__`. Portal-only roles limited to own row. 98 tests passing, 94.83% coverage. Evidence trace at `docs/coverage/registrant.md`. |
|
||||
| — | 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. |
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in new issue
Block a user