ci / test (push) Has been cancelled
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>
663 lines
48 KiB
Markdown
663 lines
48 KiB
Markdown
# 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.**
|