# Architecture — YPMS Phase 0 **Document version:** 0.1 (Draft) **Date:** 2026-09-30 **Author:** Chris Tonjes, DOES CIO **Companion to:** [`CLAUDE.md`](CLAUDE.md), [`OVERVIEW.md`](OVERVIEW.md), [`SYSTEM_SECURITY_PLAN.md`](SYSTEM_SECURITY_PLAN.md), [`NIST_800_53_CONTROL_MAPPING.md`](NIST_800_53_CONTROL_MAPPING.md), [`POAM.md`](POAM.md), [`PRIVACY_IMPACT_ASSESSMENT.md`](PRIVACY_IMPACT_ASSESSMENT.md) This document lays out the architecture YPMS will be built to. It follows the OWC pattern — same stack, same framework instincts, same delivery discipline — with YPMS-specific adjustments for the youth-workforce domain, the four-population identity model, and the integration-heavy data flows. --- ## 1. Purpose and scope YPMS replaces seven legacy tools that today run DOES Office of Youth Programs (MBSYEP, HSIP, MBYLI, CRESP, and future OYP programs). See [`OVERVIEW.md`](OVERVIEW.md) for the program-level summary and the legacy-tool inventory. Phase 0 delivers the **framework and scaffolding**: identity, RBAC, audit, generic CRUD, and empty entity stubs. Domain logic (eligibility engine, matching, timekeeping, payroll, integrations) lands in phases 1–N. Explicit non-goals for this architecture document: - Not a rehash of the source RFP. That's captured in the SSP Appendix C traceability. This doc talks about the target-state system. - Not a UI style guide. Screen designs are a separate phase-1 artifact. - Not an operational runbook. That belongs in `CONTINGENCY_PLAN.md` and `INCIDENT_RESPONSE_PLAN.md` once authored. --- ## 2. Guiding principles 1. **Align with OWC where alignment is free.** Same stack, same framework instincts, same test discipline. Two systems sharing patterns is two systems that stay maintainable. Where YPMS's problem shape genuinely differs, we diverge with a written reason. 2. **Source-RFP traceability is load-bearing.** Every substantive feature traces to a source-RFP requirement (`C.5.x.y`) or a NIST control. Auditors trace forward from RFP → code; developers trace backward from code → why. The `NIST_800_53_CONTROL_MAPPING.md` Appendix C is the durable index. 3. **Minors first.** ~27,000 registrants are minors, some under 18. Every design choice — auth, consent, retention, display — starts from that constraint. Parental co-consent, IAL2 proofing before payroll, SSN masking, foster-status role scoping are all consequences. 4. **Data-driven over code-driven** where the requirement is a policy that will change. Eligibility rules (source RFP Appendix D), permissions matrix, program definitions, retention policy, notice text — these all live in seed data or the DB, not in Python conditionals. Rule changes are DB / seed updates, not deploys. 5. **Integrations are boundaries.** SSA, OSSE, CFSA, DC MAR, payroll vendor, LMS all sit outside the trust boundary. Every integration is a service in its own module with its own retries, its own audit, and its own kill switch. When (not if) a partner has an outage, YPMS should degrade sensibly, not crash. 6. **Every state-mutating action emits an audit row.** Same rule as OWC. Enforced in the CRUD service and route factory so you can't accidentally write a handler that bypasses it. PII-touching reads are audited too (source RFP `C.5.6.9` #5). 7. **Phased delivery, phase plans authoritative.** OWC's `PHASE{N}_PLAN.md` pattern is proven — small phases, explicit in/out scope, current phase plan wins over older ones on conflict. Same pattern here. --- ## 3. System context ``` ┌─────────────────────────────────────────┐ │ External identity providers │ │ ┌──────────┐ ┌──────────────────┐ │ │ │ Entra ID │ │ Social-media │ │ │ │ (staff) │ │ IdPs (youth)* │ │ │ └──────────┘ └──────────────────┘ │ └────────────────┬────────────────────────┘ │ OIDC / OAuth ▼ ┌───────────────┐ ┌─────────────────────┐ ┌───────────────┐ │ Youth / │ HTTPS │ │ HTTPS │ DOES staff │ │ Parent portal ├─────────►│ YPMS App │◄─────────┤ portal │ │ (mobile-first)│ │ (FastAPI / Jinja / │ │ (internal) │ └───────────────┘ │ HTMX) │ └───────────────┘ │ │ ┌───────────────┐ HTTPS │ ┌───────────────┐ │ │ Host / ├─────────►│ │ Postgres │ │ │ Employer │ │ │ (Azure Gov, │ │ │ portal │ │ │ private ep) │ │ └───────────────┘ │ └───────────────┘ │ │ │ │ Integration │ │ adapters (below) │ │ │ └──┬──┬──┬──┬──┬──┬──┘ │ │ │ │ │ │ ┌──────────────────────────┘ │ │ │ │ └────────────────────────────┐ │ ┌────────────────────┘ │ │ └──────────────┐ │ ▼ ▼ ▼ ▼ ▼ ▼ ┌─────┐ ┌────────┐ ┌───────┐ ┌────────┐ ┌────────┐ ┌────────────┐ │ SSA │ │ OSSE │ │ CFSA │ │DC MAR │ │ LMS │ │ Payroll │ │(API)│ │(CSV) │ │(CSV) │ │(API) │ │(API) │ │ vendor │ └─────┘ └────────┘ └───────┘ └────────┘ └────────┘ │ (API) │ └────────────┘ ┌───────────────────────────────────────┐ │ DOES OIT infrastructure telemetry │ │ ┌─────────────────────┐ │ │ │ Sentinel / OCTO │ │ │ │ SOC │◄────────┼──── log stream │ └─────────────────────┘ │ └───────────────────────────────────────┘ * Social-media login for youth is limited to browsing / pre-application. IAL2-equivalent proofing (Login.gov or SSA verification) is required before banking/payroll actions. See PIA §12.3 and YPMS-POAM-033. ``` --- ## 4. Guiding architectural choices ### 4.1 Why the OWC framework OWC has an in-repo framework at `src/owc/framework/` and `src/owc/security/` that: generic-CRUD's every entity, enforces data-driven RBAC, emits audit for every state change, and lets a new entity be scaffolded in ~100 lines. YPMS has fewer distinct entities (~25 at Go-Live) but the same pattern applies: application, registrant, parent, host, worksite, position, job posting, enrollment, program, cohort, orientation, timesheet, payroll batch, transportation voucher, note, attachment, audit row, permission, role, user, session, notification, integration event, eligibility rule, event schedule. **YPMS Phase 0 vendors the OWC framework in.** Copy — don't submodule — because the two projects can and should evolve independently once YPMS diverges. Vendored code carries a comment naming the OWC commit it was copied from so we can pull upstream fixes deliberately. If the vendored code needs a YPMS-specific change, it stays inside `ypms/app/framework/` and doesn't bleed back. ### 4.2 Why data-driven RBAC The source RFP `C.5.7.29`–`C.5.7.34` mandates group + individual assignment, role-based access control, and admin-managed role definitions. That plus DOES-specific role realities (OYP eligibility examiners vs. payroll admins vs. hearing officers vs. read-only reporting analysts) means the permission matrix will change repeatedly during and after build. **Same DB-plus-seed pattern as OWC.** Refinements are seed updates, not deploys. A superuser role (`DOES_SYSTEM_ADMIN`) bypasses checks the same way `ARKCASE_ADMINISTRATOR` does in OWC. ### 4.3 Why audit lives in the framework, not in domain modules NIST AU-2 / AU-3 / AU-12 and source RFP `C.5.7.39`, `C.5.7.48`, `C.5.7.49` require recording actor, action, resource, and before/after state on every consequential event. If audit emission lived per-handler, every new endpoint would be a chance to forget. Instead, `CRUDService` emits an audit row for create/update/delete/restore, and the router factory emits one even for 501 stubs. **You can't write a route that mutates state without producing an audit row.** Same rule as OWC. Additionally, YPMS audits **PII-touching reads** on sensitive-data screens (source RFP `C.5.6.9` #5) — a stricter posture than OWC needs. Achieved by decorating sensitive-record read handlers with an `@audit_read("pii_access")` decorator that flows through the same emission path. ### 4.4 Why soft delete + version history are mixin defaults Same as OWC: base mixins make Archive and Version-history the default rather than retrofitting per-entity. Hard-delete via `CRUDService.hard_delete()` for admin scenarios only. YPMS adds a **PII purge** hard-delete path used by retention automation (§9 of PIA) — separate code path from user-initiated delete, always logged with the retention rule that triggered it. ### 4.5 Why JWT plus an OIDC-shaped interface Phase 0 needs working auth without wiring three external IdPs (Entra ID for staff, an external IdP for youth/host, social-media OAuth). Local username/password + JWT solves that. The shape of `get_current_user` and `security.create_access_token` is OIDC-compatible — Phase 1 swaps the implementation (issuer, JWKS-based verification, refresh tokens) without changing every caller. Same pattern as OWC. **YPMS-specific twist**: because there are four user populations with different assurance requirements, the token payload includes an `iam.assurance_level` claim (values: `pre_application`, `standard`, `payroll_authorized`, `staff`, `admin`). Authorization decisions on payroll-touching actions require `iam.assurance_level >= payroll_authorized`, which is only issued after IAL2 proofing. This lets us ship pre-application flows in Phase 1 without waiting on the proofing integration. ### 4.6 Why pbkdf2_sha256, not bcrypt Same reason as OWC: bcrypt 4.x has a runtime incompatibility with passlib's loader on Python 3.12+. Reassess when migrating to argon2id. Password hashing only applies to local-user accounts (initial DOES staff, test users); production staff use Entra ID / SSO, and youth/host external identity is either social-media OAuth or federated to Login.gov. ### 4.7 Why every stub still fires RBAC + audit Same as OWC. Deferred actions return 501 but the request still flows through `require_permission` and emits an audit event. Permission gaps surface at Phase 0 review, not at Phase N feature-completion. ### 4.8 Why Postgres in dev, SQLite in tests Same as OWC. Dev uses Postgres in Docker; tests use SQLite for sub-second runs; anything Postgres-specific (FTS, JSONB ops, address-tsvector search) lives in `framework/search.py` and gets its own Postgres-only test path. ### 4.9 Why server-rendered Jinja + HTMX for the internal portal, more JS for public The internal DOES staff portal is form-heavy, keyboard-driven, and used by ~90 people. OWC's Jinja + HTMX pattern is a proven fit and keeps front-end complexity manageable. The public youth / parent / host portal has a wider audience (~35,000), more responsive-design requirements, and mobile-first expectations from the source RFP (`C.5.21.1`). We reserve the option to build the public portal as a richer client (Vite + React or SvelteKit) rendered by the same FastAPI backend, deciding at Phase 3 kickoff based on how far HTMX carries us. ### 4.10 Why source RFP wording is treated as spec, not gospel The source RFP is the authoritative statement of what OYP needs YPMS to do. But some of its wording predates current standards: | Source RFP | Wording | What we build | |---|---|---| | `C.5.6.15` | "SSL" | TLS 1.2 minimum, 1.3 preferred, FIPS-approved cipher suites | | `C.5.7.6` | "unique identifier + shared secret answer" | MFA for staff and admin; MFA above-standard for payroll-authorized youth actions | | `C.5.7.9` | "FIPS 140-2" | FIPS 140-3 where available; 140-2 acceptable during deprecation window | | `C.5.7.24` | "Level 3 security" | Treat as NIST 800-63 IAL3/AAL3 pending AO clarification | | `C.5.3.1` glossary | "Youth: DC resident participants between 99–24 years old" | 14–24 per program-specific eligibility | These tailorings are documented in the SSP Appendix D and control-mapping §5. The build follows the tailored level. --- ## 5. Layered architecture ``` ┌────────────────────────────────────────────────────────────────────────┐ │ Presentation │ │ ┌─────────────────────┐ ┌─────────────────────┐ ┌─────────────────┐ │ │ │ Youth/Parent portal │ │ Host/Employer portal│ │ Internal portal │ │ │ │ (public; TBD Vite/ │ │ (public; same as │ │ (Jinja+HTMX, │ │ │ │ HTMX at Phase 3) │ │ Youth stack) │ │ OWC-style) │ │ │ └─────────────────────┘ └─────────────────────┘ └─────────────────┘ │ │ Cross-cutting: WCAG 2.1 AA · Language Access · print-friendly views │ └────────────────────────────────────────────────────────────────────────┘ │ ┌───────────────────────────────────▼────────────────────────────────────┐ │ API / application │ │ FastAPI routers per entity, generated by the vendored OWC router │ │ factory. Each router: RBAC-gated, audit-emitting, versioned URL space. │ │ │ │ ┌───────────────────┐ ┌────────────────────┐ ┌────────────────────┐ │ │ │ Public API │ │ Internal API │ │ Admin API │ │ │ │ (rate-limited, │ │ (staff-scoped, │ │ (bulk, config, │ │ │ │ anti-bot on │ │ keyboard-friendly │ │ data-model mgmt) │ │ │ │ peak windows) │ │ UX) │ │ │ │ │ └───────────────────┘ └────────────────────┘ └────────────────────┘ │ │ │ │ Domain services: eligibility engine · matching · timekeeping · │ │ notifications · reporting · consent lifecycle │ └────────────────────────────────────────────────────────────────────────┘ │ ┌───────────────────────────────────▼────────────────────────────────────┐ │ Framework │ │ Vendored from OWC: CRUDService · router factory · RBAC · audit · │ │ soft-delete/version mixins · JWT/OIDC-shaped auth · pagination · │ │ search hooks · seed loader │ │ YPMS-added: PII-read audit decorator · retention purge · parental- │ │ consent lifecycle helper · integration-event queue │ └────────────────────────────────────────────────────────────────────────┘ │ ┌───────────────────────────────────▼────────────────────────────────────┐ │ Data │ │ ┌─────────────────────┐ ┌────────────────────┐ ┌────────────────────┐│ │ │ Postgres flexible │ │ Azure Blob (docs, │ │ Azure Key Vault ││ │ │ server (Azure Gov) │ │ photos, resumes) │ │ (FIPS 140-3 HSM) ││ │ │ private endpoint │ │ private endpoint │ │ keys, secrets ││ │ └─────────────────────┘ └────────────────────┘ └────────────────────┘│ │ Alembic migrations, immutable once merged. Row-level PII fields │ │ encrypted at the app layer (SSN, banking, TFN); rest via TDE. │ └────────────────────────────────────────────────────────────────────────┘ │ ┌───────────────────────────────────▼────────────────────────────────────┐ │ Integrations (external) │ │ Each integration is a service module with retries, circuit-breaker, │ │ audit, and kill switch. All calls TLS 1.2+; auth via KV-stored secrets.│ │ See §7. │ └────────────────────────────────────────────────────────────────────────┘ ``` ### 5.1 Repository layout (planned) Matches OWC's `owc-system/src/owc/` shape: ``` ypms/ ├── ARCHITECTURE.md (this file) ├── CLAUDE.md ├── OVERVIEW.md ├── TODO.md ├── SYSTEM_SECURITY_PLAN.md ├── NIST_800_53_CONTROL_MAPPING.md ├── PRIVACY_IMPACT_ASSESSMENT.md ├── POAM.md ├── OLA_RESPONSE_2026-09-30.md ├── PROJECT_PLAN.md (to be authored — mirrors OWC/PROJECT_PLAN.md) ├── PHASE0_PLAN.md (to be authored) ├── README.md (runnable quickstart; to be authored) ├── CONTRIBUTING.md (ground rules; to be authored) ├── Dockerfile ├── docker-compose.yml ├── pyproject.toml ├── alembic.ini ├── migrations/ ├── src/ypms/ │ ├── __init__.py │ ├── main.py (FastAPI app + router registration) │ ├── framework/ (vendored from OWC + YPMS additions) │ ├── security/ (JWT, RBAC decorators, password hashing) │ ├── seed/ (permissions matrix, eligibility rules, program defs, notice text) │ ├── youth/ (Registrant entity) │ ├── parent/ (Guardian entity + consent lifecycle) │ ├── host/ (Host, Worksite, Position, JobPosting) │ ├── program/ (Program, Cohort, EligibilityRule) │ ├── application/ (Application, EligibilityDecision, Certification) │ ├── enrollment/ │ ├── orientation/ (LMS integration adapter) │ ├── timekeeping/ (TimeEntry, Timesheet) │ ├── payroll/ (Payroll vendor adapter, PayrollBatch, TaxDocument) │ ├── integrations/ │ │ ├── ssa.py (SSA BSO SSN verify) │ │ ├── osse.py (CSV ingest, retention) │ │ ├── cfsa.py (CSV ingest, retention, role-scoped display) │ │ ├── dc_mar.py (Address verify) │ │ ├── lms.py │ │ └── payroll_vendor.py │ ├── reporting/ (Native reports replacing MicroStrategy) │ ├── notifications/ (Email, SMS via DOES-approved provider) │ └── admin/ (User mgmt, role mgmt, workflow rules, broadcasts) ├── tests/ │ ├── framework/ │ ├── security/ │ ├── integrations/ (with recorded fixtures) │ └── ... ├── client/ (public-portal richer client if we go that way — Phase 3 decision) └── docs/ ├── brs-coverage.md ├── nist-mapping.md ├── data-flow-diagrams/ └── compliance/ ├── ssp/ ├── controls/ (one file per NIST control, OWC pattern) ├── poam/ └── evidence/ ``` --- ## 6. Data architecture ### 6.1 Core entities (conceptual) | Entity | Purpose | Notable fields | |---|---|---| | `Registrant` (Youth) | Person applying to and participating in OYP programs | name, DOB, SSN (encrypted, masked in display), address, demographics, contact, immigration-doc references, banking (encrypted), tax elections, minor-flag | | `Guardian` | Parent/guardian of an under-18 registrant | name, contact, relationship, consent records | | `ConsentRecord` | Consent artifacts | consent scope, notice-version, timestamp, IP, granter identity, revocation-timestamp | | `Host` | Employer participating as a placement site | name, business address, tax ID, contact, verified-flag | | `HostContact` | Named individual at a host | name, title, contact, role at host | | `Worksite` | Physical location where placements happen | address, host, supervisor, safety attestation | | `Position` | Placement opening at a worksite | title, description, hours, dates, hourly rate, program eligibility | | `JobPosting` | Public-facing view of a Position | derived from Position; public-fields only | | `Program` | OYP program definition | name (MBSYEP, HSIP, MBYLI, CRESP, ...), year, dates, eligibility rule set | | `Cohort` | Named subset within a Program (e.g. SIT, SYP, Global) | Program, name, dates | | `EligibilityRule` | Rule row driving auto-eligibility (data-driven, from source RFP Appendix D) | rule id, predicate, effect (`eligible`, `ineligible`, `categorical`), applies-to-program | | `Application` | A registrant's application to a program | Registrant, Program, cohort, status, submitted-at, decision, decision-actor | | `EligibilityDecision` | Outcome of running rules on an application | decisions, rule-hits, categorical-eligibility flag | | `Certification` | Staff certification event on an application | actor, attestation, evidence documents | | `Orientation` | Orientation completion tracking (via LMS) | Registrant, program, completion-status, completion-date | | `Enrollment` | Registrant enrolled in a program placement | Registrant, Program, Cohort, Position, dates, status | | `TimeEntry` | Single hours record | Enrollment, date, hours, submitted-by, approved-by | | `Timesheet` | Weekly/period rollup of TimeEntry rows | Enrollment, period, entries, approval status | | `PayrollBatch` | Batch sent to payroll vendor | period, timesheets, sent-at, vendor batch id, reconciliation state | | `TaxDocument` | W-2 / 1099 reference | Registrant, year, doc-type, source (vendor-generated), retrieval-uri | | `TransportationVoucher` | Voucher tracking | Registrant, Program, amount, issued-at, redeemed-at | | `Attachment` | Uploaded document | owner-entity, filename, mime, size, virus-scan-status, encrypted-uri | | `Note` | Free-form note associated with an entity | subject, body, actor, entity, timestamp | | `IntegrationEvent` | Every outbound / inbound integration call | integration, direction, request-summary, response-summary, status, retry-count, timestamps | | `Notification` | Every notification sent | recipient, channel, template, sent-at, delivery-status | | `EducationRecord` | OSSE-ingested enrollment record (FERPA-scoped) | Registrant, school, grade, status, ingest-batch | | `FosterIndicator` | CFSA-ingested foster/ward flag (Title 4-scoped) | Registrant, status, ingest-batch, current-only | | `AuditRow` | Framework-emitted audit | actor, action, entity, entity-id, before, after, timestamp, request-id | | `Role`, `Permission`, `RoleAssignment` | Data-driven RBAC | seed-loaded | | `User`, `UserSession` | Authenticated principals across all populations | username, IdP, assurance level, mfa-verified | ### 6.2 Relationship highlights - `Registrant` is the spine. Everything user-facing hangs off it. - `Guardian ↔ Registrant` is many-to-many but constrained: at most one `active` guardian per under-18 registrant at a time (guardianship handoffs require staff action and a full consent chain replay). - `Application` is per `(Registrant, Program, Program.year)` — a youth applying to MBSYEP 2027 and HSIP 2027 has two Applications. - `EducationRecord` and `FosterIndicator` are **join-only visible to role-scoped users**. Neither ever appears on host- or supervisor-facing views. This is an application-layer enforcement, not just RBAC — the API omits the fields from responses that lack the required scope, even if the caller has read on `Registrant`. ### 6.3 Encryption strategy - **Postgres TDE** covers the whole database at rest (Azure-managed). - **App-layer encryption** for the most sensitive columns: SSN, banking account number, tax ID, immigration-doc image references. Encrypted with keys resolved from Key Vault at request time; decrypted only in memory during processing; never logged. This is above-TDE defense-in-depth for the columns most attractive to an insider or a compromised-app scenario. - **Blob encryption** for uploaded documents: envelope-encrypted with per-blob DEKs wrapped by a KV-managed KEK. - **Display masking** for SSN (source RFP `C.5.7.50`) and banking (last-4 only). Masking is applied in the serializer, not just the UI, so API consumers see masked values too. ### 6.4 Retention automation Retention rules from the PIA §9 are encoded in the framework: - Each entity declares a `retention_policy` on its model. - A nightly job scans for records past their retention envelope and executes the policy: soft-delete + tombstone, hard-delete (with audit), or archive-and-scrub-PII depending on the class. - CFSA-ingested `FosterIndicator` rows purge older-than-two-years annually as a distinct job with a lower error tolerance. - OSSE-ingested `EducationRecord` rows purge older-than-two-years annually. - Banking data purges at end of active-payroll status. --- ## 7. Integration architecture Each integration follows the same shape: adapter module, config in Key Vault, IntegrationEvent audit on every call, retry with exponential backoff, circuit-breaker, kill-switch env flag. ### 7.1 SSA (Business Services Online) — SSN verify - Outbound API to SSA BSO with SSN and identity elements. - Called during eligibility determination (post-application, pre-enrollment). - Governed by the SSA Electronic Information Exchange agreement — `YPMS-POAM-022` (renewal). - Response cached only for the current eligibility decision — not retained after the decision emits. - Kill-switch: if SSA endpoint is unreachable, applications queue with a `pending_ssa_verify` status; staff can retry manually or approve with alternate documentation per policy. ### 7.2 OSSE — school enrollment CSV - Inbound secure file transfer (Azure Storage with a private endpoint, or a DOES-hosted SFTP surface with strict IP allowlist). - Ingest process: fetch → validate schema → dedupe by Registrant → write `EducationRecord` rows → archive source file. - Every ingest run creates a batch id; every produced row references the batch id for provenance. - Governed by the DOES–OSSE data-sharing agreement — `YPMS-POAM-020` (renewal). - FERPA overlay: role-scoped access enforced at the application layer, not just RBAC. ### 7.3 CFSA — foster / ward-of-state CSV - Same shape as OSSE. Different agreement (`YPMS-POAM-021`), different retention (annual purge of older-than-two-years), stricter role scoping (support-service triage only; never displayed to hosts). ### 7.4 DC MAR — address verify - Outbound API for address validation and canonicalization. - Called at application submission and at every address change. - Fallback: if DC MAR is unreachable, user-entered address is accepted and flagged for staff review. ### 7.5 Third-party payroll vendor — hours + payments + tax forms - Bidirectional API. Vendor selection outstanding (`YPMS-POAM-023`). - Outbound: `PayrollBatch` sends approved `Timesheet` rows. - Inbound: vendor confirms processing and returns `TaxDocument` references. - Reconciliation is a distinct process: for every batch sent, we wait for confirmation within N business days; unreconciled batches raise alerts. - Vendor DPA governs breach notification and PII handling. ### 7.6 Third-party LMS — orientation completion - Bidirectional API. Vendor TBD. - Outbound: enrollment notification (Registrant + Program). - Inbound: completion status flag only. No LMS-owned PII flows back into YPMS. ### 7.7 Entra ID — staff identity - OIDC federation. Staff authenticate via DOES Entra ID tenant with conditional-access MFA (`YPMS-POAM-030`). - No local staff passwords in production. Local password auth is used only in dev/test environments and for a small number of pre-Entra bootstrap accounts. ### 7.8 Youth / host external identity - Options tracked in `YPMS-POAM-050`: 1. Local username / password for youth and host self-registration, plus Login.gov federation option. 2. Social-media OAuth for browsing / pre-application only; step up to Login.gov before payroll actions. - The `iam.assurance_level` claim on the JWT gates payroll-touching actions regardless of which IdP issued the initial token. ### 7.9 Sentinel — SIEM sink - Log stream via Azure Diagnostic Settings + custom Log Analytics tables where needed. - DOES OIT ships application audit events; DC OCTO SOC correlates and alerts. - Log-schema agreement with OCTO SOC is `YPMS-POAM-043` M1. ### 7.10 Notifications — email + SMS - Email via a DOES-approved provider (likely the same one OWC uses; confirm at Phase 1). - SMS via a DOES-approved provider. - Both providers see minimal PII in payloads — never SSN or banking; often just a name and a secure URL. - Delivery failures raise staff alerts for time-sensitive notices. --- ## 8. Security architecture Full detail is in the [SSP](SYSTEM_SECURITY_PLAN.md) and the [control mapping](NIST_800_53_CONTROL_MAPPING.md). Architecture-level highlights: ### 8.1 Authentication - **Staff**: Entra ID + conditional-access MFA (`YPMS-POAM-030`). - **Youth / host**: external IdP or local account; `iam.assurance_level` gates high-risk actions. - **Under-18 youth**: additional parental co-consent workflow (see §8.5). - **Local passwords** (dev/test only): pbkdf2_sha256 per OWC choice (§4.6). ### 8.2 Authorization - Data-driven RBAC via the vendored OWC framework (§4.2). - `iam.assurance_level` gates a small set of high-risk actions above the base RBAC (payroll enrollment, banking change, immigration-doc access, foster-status view). - Row-level access ("only your caseload") is a Phase 1+ enhancement — not in Phase 0. Same trajectory as OWC. ### 8.3 Transport and at-rest - TLS 1.2+ (target 1.3) on all endpoints, public and private (`YPMS-POAM-031`). - Certificates issued through Azure Front Door / App Gateway (managed cert renewal). - Postgres TDE + app-layer encryption for the highest-sensitivity columns (§6.3). - Blob envelope encryption with KV-managed KEKs. - All keys in Azure Key Vault (Gov / HSM), FIPS 140-3 (`YPMS-POAM-032`). ### 8.4 Boundary Three zones matching source RFP `C.5.7.23`: - **Untrusted**: public internet, mobile clients, unauthenticated portal. - **Transition**: WAF + App Gateway; TLS termination; rate limiting; authentication. - **Trusted**: FastAPI app, Postgres, Key Vault, blob storage — private endpoints only, no public ingress. ### 8.5 Consent and parental co-consent Because parental co-consent for under-18 registrants is a first-class flow (PIA §6.3), it's a real subsystem, not a checkbox on a form: - Under-18 registrant provides parent contact. - YPMS sends a secure link to the parent; parent creates account or authenticates. - Parent explicit-grants consent per notice-version; a `ConsentRecord` row is created. - The registrant's `Application.eligibility_gate` requires an unrevoked active `ConsentRecord` for the notice-version in force. - Parent portal exposes view / update / export / revoke on the child's data. - Consent revocation triggers the individual-rights workflow (PIA §7). ### 8.6 Audit and monitoring - Framework-emitted audit on every state change (§4.3). - PII-read audit on sensitive-record reads (`@audit_read`; source RFP `C.5.6.9` #5). - All audit rows tagged with request id, actor, target, action, before/after. - Audit stream to Sentinel (`YPMS-POAM-043`). - Monthly privileged-user audit review by ISSO (that's Chris, dual-hatting per SSP §5). --- ## 9. Deployment architecture ### 9.1 Environments (per source RFP `C.5.5.1`, minimum two lower + prod) | Env | Purpose | Data | Access | |---|---|---|---| | **dev** | Developer sandbox, feature branches | Synthetic only | DOES OIT engineers | | **test** | Integration testing, UAT | Synthetic + optionally anonymized subsets | DOES OIT + OYP UAT users | | **prod** | Production | Real PII | Named production users; JIT admin | Dev and test are sanitized of production PII. Any refresh of dev/test from prod runs through a scrub-and-synthesize pipeline that overwrites SSN, banking, addresses, and any FERPA/CFSA-ingested rows with synthetic equivalents. ### 9.2 Azure Government topology ``` Azure Front Door (WAF, DDoS, TLS termination, rate limiting) │ ▼ App Gateway (private endpoint termination for internal portal) │ ▼ ┌──────────────────────────────────────────────────────────┐ │ Resource group per environment │ │ │ │ ┌───────────────────┐ ┌────────────────────────────┐ │ │ │ App Service / │───►│ Postgres flexible server │ │ │ │ Container Apps │ │ (private endpoint) │ │ │ │ (FastAPI) │ └────────────────────────────┘ │ │ └───────────────────┘ │ │ │ │ │ ├──► Azure Blob (private endpoint) │ │ │ │ │ ├──► Azure Key Vault (Gov / HSM) │ │ │ │ │ └──► Azure Diagnostic Settings ──► Log Analytics│ │ │ │ │ ▼ │ │ Sentinel (OCTO) │ └──────────────────────────────────────────────────────────┘ ``` Compute choice — App Service vs. Container Apps vs. AKS — is a Phase 0 sub-decision. Default is **Azure App Service for Containers** unless OWC has already picked something different, in which case we match. (OWC currently runs in Docker; we'll confirm OWC's Azure Gov deployment target at Phase 0 kickoff.) ### 9.3 IaC and CI/CD - **IaC**: Bicep (matches Azure-native tooling and OCTO templates). Tracked in `infra/`. - **CI**: Gitea Actions on `gitea.doesworks.net` (decided 2026-10-01 — Gitea is the DOES CI host). Workflow at `.gitea/workflows/ci.yml`. - **Pipeline gates**: unit tests, SAST (ruff + a security-focused linter like bandit), SCA (pip-audit or safety), secret scanning (gitleaks), SBOM (CycloneDX; `YPMS-POAM-042`). - **Deploy**: environment promotion (dev → test → prod) with approvals for prod. - **Migrations**: Alembic auto-applied via a migration container at deploy time; migrations are immutable once merged (OWC hard rule). ### 9.4 Failover and backup - Azure paired region for backup storage (inherited FedRAMP-High geo-redundancy). - Application-tier redundancy via App Service instance count (min 2 in prod). - Postgres flexible-server high availability with zone-redundant standby in prod. - **Failover-during-patching** capability required by source RFP `C.5.6.13` — achieved through blue/green or rolling deploy against the load balancer. - Restore drill annually (`YPMS-POAM-044`). --- ## 10. Cross-cutting concerns ### 10.1 Logging - Structured JSON (`ypms.log.record` shape); one line per event. - Request ID propagated end-to-end. - Sensitive fields (SSN, banking, passwords, tokens) never logged, ever. A `sanitize_for_log` helper strips them before emit. - Logs go to stdout (Azure App Service picks them up), thence to Log Analytics, thence to Sentinel. ### 10.2 Error handling - Errors are logged with request id and category (source RFP `C.5.6.9` #3, `C.5.7.43`). - User-facing error messages are non-exploitable (no stack traces, no DB messages, no version strings). - Errors on integration calls raise circuit-breaker warnings after N consecutive failures. - Errors on payroll batches are staff-alertable; payroll cannot silently drop hours. ### 10.3 Notifications Every consequential state change has a corresponding notification design: - Application submitted → confirmation to registrant + guardian. - Eligibility decided → notice to registrant + guardian. - Certification approved / rejected → notice. - Enrollment confirmed → welcome notice. - Timesheet approved → notice. - Payroll processed → notice with masked amount and last-4 of account. - Password change → notice via preferred channel (source RFP `C.5.7.14`). ### 10.4 Internationalization and accessibility - DC Language Access Act obligations require multi-language delivery. Notices and portal content templated with a language key; translation review process is an operational activity, not a code activity. (`YPMS-POAM-081`) - WCAG 2.1 AA target: automated checks in CI (`axe-core` or equivalent), manual audit pre-Go-Live (`YPMS-POAM-082`). - Keyboard navigation and alternative-keyboard shortcuts per source RFP `C.5.21.2`, `C.5.21.24`. ### 10.5 Peak-window scaling - MBSYEP application window is a hard peak. Auto-scaling on App Service configured with pre-warm before the window opens. - Anti-bot / rate-limit rules on public endpoints tuned for the window (`YPMS-POAM-101`). - CDN / Front Door caches static content aggressively during the window. --- ## 11. Non-functional targets | Property | Target | Source | |---|---|---| | Availability (rolling 30 days) | 99.5% | Source RFP `C.5.6.17` | | Response time (webserver request → response) | ≤ 1 second | Source RFP `C.5.6.27` | | Concurrent-user scaling | 50%/year growth headroom | Source RFP `C.5.6.20` | | Browser support | Latest Chrome/Edge/Safari/Opera (source RFP `C.5.6.11`); no IE despite the source RFP mentioning it (deprecated) | Tailoring | | Mobile support | Mobile-first for public portal; portable-device support for internal (`C.5.21.1`) | Source RFP | | Backup cadence | Daily (`C.5.8.21`) | Source RFP | | Restore RTO | Within SLA envelope; ≤ 4 hours target for full restore | Derived | | RPO | ≤ 1 hour (point-in-time restore) | Derived | | Peak-load capacity | Sustained MBSYEP application window + 3× spike headroom | Program-owner input; validate at Phase-8 load-test | --- ## 12. Delivery phases OWC's `PHASE{N}_PLAN.md` pattern is the template. The current phase plan is authoritative over older ones on conflict. | Phase | Scope | Illustrative deliverables | |---|---|---| | **Phase 0 — Foundation** | Framework vendor-in, auth (local + JWT), RBAC, audit, base CRUD, dev/test envs, CI/CD, migration harness | `main.py`, `framework/`, `security/`, first migration, one working entity (Registrant), 501 stubs on all others | | **Phase 1 — Applications and identity** | Registrant + Guardian + ConsentRecord, application intake, parental co-consent workflow, Entra ID for staff | Public registration; staff portal shell; consent lifecycle | | **Phase 2 — Eligibility** | EligibilityRule seed from source RFP Appendix D, eligibility engine, DC MAR + SSA integration | Applications flow through auto-eligibility to a decision | | **Phase 3 — Public portal decision** | Vite/React vs HTMX-only for youth/host portal; whichever chosen, complete public UX baseline | Live public portal at usable state | | **Phase 4 — Host + worksite + posting** | Host registration, Worksite, Position, JobPosting, matching | Hosts can register and post; youth can browse | | **Phase 5 — OSSE + CFSA + LMS integrations** | CSV ingest pipelines, retention automation, role-scoped surfaces | Data-sharing agreements complete; ingest live | | **Phase 6 — Enrollment + orientation** | Youth-program matching → enrollment; LMS orientation tracking | End-to-end from application to enrollment | | **Phase 7 — Timekeeping + payroll** | Timesheets, payroll batches, payroll-vendor integration, tax-doc reference | End-to-end from enrollment to payment | | **Phase 8 — Reporting + admin** | Native reports replacing MicroStrategy; workflow-rule management; broadcast notifications | Feature-complete for MBSYEP season | | **Phase 9 — Hardening** | Load test, pentest, SCA remediation, accessibility, Language Access, docs | ATO submission package | | **Phase 10 — ATO + Go-Live** | Independent Control Assessment, AO decision, cutover | Production | --- ## 13. Open questions Mirrored from SSP Appendix D and control-mapping §5 to avoid drift. Each has a POA&M id. 1. **"Level 3 security"** (`YPMS-POAM-051`). Interim posture: treat as NIST 800-63 IAL3/AAL3 — the more demanding reading. 2. **Youth identity model** (`YPMS-POAM-050`, `YPMS-POAM-033`). Recommendation: social-media login for browsing only; step-up to IAL2 (Login.gov) before payroll actions; parental co-auth for under-18. 3. **PCI DSS scope** (`YPMS-POAM-052`). Expected out of scope (payroll is ACH via vendor). Confirm at Phase 7 payroll-vendor selection. 4. **Health-record CSV path** (`YPMS-POAM-053`). Recommendation: defer to an emergency-scoped extension with its own PIA addendum; do not include in steady-state boundary. 5. **Retention** (`YPMS-POAM-054`). Interim retention posture is in PIA §9; formalize with DOES records officer before Phase 8. --- ## 14. What Phase 0 deliberately does not do Following the OWC pattern of stating non-goals up front: - **No frontend beyond an admin shell.** Public portal UX is Phase 1+. - **No domain workflow.** Eligibility engine, matching, timekeeping, payroll integration are Phase 2+. - **No integrations.** SSA, OSSE, CFSA, DC MAR, LMS, payroll adapters are Phase 5+ (SSA earlier if eligibility is on the critical path). - **No row-level RBAC.** Phase 0 is "can role X do action Y on entity type Z?" Row-level ("your caseload only") comes with the eligibility phase. - **No NIST-control code beyond the technical subset.** Policy/org controls (PM, PL, PS, AT families) are policy work, tracked in POA&M, not code. - **No production data.** Dev and test are synthetic-only until sanitization pipelines and data-sharing agreements are complete. --- ## 15. Alignment with OWC — what we borrow directly To keep two systems maintainable by one team, we lean hard on OWC's patterns. Everything below is copied or adapted from OWC unless YPMS's problem shape genuinely requires deviation. - Stack (Python 3.12 / FastAPI / SQLAlchemy 2.x / Alembic / Postgres / SQLite-in-tests). - Framework directory structure and generic-CRUD pattern. - Data-driven RBAC with a super-role bypass. - Audit-in-framework rule; every state change and every stub emits. - Soft-delete + version-history mixins. - JWT + OIDC-shaped auth. - pbkdf2_sha256 for local passwords (until argon2id migration). - Postgres in dev, SQLite in tests. - `PHASE{N}_PLAN.md` cadence and phase-current-wins rule. - CONTRIBUTING.md hard rules (migrations immutable, no `--no-verify`, tests required on framework, no domain logic in framework/security). - `docs/compliance/{ssp,controls,poam,evidence}/` layout. - The DCYWP `NIST_800_53_CONTROL_MAPPING.md` DCYWP-style ✅/🟡/❌/🔵/📋 status vocabulary once code starts landing (control mapping is at 📝 today because there's no code yet). Where YPMS diverges: - **Four-population identity model** (staff, youth, parent, host) is more elaborate than OWC's mostly-staff model. - **`iam.assurance_level` claim** to gate payroll-touching actions is YPMS-specific. - **Parental co-consent lifecycle** is a real subsystem, not a checkbox. - **PII-read audit decorator** on sensitive-record reads. - **Retention automation** is more elaborate because of FERPA + CFSA + banking timing rules. - **CSV ingest pipelines** (OSSE, CFSA) are a subsystem OWC doesn't have. --- **End of draft v0.1.**