Files
Chris TonjesandClaude Opus 5.5 98b783b6b5 Phase 0 milestone 0.6: 501-stub routers for remaining entities
26 stub entities (every matrix entity except Registrant, User, Role,
AuditLog) get a router from framework/stub_router.py: one route per
matrix action (List, Search, Create, View, Edit, Delete, Restore,
VersionHistory). Each route runs the real RBAC check, writes an audit
row, then returns 501. Source RFP C.5.7.27.

- Registry in src/ypms/stubs.py; test fails if a matrix entity is
  neither real nor stubbed.
- tests/test_stubs.py sweeps entity x role x route against the matrix
  JSON: 501 + exactly one audit row when granted, 403 + none when not.
  FERPA/CFSA stubs pinned closed to non-examiner roles.
- Registrant restore now gated on the matrix's Restore action (was
  Edit, the vendored-factory convention); only the super-role holds it.
- Test fixtures (roles, make_user, api) lifted to conftest; one app per
  session (route construction dominated runtime) and a shared password
  hash for minted-token users.

138 tests passing, ruff + mypy clean.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 20:54:12 -04:00

5.7 KiB
Raw Permalink Blame History

Registrant — requirement coverage

Milestone: Phase 0 / 0.5 (vertical slice) · As of: 2026-09-30 Entity: Registrant (ARCHITECTURE.md §6.1) · RBAC entity string: Registrant

Traces each requirement the Registrant slice claims to the code and test that evidence it. Auditors: file paths are relative to ypms/.


Surface

Method Path Permission Portal-only callers PII-read audit
GET /registrants List 403 one pii_read row per page, after.ids = returned ids
GET /registrants/{id} View own row only (YOUTH_USER); else 404 pii_read row, entity_id = id
POST /registrants Create 403 — (create audit row)
PATCH /registrants/{id} Edit own row only (YOUTH_USER); else 404 — (update audit row)
DELETE /registrants/{id} Delete 403 — (delete audit row)
POST /registrants/{id}/restore Restore 403 — (restore audit row)
GET /registrants/{id}/history VersionHistory 403 pii_read row, entity_id = id

"Portal-only" = every role the caller holds is in {YOUTH_USER, PARENT_USER, HOST_USER}. A caller with any staff role gets staff behaviour. PARENT_USER sees no rows until the Guardian link lands (Phase 1). No bulk endpoints for Registrant in Phase 0.


Requirement trace

Requirement What the code does Evidence (code) Evidence (test)
Source RFP C.5.3.1, C.5.3.4 — youth application / account management Registrant CRUD with soft delete, restore, cursor pagination src/ypms/youth/router.py tests/youth/test_registrant_api.py
Source RFP C.5.7.50; ARCH §6.3 — SSN masked in display Read schema exposes only ssn_masked (XXX-XX-1234); masking in the serializer, so API consumers see it too src/ypms/youth/schemas.py (RegistrantRead), src/ypms/youth/models.py (ssn_masked) test_create_masks_ssn_and_never_returns_it
NIST SC-28, SC-28(1); ARCH §6.3 — app-layer encryption of SSN AES-256-GCM, HKDF-derived key, per-value nonce, column-bound AAD, versioned format; SSN is write-only on the model src/ypms/security/column_crypto.py, src/ypms/youth/models.py (ssn setter, decrypt_ssn) test_roundtrip_and_fresh_nonce, test_ciphertext_is_bound_to_column, test_ssn_is_write_only
NIST AU-2, AU-3, AU-12 — every state change audited Create / update / delete / restore emit audit rows with actor, before, after src/ypms/framework/crud.py test_create_emits_redacted_audit_and_version_snapshot, test_update_bumps_version_and_audits, test_delete_requires_delete_permission_then_restore
NIST AU-14; source RFP C.5.6.9 #5 — record access to sensitive data GET, list, and history emit pii_read rows with actor and client IP src/ypms/framework/pii_audit.py, src/ypms/youth/router.py test_get_emits_pii_read, test_list_emits_one_pii_read_with_ids, test_history_requires_version_history_permission
NIST AU-9 (protection of audit info) — no secrets in the audit trail __audit_redact__ replaces ssn_encrypted with [REDACTED] in audit before/after and version-history snapshots src/ypms/framework/crud.py (_to_dict), src/ypms/youth/models.py test_create_emits_redacted_audit_and_version_snapshot, test_update_bumps_version_and_audits
NIST AC-3, AC-6 — least privilege Data-driven RBAC per route; history gated on VersionHistory (not View); delete requires Delete and restore requires Restore (only the super-role holds either today) src/ypms/youth/router.py, src/ypms/seed/permissions_matrix.json test_history_requires_version_history_permission, test_delete_requires_delete_permission_then_restore, test_host_user_denied
NIST AC-3 — row-level restriction for external users (Phase 0 minimum) Portal-only callers limited to own row by user_id; out-of-scope → 404 (no id probing) src/ypms/youth/router.py (_get_scoped, _require_staff) test_youth_can_read_and_edit_only_own_row, test_youth_blocked_from_staff_routes, test_parent_sees_nothing_until_guardian_link
NIST SI-10 — input validation SSN format + SSA never-issued ranges; DOB not in future; ZIP, email, length limits; NOT NULL fields can't be PATCHed to null; user_id not writable src/ypms/youth/schemas.py test_create_validation, test_update_rejects_null_on_required_field, test_update_cannot_set_user_id
ARCH §3 "minors first" — minor status minor_flag derived from DOB at read time, never stored src/ypms/youth/models.py (minor_flag, age_on) test_minor_flag_derived_from_dob, test_age_on_boundaries
NIST CM-3; source RFP C.5.8.12 — schema change control New immutable Alembic revision migrations/versions/0003_registrants.py rendered offline (alembic upgrade 0002_framework_tables:0003_registrants --sql); live Postgres run pending

Deferred (not in 0.5)

Item Why deferred Lands in
Banking columns (encrypted account, last-4) Needs PAYROLL_ADMIN-only field scoping built alongside it; payroll vendor not selected (YPMS-POAM-052) Payroll phase
SSN blind index (HMAC) for full-SSN dedupe/search ssn_last4 covers Phase 0 display/search; dedupe is an intake concern Phase 1 (application intake)
Search endpoint Matrix grants Search; no route yet Phase 1
Guardian-scoped access for PARENT_USER Needs Guardian + ConsentRecord Phase 1
Account-link flow setting user_id Identity model open (YPMS-POAM-050) Phase 1
Key Vault–backed key + v2 ciphertext Phase 0 uses env-var key material Phase 1 (YPMS-POAM-032)
Demographics, immigration-doc refs, tax elections Not in PHASE0_PLAN §4 sketch Phase 1+