Files
YPMS/PHASE0_STATUS.md
T
Chris TonjesandClaude Opus 4.7 185b5d2af3
ci / test (push) Has been cancelled
Add PHASE0_STATUS.md — handoff doc
Snapshot of where Phase 0 stands after milestones 0.1-0.4. Structured
as: TL;DR, current state, what's done per milestone, what's next, locked
decisions, open questions, repo state, how to run + verify, gotchas,
file map, POA&M state. Mirrors the DCYWP PHASE_2_STATUS pattern so
anyone (or a future session) can pick this up cold.

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

305 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Phase 0 — Handoff status
**Last updated:** 2026-09-30
**Phase:** 0 — Foundations (in flight)
**Owner:** Chris Tonjes, DOES CIO (System Owner / ISSO / AO)
**Companion docs:** [`PHASE0_PLAN.md`](PHASE0_PLAN.md) (scope + milestones), [`ARCHITECTURE.md`](ARCHITECTURE.md) (design), [`README.md`](README.md) (runnable quickstart)
---
## TL;DR
Phase 0 milestones **0.1 → 0.4** are shipped. **0.5 (Registrant vertical slice)** and **0.6 (501-stub routers for the remaining entities)** are next; **0.7–0.9** follow. All ATO artifacts (SSP, control mapping, POA&M, PIA) are drafted at v0.1–v0.3 and awaiting review signatures. The repo builds, tests are green (**53 passing, 92% coverage**), and Docker Compose boots. Not yet pushed to Gitea — auth on `gitea.doesworks.net` needs a local `git push` from the operator's terminal.
---
## Current state
| Dimension | Status |
|---|---|
| Phase | 0 (Foundations), milestones 0.1–0.4 shipped |
| Git branch | `main`, 9 commits ahead of `origin` (Gitea `gitea.doesworks.net/chris.tonjes/YPMS.git`) |
| Tests | **53 passing**, **92.20% coverage** (floor 80% enforced in CI) |
| Lint / types | `ruff` clean, `mypy` clean (32 files, strict) |
| Runtime | FastAPI + Postgres on Docker Compose; boots to `/healthz` + `/readyz`; `/auth/*` live |
| Migrations | 2 revisions applied (`0001_baseline`, `0002_framework_tables`) |
| Seed | 8 roles + permissions matrix + bootstrap admin (`chris` / `DOES_SYSTEM_ADMIN`) |
| ATO docs | SSP v0.3, control mapping v0.2, POA&M v0.1, PIA v0.1 — all drafts, awaiting signatures |
---
## What's done in Phase 0
Read [`PHASE0_PLAN.md`](PHASE0_PLAN.md) for the full scope tables. High points by milestone:
### 0.1 — Repo scaffold + Docker Compose ✅
- `pyproject.toml` (Python 3.12 / FastAPI / SQLAlchemy 2.x / Alembic / Postgres / dev extras: pytest, ruff, mypy, bandit, pip-audit)
- Multi-stage `Dockerfile`, `docker-compose.yml` (app on :8000, Postgres on :5433 to avoid clashing with OWC on :5432, Adminer on :8081)
- `alembic.ini` + empty baseline migration
- `src/ypms/main.py` — FastAPI factory with `/healthz` + `/readyz`
- `src/ypms/settings.py`, `logging.py` (structlog + `sanitize_for_log`), `db.py`, `middleware.py` (RequestId)
- `.github/workflows/ci.yml` — lint, types, tests+80% coverage, bandit, pip-audit, gitleaks
- `README.md`, `CONTRIBUTING.md`
### 0.2 — Framework vendor-in ✅
Vendored from OWC (`../owc-system/src/owc/`, 2026-04-20 snapshot) with `owc → ypms` renames:
- `framework/{base,bulk,crud,pagination,router,version_history}.py`
- `security/{models,rbac}.py` (Permission table + `can()`/`require_permission()` + `DOES_SYSTEM_ADMIN` super-role)
- `audit/{models,service}.py` (append-only AuditLog + `record()`)
- `users/models.py` (User/Role/UserRole with YPMS additions: `mfa_verified`, `assurance_level`; dropped OWC's `claimant_id`)
- `auth/deps.py` (stub — replaced in 0.3)
YPMS-specific addition: `framework/pii_audit.py` — `emit_pii_read()` for source RFP `C.5.6.9` #5 / NIST AU-14.
**Upstream bugfix in the vendored code:** `crud.py` pagination `next_cursor` was the peek-ahead row's id, causing silent row-drop on page boundaries. Fixed here; flagged for OWC.
Migration `0002_framework_tables.py` creates: `users`, `roles`, `user_roles`, `permissions`, `audit_log`, `version_history`.
### 0.3 — Auth + JWT + `iam.assurance_level` claim ✅
- `src/ypms/auth/security.py` — `hash_password`/`verify_password` (pbkdf2_sha256), `create_access_token` (with YPMS claims), `create_refresh_token`, `decode_token`
- `src/ypms/auth/rate_limit.py` — SlidingWindow; login limiter tuned to NIST AC-7 / source RFP `C.5.7.10`: 5 failed attempts / IP / 15 min → 429
- `src/ypms/auth/schemas.py` — LoginResponse, RefreshRequest/Response, ChangePasswordRequest, UserRead
- `src/ypms/auth/deps.py` — real `get_current_user`
- `src/ypms/auth/router.py` — `POST /auth/login`, `POST /auth/refresh`, `GET /auth/me`, `POST /auth/change-password`
- Wired into `main.py`
Access token carries: `sub`, `uid`, `roles`, `iam.assurance_level`, `mfa_verified`, `iat`, `exp`, `type`.
### 0.4 — RBAC seed loaded ✅
- 8 roles defined in `src/ypms/seed/roles.py`
- Permissions matrix at `src/ypms/seed/permissions_matrix.json` — role-first grants (30 entities × 8 actions)
- Loader at `src/ypms/seed/permissions.py` — validates every cell, refuses unknown roles at seed time, idempotent
- Runner at `src/ypms/seed/run.py` — `python -m ypms.seed.run`; auto-seeds `chris` as bootstrap admin
- Docker Compose command now: `alembic upgrade head && python -m ypms.seed.run && uvicorn …`
Guardrails baked in: `OYP_STAFF` can't see FERPA/CFSA (that's `ELIGIBILITY_EXAMINER`); `PAYROLL_ADMIN` can't either; `REPORTING_ANALYST` is strictly read-only; external portal roles never touch admin surfaces.
---
## What's next
### Immediate next milestone: 0.5 — Registrant vertical slice
Real model, not a stub. Proves the framework end-to-end with a domain entity that has:
- Masked SSN in serializer output (`XXX-XX-1234`)
- App-layer encrypted SSN + banking-placeholder columns
- `@audit_read`-equivalent PII-read audit on GET (via `emit_pii_read`)
- Full CRUD flowing through `CRUDService` with automatic audit + version-history rows
- Migration adding the `registrants` table
- Seed matrix already grants OYP_STAFF / ELIGIBILITY_EXAMINER / PAYROLL_ADMIN / etc. their scoped rights on it (from 0.4)
- Tests: happy paths, SSN masking verified end-to-end via TestClient, PII read logged
Coverage doc lands at `docs/coverage/registrant.md`.
### After 0.5
- **0.6** — 501-stub routers for the other 25 entities in ARCH §6.1. Each still fires RBAC + audit. Permission gaps surface at Phase 0 review.
- **0.7** — Retention scaffold (nightly job, no-op in Phase 0)
- **0.8** — CI pipeline hardening — SAST/SCA/SBOM configured in `.github/workflows/ci.yml` already; 0.8 is the discipline of green builds and any needed suppressions.
- **0.9** — Docs + acceptance (README expanded, `docs/nist-mapping.md` skeleton, "Phase 0 done" acceptance checklist)
Then Phase 1 (applications + identity + parental consent) starts.
---
## Key decisions locked in
Documented decisions that a future session must not re-open without a written reason:
1. **Stack: Python 3.12 / FastAPI / SQLAlchemy 2.x / Postgres** (OWC alignment). Decided 2026-09-30; recorded in [`CLAUDE.md`](CLAUDE.md).
2. **Hosting: Azure Government Cloud** (aligned with OWC and Employer 360).
3. **Chris Tonjes serves as System Owner + ISSO + AO** in Phase 0. Role concentration recorded and accepted in [`SYSTEM_SECURITY_PLAN.md`](SYSTEM_SECURITY_PLAN.md) §5 with rationale (DC agency structure + DC OCTO CISO / USDOL / OIG external oversight + independent SCA and pentest).
4. **Framework vendored from OWC** (not submoduled). Divergences carry written reasons; upstream re-vendoring is a deliberate act.
5. **`DOES_SYSTEM_ADMIN` is the super-role** and does **not** carry permission rows — it bypasses via `security.rbac.ADMIN_ROLE`.
6. **Row-level RBAC is Phase 2+** — Phase 0 is action-on-entity-type only.
7. **Every state-mutating route emits an audit row**; every stub still fires RBAC + audit; sensitive PII reads emit via `emit_pii_read`.
8. **Migrations are immutable once merged** — new revisions, never edits to older ones.
9. **SSL wording in source RFP is treated as TLS 1.2+ (target 1.3)**; FIPS 140-2 is treated as FIPS 140-3 forward alignment; `C.5.7.6` "shared secret answer" is treated as MFA-required. Documented in SSP Appendix D and control-mapping §5.
10. **Data-sharing agreements (OSSE, CFSA, SSA) are renewals of existing DOES agreements**, not new drafts (YPMS-POAM-020, -021, -022).
---
## Open questions
Each has a POA&M ID. See [`POAM.md`](POAM.md) for the full list of items. These are the ones actively awaiting a decision:
| # | Question | POA&M | Owner |
|---|---|---|---|
| 1 | What is "Level 3 security" in source RFP `C.5.7.24` — internal maturity vs. NIST 800-63 AAL/IAL 3? | `YPMS-POAM-051` | AO |
| 2 | Youth identity model for minors — social-media login scope; IAL2 proofing before payroll; parental co-auth for under-18? | `YPMS-POAM-050`, `YPMS-POAM-033` | AO + Privacy Officer + OYP |
| 3 | PCI DSS scope — expected out of scope since payroll is ACH via vendor; confirm at vendor selection | `YPMS-POAM-052` | OYP + procurement |
| 4 | Dormant COVID-era health-record CSV path — in-boundary steady state, or defer to emergency-scoped extension? | `YPMS-POAM-053` | AO + Privacy Officer |
| 5 | Retention policy for audit logs, case records, backups — align with DOES records schedule | `YPMS-POAM-054` | DOES records officer |
| 6 | SORN applicability determination | `YPMS-POAM-094` | Privacy Officer + OGC |
---
## Repo state
### Git
- Branch: `main`
- Remote: `origin` → `https://gitea.doesworks.net/chris.tonjes/YPMS.git`
- **9 commits ahead of `origin/main`** — push blocked on Gitea auth (interactive prompt). Push from a local terminal:
```bash
git -C /Users/christonjes/cc/ypms push -u origin main
```
- Commit history (newest first):
```
390e917 Mark milestone 0.4 complete in PHASE0_PLAN and POA&M
913c69c Phase 0 milestone 0.4: RBAC seed with 8 roles + permissions matrix
0050ad1 Mark milestone 0.3 complete in PHASE0_PLAN and POA&M
0501850 Phase 0 milestone 0.3: auth + JWT + iam.assurance_level claim
7379bb4 Mark milestone 0.2 complete in PHASE0_PLAN and POA&M
ba75658 Phase 0 milestone 0.2: vendor OWC framework + YPMS pii_audit
09d862b Mark milestone 0.1 complete in PHASE0_PLAN and POA&M
393631a Phase 0 milestone 0.1: repo scaffold + Docker Compose
8712e93 Initial YPMS planning and ATO artifacts
```
### Local git identity (repo-local, not global)
```
Chris Tonjes <ctonjes@gmail.com>
```
If commits should attribute to a DOES email instead, run:
```bash
git -C /Users/christonjes/cc/ypms config user.email <addr>
```
### Test / coverage
| Component | Coverage |
|---|---|
| `framework/` (base, bulk, crud, pagination, pii_audit, version_history) | 91–100% |
| `security/` (models, rbac) | 80–100% |
| `auth/` (security, deps, router, schemas, rate_limit) | 80–100% |
| `seed/` (roles, permissions, run) | 77–100% |
| Overall | **92.20%** |
`framework/router.py` is excluded from coverage until it has real routes (0.5) — coverage config in `pyproject.toml`.
---
## How to run locally
### Docker (canonical)
```bash
cd /Users/christonjes/cc/ypms
cp .env.example .env
docker compose up --build
```
- App: http://localhost:8000 (OpenAPI at `/docs`)
- Adminer: http://localhost:8081 (server `db`, user/pw/db all `ypms`)
- On first boot, seeding runs and `chris` is created with password `change-me-immediately` (or `SEED_ADMIN_PASSWORD` env). Change it via `POST /auth/change-password`.
### Native (no Docker)
```bash
cd /Users/christonjes/cc/ypms
python3 -m venv .venv && source .venv/bin/activate
pip install -e '.[dev]'
pytest
```
Tests use SQLite in-memory — no Postgres required.
### Log in from the CLI
```bash
curl -X POST http://localhost:8000/auth/login \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'username=chris&password=change-me-immediately'
```
Response includes `access_token`. Use it as `Authorization: Bearer <token>`.
---
## How to verify a milestone before committing
```bash
source .venv/bin/activate
ruff check src tests # style
mypy src # types (strict)
pytest -q --cov=src/ypms --cov-report=term --cov-fail-under=80
```
The GitHub Actions workflow at `.github/workflows/ci.yml` runs the same commands plus `bandit`, `pip-audit`, and `gitleaks`.
---
## Gotchas — things that would trip up someone new
1. **`app = create_app()` at the bottom of `src/ypms/main.py` runs at import time.** Tests must have env vars set before that module is imported. `tests/conftest.py` does this at *module* level (not in a fixture) — do not move it back into a fixture.
2. **SQLite in-memory tests need `StaticPool` + `check_same_thread=False`.** `TestClient` runs FastAPI handlers in a worker thread. The `db` fixture in `tests/conftest.py` configures the engine correctly — don't strip those options.
3. **`framework/router.py` deliberately does NOT use `from __future__ import annotations`.** FastAPI introspects route parameter types via `typing.get_type_hints`, which would fail to resolve closure-local TypeVars under PEP 563. See the module docstring.
4. **Two mypy overrides in `pyproject.toml`** — `framework/router.py` (FastAPI closure typevars) and `framework/crud.py` (generic-CRUD TypeVar attribute access). Same limitations OWC accepts.
5. **The vendored OWC framework had a pagination bug** — `next_cursor` was the peek-ahead row's id, silently dropping one row per page boundary. **Fixed in YPMS's copy** at `framework/crud.py`. If OWC is re-vendored, apply the fix again.
6. **`DOES_SYSTEM_ADMIN` does NOT appear in `permissions_matrix.json` under `grants`.** It bypasses via `security.rbac.ADMIN_ROLE`. Adding rows for it would be redundant and confuse the "who really has what" story.
7. **Row-level scoping is a Phase 2+ concern.** `HOST_USER` has `Host.Edit` in the matrix, which means the RBAC check passes. The service layer must ALSO filter by ownership before applying the edit. Don't rely on the matrix alone for row-level authorization.
8. **Alembic baseline (0001) is empty by design.** All schema lives in 0002+ so that "the framework tables" can be re-derived as an additive layer during future architecture reviews.
---
## File map
Read in this order if you're picking this up cold:
1. [`CLAUDE.md`](CLAUDE.md) — project router, distinctions, open questions, resolved decisions
2. [`OVERVIEW.md`](OVERVIEW.md) — programs served, users, integrations, timeline
3. [`ARCHITECTURE.md`](ARCHITECTURE.md) — design blueprint (§4 rationale, §5 layers, §6 data, §12 delivery phases)
4. [`PHASE0_PLAN.md`](PHASE0_PLAN.md) — phase scope, milestone exit criteria, controls-wired-in-Phase-0
5. **This file** — where we are + what's next + gotchas
6. [`README.md`](README.md) — runnable quickstart
7. [`CONTRIBUTING.md`](CONTRIBUTING.md) — hard rules
Then the ATO artifacts as needed:
- [`SYSTEM_SECURITY_PLAN.md`](SYSTEM_SECURITY_PLAN.md) v0.3 — SSP
- [`NIST_800_53_CONTROL_MAPPING.md`](NIST_800_53_CONTROL_MAPPING.md) v0.2 — control mapping
- [`PRIVACY_IMPACT_ASSESSMENT.md`](PRIVACY_IMPACT_ASSESSMENT.md) v0.1 — PIA
- [`POAM.md`](POAM.md) v0.1 — 62 items, 2 closed, 4 partially closed
- [`OLA_RESPONSE_2026-09-30.md`](OLA_RESPONSE_2026-09-30.md) — external Q&A snapshot
---
## POA&M state (as of 2026-09-30)
- **62 items total** — 2 fully closed, 4 partial closures under `YPMS-POAM-040`
- **6 open decisions** (see Open Questions table above)
- **Immediate remaining action items:**
- PIA route to Privacy Officer + OGC review
- Pull OSSE / CFSA / SSA existing agreements from OGC files to scope renewals
- Draft FIPS 199 categorization memo (`YPMS-POAM-001`) for AO signature
- Schedule DC OCTO security architecture review
---
## Signals to watch for as we approach milestone 0.5
- Any need to touch `framework/` or `security/` — resist unless it's a genuine framework improvement that would flow back to OWC. Domain entities live in their own directories.
- Any place where a Registrant handler is tempted to skip `emit_pii_read` — it isn't optional; the audit trail is load-bearing for the ATO story on minors' PII.
- Any temptation to store SSN in cleartext — SSN goes into the app-layer encrypted column; the serializer produces `XXX-XX-1234`; only the `ssn_last4` field is stored plain and only for search/display.
- Any premature row-level RBAC — that's Phase 2+. If a phase-0 handler needs "the caller's own row," it can filter by `actor.id`, but don't build a generic scope layer yet.
---
**End of handoff v0.1.**