Mark milestone 0.5 complete in PHASE0_PLAN, POA&M, and status handoff
ci / test (push) Has been cancelled
ci / test (push) Has been cancelled
PHASE0_STATUS also refreshed: SSH remote, push state, 0.6 as next milestone, new gotchas (write-only SSN, __audit_redact__, key changes). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
5dd828f0e5
commit
6b2d0008a1
3 files changed
+41
-47
No files matched your search
+1
-1
@@ -75,7 +75,7 @@ Every Phase 0 component maps to a source-RFP requirement or a NIST control. Tabl
|
||||
| **0.2** | Framework vendor-in | ✅ **Complete 2026-09-30.** OWC framework (base, bulk, crud, pagination, router, version_history) + security (models, rbac) + audit (models, service) + users (models) + auth stub vendored with owc→ypms renames. YPMS-specific `framework/pii_audit.py` added on top. Pagination bug in upstream fixed here (peek-ahead cursor was silently dropping rows). 26 tests passing, 93.10% coverage. Migration `0002_framework_tables.py` creates the shared tables. |
|
||||
| **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 | `Registrant` model + schemas + router + migration + permissions + tests. CRUD works end-to-end; SSN masked in serializer output; `@audit_read("pii_access")` fires on GET; every state change emits audit row. Coverage doc at `docs/coverage/registrant.md`. |
|
||||
| **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.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`. |
|
||||
|
||||
+39
-46
@@ -9,7 +9,7 @@
|
||||
|
||||
## TL;DR
|
||||
|
||||
Phase 0 milestones **0.1 → 0.4** are shipped. **0.5 (Registrant vertical slice)** and **0.6 (501-stub routers for the remaining entities)** are 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 (**53 passing, 92% coverage**), and Docker Compose boots. Not yet pushed to Gitea — auth on `gitea.doesworks.net` needs a local `git push` from the operator's terminal.
|
||||
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 over SSH.
|
||||
|
||||
---
|
||||
|
||||
@@ -17,12 +17,12 @@ Phase 0 milestones **0.1 → 0.4** are shipped. **0.5 (Registrant vertical slice
|
||||
|
||||
| Dimension | Status |
|
||||
|---|---|
|
||||
| Phase | 0 (Foundations), milestones 0.1–0.4 shipped |
|
||||
| Git branch | `main`, 9 commits ahead of `origin` (Gitea `gitea.doesworks.net/chris.tonjes/YPMS.git`) |
|
||||
| Tests | **53 passing**, **92.20% coverage** (floor 80% enforced in CI) |
|
||||
| Lint / types | `ruff` clean, `mypy` clean (32 files, strict) |
|
||||
| Runtime | FastAPI + Postgres on Docker Compose; boots to `/healthz` + `/readyz`; `/auth/*` live |
|
||||
| Migrations | 2 revisions applied (`0001_baseline`, `0002_framework_tables`) |
|
||||
| Phase | 0 (Foundations), milestones 0.1–0.5 shipped |
|
||||
| Git branch | `main`, tracking `origin/main` (Gitea `git@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 |
|
||||
| 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 |
|
||||
|
||||
@@ -79,27 +79,27 @@ Access token carries: `sub`, `uid`, `roles`, `iam.assurance_level`, `mfa_verifie
|
||||
|
||||
Guardrails baked in: `OYP_STAFF` can't see FERPA/CFSA (that's `ELIGIBILITY_EXAMINER`); `PAYROLL_ADMIN` can't either; `REPORTING_ANALYST` is strictly read-only; external portal roles never touch admin surfaces.
|
||||
|
||||
### 0.5 — Registrant vertical slice ✅
|
||||
|
||||
- `src/ypms/youth/{models,schemas,router}.py` + `migrations/versions/0003_registrants.py`
|
||||
- `src/ypms/security/column_crypto.py` — AES-256-GCM, HKDF-SHA256 key from `COLUMN_ENCRYPTION_KEY` (≥32 bytes enforced), per-value nonce, column name as AAD, `v1.` prefix for rotation
|
||||
- SSN is **write-only** on the model (`registrant.ssn = ...` encrypts + sets `ssn_last4`); the only read path is `decrypt_ssn()`. Serializer exposes `ssn_masked` only.
|
||||
- `pii_read` audit on GET, list (one row per page, ids in `after.ids`), and history — `framework/pii_audit.py` gained `emit_pii_list_read`
|
||||
- Own-row scoping for portal-only callers via nullable `Registrant.user_id` (set by Phase 1 account-link flow; not API-writable). PARENT_USER sees nothing until Guardian lands.
|
||||
- Hand-built router on `CRUDService` instead of `make_router` — needs PII-read audit, own-row scoping, and `VersionHistory`-gated history. No bulk endpoints for Registrant.
|
||||
- **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).
|
||||
|
||||
---
|
||||
|
||||
## What's next
|
||||
|
||||
### Immediate next milestone: 0.5 — Registrant vertical slice
|
||||
### Immediate next milestone: 0.6 — 501-stub routers
|
||||
|
||||
Real model, not a stub. Proves the framework end-to-end with a domain entity that has:
|
||||
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`.
|
||||
|
||||
- Masked SSN in serializer output (`XXX-XX-1234`)
|
||||
- App-layer encrypted SSN + banking-placeholder columns
|
||||
- `@audit_read`-equivalent PII-read audit on GET (via `emit_pii_read`)
|
||||
- Full CRUD flowing through `CRUDService` with automatic audit + version-history rows
|
||||
- Migration adding the `registrants` table
|
||||
- Seed matrix already grants OYP_STAFF / ELIGIBILITY_EXAMINER / PAYROLL_ADMIN / etc. their scoped rights on it (from 0.4)
|
||||
- Tests: happy paths, SSN masking verified end-to-end via TestClient, PII read logged
|
||||
### After 0.6
|
||||
|
||||
Coverage doc lands at `docs/coverage/registrant.md`.
|
||||
|
||||
### After 0.5
|
||||
|
||||
- **0.6** — 501-stub routers for the other 25 entities in ARCH §6.1. Each still fires RBAC + audit. Permission gaps surface at Phase 0 review.
|
||||
- **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)
|
||||
@@ -145,23 +145,9 @@ Each has a POA&M ID. See [`POAM.md`](POAM.md) for the full list of items. These
|
||||
### Git
|
||||
|
||||
- Branch: `main`
|
||||
- Remote: `origin` → `https://gitea.doesworks.net/chris.tonjes/YPMS.git`
|
||||
- **9 commits ahead of `origin/main`** — push blocked on Gitea auth (interactive prompt). Push from a local terminal:
|
||||
```bash
|
||||
git -C /Users/christonjes/cc/ypms push -u origin main
|
||||
```
|
||||
- Commit history (newest first):
|
||||
```
|
||||
390e917 Mark milestone 0.4 complete in PHASE0_PLAN and POA&M
|
||||
913c69c Phase 0 milestone 0.4: RBAC seed with 8 roles + permissions matrix
|
||||
0050ad1 Mark milestone 0.3 complete in PHASE0_PLAN and POA&M
|
||||
0501850 Phase 0 milestone 0.3: auth + JWT + iam.assurance_level claim
|
||||
7379bb4 Mark milestone 0.2 complete in PHASE0_PLAN and POA&M
|
||||
ba75658 Phase 0 milestone 0.2: vendor OWC framework + YPMS pii_audit
|
||||
09d862b Mark milestone 0.1 complete in PHASE0_PLAN and POA&M
|
||||
393631a Phase 0 milestone 0.1: repo scaffold + Docker Compose
|
||||
8712e93 Initial YPMS planning and ATO artifacts
|
||||
```
|
||||
- Remote: `origin` → `git@gitea.doesworks.net:chris.tonjes/YPMS.git` (SSH; switched from HTTPS 2026-09-30)
|
||||
- `main` tracks `origin/main`. Push with `git -C /Users/christonjes/cc/ypms push`.
|
||||
- Milestone history: `git log --oneline` — one "Phase 0 milestone 0.N" commit per milestone, followed by a "Mark milestone 0.N complete" docs commit.
|
||||
|
||||
### Local git identity (repo-local, not global)
|
||||
|
||||
@@ -182,9 +168,11 @@ git -C /Users/christonjes/cc/ypms config user.email <addr>
|
||||
| `security/` (models, rbac) | 80–100% |
|
||||
| `auth/` (security, deps, router, schemas, rate_limit) | 80–100% |
|
||||
| `seed/` (roles, permissions, run) | 77–100% |
|
||||
| Overall | **92.20%** |
|
||||
| `youth/` (models, router, schemas) | 99–100% |
|
||||
| `security/column_crypto.py` | 100% |
|
||||
| Overall | **94.83%** |
|
||||
|
||||
`framework/router.py` is excluded from coverage until it has real routes (0.5) — coverage config in `pyproject.toml`.
|
||||
`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`.
|
||||
|
||||
---
|
||||
|
||||
@@ -256,6 +244,12 @@ The GitHub Actions workflow at `.github/workflows/ci.yml` runs the same commands
|
||||
|
||||
8. **Alembic baseline (0001) is empty by design.** All schema lives in 0002+ so that "the framework tables" can be re-derived as an additive layer during future architecture reviews.
|
||||
|
||||
9. **`Registrant.ssn` has no getter.** Reading it raises `AttributeError` on purpose. Use `decrypt_ssn()` for the one sanctioned plaintext path, and never put it in a response schema or log line.
|
||||
|
||||
10. **Any new encrypted column must be listed in the model's `__audit_redact__`** — otherwise its ciphertext lands in `audit_log.before/after` and `version_history.snapshot`.
|
||||
|
||||
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`.
|
||||
|
||||
---
|
||||
|
||||
## File map
|
||||
@@ -292,13 +286,12 @@ Then the ATO artifacts as needed:
|
||||
|
||||
---
|
||||
|
||||
## Signals to watch for as we approach milestone 0.5
|
||||
## Signals to watch for as we approach milestone 0.6
|
||||
|
||||
- Any need to touch `framework/` or `security/` — resist unless it's a genuine framework improvement that would flow back to OWC. Domain entities live in their own directories.
|
||||
- Any place where a Registrant handler is tempted to skip `emit_pii_read` — it isn't optional; the audit trail is load-bearing for the ATO story on minors' PII.
|
||||
- Any temptation to store SSN in cleartext — SSN goes into the app-layer encrypted column; the serializer produces `XXX-XX-1234`; only the `ssn_last4` field is stored plain and only for search/display.
|
||||
- Any premature row-level RBAC — that's Phase 2+. If a phase-0 handler needs "the caller's own row," it can filter by `actor.id`, but don't build a generic scope layer yet.
|
||||
- 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.
|
||||
- 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.1.**
|
||||
**End of handoff v0.2.**
|
||||
@@ -176,6 +176,7 @@ Closures retained for traceability.
|
||||
| — | Phase 0 milestone 0.2 shipped (partial closure of YPMS-POAM-040) | 2026-09-30 | OWC framework vendored (base/crud/bulk/pagination/router/version_history + audit + security/rbac + users + auth stub) with owc→ypms renames. YPMS pii_audit decorator added. Pagination bug in upstream OWC fixed here (peek-ahead cursor). 26 tests passing, 93.10% coverage. Migration 0002 creates the framework tables. |
|
||||
| — | 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`. |
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in new issue
Block a user