Files
YPMS/PHASE0_PLAN.md
T
Chris TonjesandClaude Opus 4.7 8712e93258 Initial YPMS planning and ATO artifacts
Pre-development state; no code yet.

Delivery model: in-house build by DOES OIT on Azure Government,
matching the DCYWP / OWC / Employer 360 pattern.

Stack decision (2026-09-30): Python 3.12 / FastAPI / SQLAlchemy /
Postgres, aligning with OWC.

System Owner / ISSO / AO: Chris Tonjes (DOES CIO). Role concentration
recorded in SSP §5 with risk-acceptance rationale.

Source functional and security requirements derived from the prior
YPMS RFP (Solicitation Docxxxxx, April 2025) — 54 C.5.7 security
requirements and Appendix E non-functional requirements — treated as
source specifications, not as Contractor obligations.

Documents included:

- CLAUDE.md, OVERVIEW.md — project router and program-level summary
- SYSTEM_SECURITY_PLAN.md v0.3 — NIST 800-53 Moderate baseline SSP
- NIST_800_53_CONTROL_MAPPING.md v0.2 — full control-family mapping
  with in-house ownership tags and evidence-artifact expectations
- POAM.md v0.1 — 62-item POA&M seeded from tailorings, open decisions,
  missing companion docs, and pre-ATO deliverables; two items closed
- PRIVACY_IMPACT_ASSESSMENT.md v0.1 — full PIA covering minors, SSN,
  banking, FERPA-covered OSSE records, CFSA foster/ward flags, and
  the dormant emergency health-record path
- ARCHITECTURE.md v0.1 — forward-looking design blueprint, OWC-aligned
- PHASE0_PLAN.md v0.1 — framework vendor-in, auth, RBAC, audit,
  Registrant vertical slice, 501 stubs for remaining entities
- TODO.md — 12-section working checklist tied to POA&M IDs
- OLA_RESPONSE_2026-09-30.md — Q&A for OLA / City Administrator

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-09-30 12:14:24 -04:00

22 KiB
Raw Blame History

YPMS — Phase 0 Plan

Phase: 0 — Foundations (runnable local skeleton) Depends on: ARCHITECTURE.md v0.1, SYSTEM_SECURITY_PLAN.md v0.3, POA&M v0.1 Stack: Python 3.12 · FastAPI · SQLAlchemy 2.x · Alembic · Postgres 16 (SQLite in tests) · Docker Compose · pytest — matches OWC No frontend. Phase 0 is API + minimal admin shell. Public portal is Phase 1+.


1. Phase 0 scope

Phase 0 delivers a runnable local repo that every future phase bolts onto. It does not implement domain logic (eligibility rules, matching, timekeeping, payroll, integrations) — that's Phase 1+.

In scope

Area What ships
Repo scaffold Monorepo layout matching ARCHITECTURE.md §5.1: src/ypms/, tests/, migrations/, docs/. pyproject.toml, Dockerfile, docker-compose.yml, alembic.ini, .env.example. docker compose up boots app + Postgres.
Framework vendor-in Copy OWC framework/ and security/ modules into src/ypms/framework/ and src/ypms/security/. Header comment names the OWC commit; if a YPMS-specific tweak is needed it stays in YPMS's copy and does not flow back.
Config, logging, health pydantic-settings-based config, structured JSON logging (sensitive-field sanitizer per ARCH §10.1), /healthz and /readyz.
Auth Local username + password (pbkdf2_sha256) + JWT. iam.assurance_level claim issued at authentication time (§4 of ARCH). No external IdPs in Phase 0.
RBAC seed Data-driven permissions matrix seeded from src/ypms/seed/permissions_matrix.json. Roles seeded: DOES_SYSTEM_ADMIN (super-role, bypasses checks), OYP_STAFF, ELIGIBILITY_EXAMINER, PAYROLL_ADMIN, REPORTING_ANALYST, HOST_USER, PARENT_USER, YOUTH_USER.
Generic Entity base CRUD + soft delete + optimistic locking + audit + version history + cursor pagination + bulk create/update/delete. Vendored from OWC.
Audit logging Framework-emitted for every state change (create / update / delete / restore). Structured JSON, append-only table. Ties to NIST AU-2 / AU-3 / AU-12 and source RFP C.5.7.39, C.5.7.48, C.5.7.49.
PII-read audit decorator YPMS addition: @audit_read("pii_access") on sensitive-record read endpoints. Ties to source RFP C.5.6.9 #5. Exercised on the Phase 0 Registrant vertical slice.
Registrant vertical slice The Registrant entity is real, end-to-end: model, schemas, router, migration, permissions, tests, coverage doc. Proves the framework works with a domain entity that has masked SSN, encrypted-at-rest banking placeholder, and PII-read audit.
501 stubs for other entities Every entity enumerated in ARCH §6.1 (Guardian, Host, Worksite, Position, Program, Application, Enrollment, TimeEntry, Timesheet, PayrollBatch, TransportationVoucher, Attachment, Note, IntegrationEvent, Notification, EducationRecord, FosterIndicator, ConsentRecord, Cohort, EligibilityRule, JobPosting, HostContact, Certification, Orientation, TaxDocument) shipped as a 501-returning router that still fires RBAC + audit. Permission gaps surface at Phase 0, not Phase N.
Retention scaffold retention_policy attribute declared on entity models (per ARCH §6.4). Nightly job wired but no-op in Phase 0 — enforcement lands with the entity-specific implementations in later phases.
Alembic baseline One initial migration containing all model tables. Phase 0 baseline is 0001_baseline.py; immutable once merged.
CI-ready layout GitHub Actions workflow (matches OWC): lint (ruff), type check (mypy or pyright — match OWC), tests (pytest), SAST (bandit), SCA (pip-audit), secret scan (gitleaks), SBOM (CycloneDX). Blocks merge on failure.
Test discipline ≥ 80% coverage on framework/ and security/. Every other module needs at least one happy-path test.
Docs README.md (runnable quickstart, entity-adding template), CONTRIBUTING.md (hard rules, mirrored from OWC), PHASE0_PLAN.md (this file), docs/nist-mapping.md (stub).

Out of scope (Phase 1+)

  • Any frontend. Public portal, host portal, staff portal — all Phase 1+. The Phase 0 admin surface is FastAPI's built-in /docs (OpenAPI UI).
  • External IdP integration. Entra ID (staff), Login.gov (youth/host), social-media OAuth (browsing) — all Phase 1.
  • Parental co-consent lifecycle. Data model has a Guardian stub and a ConsentRecord stub; the workflow that binds them to an Application is Phase 1 alongside intake.
  • Integrations. SSA, OSSE, CFSA, DC MAR, payroll vendor, LMS — Phase 2 or later depending on dependency.
  • Eligibility engine. Rules seed, engine, decision workflow — Phase 2.
  • Timekeeping and payroll. Phase 7.
  • Reporting. MicroStrategy replacement is Phase 8.
  • Retention automation execution. Scaffold only; enforcement per entity as entities gain real fields.
  • Row-level RBAC. Phase 0 is "can role X do action Y on entity type Z?" Row-level ("only your caseload") is Phase 2+.
  • Real PII. Dev and test are synthetic only. Sanitization pipelines for prod-refresh come with Phase 5 CSV-ingest work.

2. Traceability

Every Phase 0 component maps to a source-RFP requirement or a NIST control. Table is the audit trail — mirrors OWC's PROJECT_PLAN.md §2 pattern.

Component Source Notes
User model + role seed Source RFP C.5.7.29–C.5.7.34; NIST AC-2 8 initial roles; seeded at migration time
RBAC permission check Source RFP C.5.7.27, C.5.7.32; NIST AC-3, AC-6 Data-driven; matrix loaded from seed JSON
Generic CRUD framework Source RFP C.5.21.31–C.5.21.34 (define, view, comment on, view history of cases and tasks); vendored from OWC Build once, reuse
Audit log Source RFP C.5.7.39, C.5.7.48, C.5.7.49, C.5.6.9 #4, #8, #11, #12, #13, #17, #18; NIST AU-2, AU-3, AU-12 Structured JSON, append-only table
PII-read audit decorator Source RFP C.5.6.9 #5; NIST AU-14 YPMS-specific addition above OWC's framework
Soft delete + version history Source RFP implicit (case/task history retention C.5.21.34) Common base mixin
Auth + session mgmt Source RFP C.5.7.1, C.5.7.3–C.5.7.14, C.5.7.38; NIST IA-2, IA-4, IA-5 Local + JWT; iam.assurance_level claim for future assurance gates
Password hashing Source RFP C.5.7.9; NIST IA-5(1) pbkdf2_sha256; FIPS 140-3 by container of the library where available
Config / secrets handling NIST SC-12, SC-28, CM-6 Env-based; .env.example committed, .env not; Key Vault comes Phase 1
Registrant vertical slice Source RFP C.5.3.1, C.5.3.4 (youth application, account management); ARCH §6.1 Proves the framework end-to-end
501-stubs on remaining entities Source RFP C.5.7.27 (enforcement even on unimplemented routes) RBAC + audit fire regardless
Alembic migration discipline NIST CM-3, CM-4; source RFP C.5.8.12 (impact analysis before DB change) Migrations immutable once merged (OWC hard rule)

3. Milestones

# Milestone Exit criteria
0.1 Repo scaffold + Docker Compose boots git init in ypms/, tree matches ARCH §5.1, docker compose up starts app + Postgres, curl :8000/healthz returns 200.
0.2 Framework vendor-in src/ypms/framework/ and src/ypms/security/ contain vendored OWC modules; header comment names OWC commit; base tests from OWC pass in YPMS repo (adjusted for module path).
0.3 Auth + JWT + assurance_level POST /auth/login returns a JWT with sub, iat, exp, iam.assurance_level, roles claims. get_current_user dep verifies. Local password auth uses pbkdf2_sha256.
0.4 RBAC seed loaded src/ypms/seed/permissions_matrix.json loaded at startup; can(user, action, entity) returns correct decisions per matrix; DOES_SYSTEM_ADMIN bypasses. Seed round-trip test passes.
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.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.
0.9 Docs + acceptance README.md with runnable quickstart; CONTRIBUTING.md with hard rules; docs/nist-mapping.md skeleton; this file marked complete.

Each milestone independently mergeable.


4. Data-model sketch (Phase 0)

Minimal. Real fields on Registrant because it's the vertical slice; everything else is a stub with id, created_at, updated_at, deleted_at, version — enough to demonstrate framework mixins.

Registrant                                        (real — vertical slice)
  ├── id
  ├── first_name, middle_name, last_name
  ├── dob
  ├── ssn_encrypted            (app-layer encrypted; masked in serializer to XXX-XX-1234)
  ├── ssn_last4                (denormalized for search / display)
  ├── email, phone
  ├── address_line1, line2, city, state, zip
  ├── minor_flag               (derived from dob at read time)
  ├── created_at, updated_at, deleted_at, version   (from framework mixins)

User                                              (real — needed for auth)
  ├── id, username, password_hash, is_active
  ├── roles (M2M → Role via RoleAssignment)
  ├── mfa_verified             (Phase 0: always false; Phase 1 flips to real)
  ├── assurance_level          ('standard' by default; upgraded by Phase 1 flows)

Role                                              (real — seed-loaded)
  ├── id, name, description, is_superuser

Permission                                        (real — seed-loaded)
  ├── id, entity, action

RoleAssignment                                    (real — junction)
  ├── role_id, permission_id

AuditRow                                          (real — framework)
  ├── id, actor_id, action, entity, entity_id
  ├── request_id, before (JSONB), after (JSONB), timestamp

All other entities from ARCH §6.1                 (stubs — id + mixins only)
  Guardian · ConsentRecord · Host · HostContact · Worksite · Position ·
  JobPosting · Program · Cohort · EligibilityRule · Application ·
  EligibilityDecision · Certification · Orientation · Enrollment · TimeEntry ·
  Timesheet · PayrollBatch · TaxDocument · TransportationVoucher · Attachment ·
  Note · IntegrationEvent · Notification · EducationRecord · FosterIndicator

5. Repository layout at Phase 0 end

Matches ARCH §5.1 with only the Phase 0 subset populated:

ypms/
├── README.md                        (runnable quickstart)
├── CONTRIBUTING.md                  (hard rules mirrored from OWC)
├── PHASE0_PLAN.md                   (this file)
├── ARCHITECTURE.md                  (design blueprint)
├── ... (SSP, control mapping, POA&M, PIA, TODO, CLAUDE.md, OVERVIEW.md)
├── Dockerfile
├── docker-compose.yml
├── pyproject.toml
├── alembic.ini
├── .env.example
├── .github/workflows/ci.yml
├── migrations/versions/0001_baseline.py
├── src/ypms/
│   ├── __init__.py
│   ├── main.py                      (FastAPI factory + router registration)
│   ├── settings.py                  (pydantic-settings)
│   ├── logging.py                   (structured JSON + sensitive-field sanitizer)
│   ├── db.py                        (engine, session, base)
│   ├── deps.py                      (FastAPI deps)
│   ├── framework/                   (vendored from OWC)
│   │   ├── __init__.py
│   │   ├── entity.py                (base + mixins)
│   │   ├── crud.py                  (CRUDService with audit emission)
│   │   ├── router.py                (make_router factory)
│   │   ├── audit.py                 (framework audit emitter)
│   │   ├── pii_audit.py             (YPMS addition — @audit_read decorator)
│   │   ├── pagination.py
│   │   ├── retention.py             (scaffold; no-op in Phase 0)
│   │   └── seed_loader.py
│   ├── security/                    (vendored + YPMS additions)
│   │   ├── __init__.py
│   │   ├── auth.py                  (JWT create/verify with iam.assurance_level)
│   │   ├── passwords.py             (pbkdf2_sha256)
│   │   ├── rbac.py                  (can/require_permission decorators)
│   │   └── encryption.py            (app-layer column encryption helpers)
│   ├── seed/
│   │   └── permissions_matrix.json
│   ├── youth/                       (Registrant — real)
│   │   ├── __init__.py
│   │   ├── models.py
│   │   ├── schemas.py
│   │   ├── router.py
│   │   └── service.py               (thin service using CRUDService)
│   ├── parent/                      (Guardian — stub)
│   ├── host/                        (Host, HostContact, Worksite, Position, JobPosting — all stubs)
│   ├── program/                     (Program, Cohort, EligibilityRule — stubs)
│   ├── application/                 (Application, EligibilityDecision, Certification, ConsentRecord — stubs)
│   ├── enrollment/                  (Enrollment — stub)
│   ├── orientation/                 (Orientation — stub)
│   ├── timekeeping/                 (TimeEntry, Timesheet — stubs)
│   ├── payroll/                     (PayrollBatch, TaxDocument, TransportationVoucher — stubs)
│   ├── notifications/               (Notification — stub)
│   ├── integrations/                (IntegrationEvent — stub; no adapter modules yet)
│   ├── attachments/                 (Attachment, Note — stubs)
│   ├── education_records/           (EducationRecord — stub; FERPA-scoped)
│   ├── foster_indicators/           (FosterIndicator — stub; Title 4-scoped)
│   └── admin/                       (User, Role, Permission — real; system-admin endpoints)
├── tests/
│   ├── framework/                   (vendored tests + YPMS additions for pii_audit)
│   ├── security/                    (auth, rbac, encryption)
│   ├── youth/                       (Registrant e2e)
│   ├── stubs/                       (one test per stub entity: RBAC + audit fire on 501)
│   └── conftest.py
└── docs/
    ├── nist-mapping.md              (skeleton — populated per phase)
    ├── coverage/
    │   └── registrant.md
    └── compliance/                  (mirrors OWC docs/compliance/ once real controls have code)

6. NIST controls that get real (non-stub) code in Phase 0

Following OWC's pattern of only wiring controls that code actually implements. Others stay as 📝 in the control mapping until later phases.

Control Implementation
AC-2 User model, role-based accounts, admin can enable/disable
AC-3 require_permission decorator on every mutating route; 501 stubs still enforce
AC-6 RBAC via permissions matrix; DOES_SYSTEM_ADMIN super-role explicit
AC-7 5-failure lockout — Phase 0 targets the check; actual lockout with locked_until field lands here
AC-11 / AC-12 JWT expiry (short-lived access); refresh token flow lands Phase 1
AU-2 / AU-3 / AU-12 Framework audit emission on every state change; AuditRow model + emission tests
AU-8 UTC-normalized timestamps everywhere; Postgres timezone set to UTC in Docker Compose
AU-14 PII-read audit decorator on Registrant read endpoints
CM-3 Alembic migrations immutable once merged (repo policy + CI check)
IA-2 Authentication required for every non-public route
IA-4 Unique usernames; no re-issue on delete (deleted users tombstoned, username reserved)
IA-5 Password change endpoint; pbkdf2_sha256
IA-5(1) FIPS-approved password hashing
SC-8 Dev/test uses TLS via reverse-proxy in compose (self-signed OK for local); production TLS is deploy-phase
SC-12 / SC-13 / SC-28 App-layer column encryption for ssn_encrypted and banking placeholder using cryptography-library primitives; Key Vault swap-in is Phase 1
SI-10 Pydantic schema validation on all inbound requests
SI-11 Non-exploitable error responses (custom exception handler)

Everything else in the control mapping stays 📝 until later phases wire it.


7. Risks

Risk Mitigation
Vendoring OWC's framework introduces divergence over time Header-comment naming the OWC commit; document the vendored-code policy in CONTRIBUTING. Pulling upstream OWC changes is a deliberate act, not an ambient sync.
Data-driven RBAC seed drifts from code expectations Seed loaded at startup with schema validation; missing entity or action fails startup rather than silently ignoring.
501 stubs mask real permission gaps Every stub is tested to fire RBAC + audit; gap surfaces in tests, not later.
App-layer encryption in Phase 0 uses local key material .env value in dev/test only; documented; Phase 1 swap to Key Vault is a single-file change with the encryption helper interface preserved. Local keys never used in staging or prod.
Vertical slice on Registrant hides framework issues for other entity shapes 501 stubs still exercise the framework at the RBAC + audit level for every entity, catching factory-parameter mistakes.
Alembic baseline captures wrong shape and every future migration inherits the mistake Baseline reviewed against ARCH §6 by CIO before first merge; every downstream migration is additive, so wrong shapes are correctable via new migration (never edit baseline).
CI pipeline slow enough to block iteration Test suite must stay under 60s (SQLite fixtures). Framework tests get priority; slow integration-style tests deferred to a nightly workflow.
Retention scaffold gets forgotten and never enforces Every entity model asserts a retention_policy in a startup check; entity without one fails startup. Enforcement per class lands later, but the scaffold is unforgettable.

8. Definition of done for Phase 0

Phase 0 is complete when:

  • docker compose up boots app + Postgres cleanly; /healthz returns 200.
  • alembic upgrade head applies the baseline migration.
  • POST /auth/login issues a JWT with iam.assurance_level claim.
  • POST /registrants creates a Registrant with masked SSN in the response, emits an audit row, requires OYP_STAFF or higher.
  • GET /registrants/{id} reads the record, fires the PII-read audit decorator, masks SSN, requires appropriate role.
  • PATCH /registrants/{id} updates fields, emits before/after in audit row, requires appropriate role.
  • DELETE /registrants/{id} soft-deletes, emits audit, requires appropriate role.
  • Every stub entity's endpoints return 501 but still emit an audit row and enforce RBAC.
  • Tests green in CI: lint, types, pytest, SAST, SCA, secret scan, SBOM.
  • Coverage ≥ 80% on framework/ and security/; every stub has one test.
  • README.md quickstart works on a clean checkout.
  • CONTRIBUTING.md published with hard rules mirrored from OWC.
  • 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.

9. What I need from you

  1. Match OWC's Azure Gov deployment target? OWC currently runs Docker locally; its Azure Gov deployment is Phase 19/20. YPMS Phase 0 is local-only too. Confirm we're not provisioning Azure Gov in Phase 0 — Azure comes with Phase 1 (Key Vault swap-in) or later.
  2. Match OWC's CI provider? OWC uses [TBD — confirm]. I'll default to GitHub Actions unless OWC is on Azure DevOps; then I match. Say which.
  3. 8 initial roles OK, or want a different seed set? Current default: DOES_SYSTEM_ADMIN, OYP_STAFF, ELIGIBILITY_EXAMINER, PAYROLL_ADMIN, REPORTING_ANALYST, HOST_USER, PARENT_USER, YOUTH_USER. Missing roles surface fast at Phase 1; adding one later is a seed update.
  4. First entity as Registrant, or something smaller? Registrant is the natural vertical slice because everything else hangs off it. Smaller options: Program (mostly reference data) or HostContact (simple). I lean Registrant because the encryption + masking + PII-read audit path is what Phase 0 needs to prove.
  5. Test-runner + type-checker to match OWC? Confirm: pytest for tests, and pick mypy or pyright to match OWC.

If you just say "proceed" I'll:

  • Start with milestone 0.1 (repo scaffold + Docker Compose),
  • Default GitHub Actions, mypy, the 8 roles above, Registrant as the slice, and no Azure Gov in Phase 0,
  • Merge each milestone independently as it lands.

End of draft v0.1.