Files
YPMS/ARCHITECTURE.md
T
Chris TonjesandClaude Opus 5.5 170280ed38
ci / test (push) Has been cancelled
Record milestone 0.8 status: pipeline complete, green-on-main pending Gitea runner
PHASE0_PLAN 0.8 marked in progress with the remaining exit check;
ARCHITECTURE 9.3 and the plan's CI-provider question resolved to Gitea
Actions; YPMS-POAM-042 to in progress; status handoff gets 0.8 details,
runner-registration steps, local gate commands, and the accumulated
upstream-to-OWC list.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 10:57:53 -04:00

48 KiB
Raw Blame History

Architecture — YPMS Phase 0

Document version: 0.1 (Draft) Date: 2026-09-30 Author: Chris Tonjes, DOES CIO Companion to: CLAUDE.md, OVERVIEW.md, SYSTEM_SECURITY_PLAN.md, NIST_800_53_CONTROL_MAPPING.md, POAM.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 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 and the control mapping. 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.

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.