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
+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.**
|
||||
Reference in new issue
Block a user