Workflow moved to .gitea/workflows/ci.yml (Gitea is the CI host). Every
gate now passes locally, step-for-step; green on main is pending an
act_runner on gitea.doesworks.net (runs #1-#5 are queued "Waiting").
Gate fixes found by actually running them:
- pip-audit: `--disable-pip` without `-r` was a usage error, so the old
step could never pass. Now `pip-audit --strict .` audits the runtime
dependency set from pyproject.
- pip-audit then flagged ecdsa (PYSEC-2026-1325, no fix), a hard dep of
python-jose. Swapped to PyJWT (HS256 only; 3-line change in
auth/security.py). Flag to OWC.
- PyJWT warned the test JWT secret was 11 bytes. JWT_SECRET now requires
>= 32 bytes (RFC 7518 s3.2) at settings load; dev/compose/test defaults
updated. Test-only value carries an inline gitleaks:allow.
- bandit B101: assert in retention._count_past replaced with a raise.
- gitleaks: gitleaks-action needs a GitHub token/license; replaced with
the pinned 8.30.1 binary, sha256-verified, scanning full history.
Added:
- SBOM: CycloneDX 1.6 from a clean runtime-only venv (39 components),
uploaded as a CI artifact. YPMS-POAM-042.
- cyclonedx-bom in dev extras; CI outputs gitignored.
158 tests passing, 95.83% coverage; ruff, mypy, bandit, pip-audit,
gitleaks clean.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Data-driven RBAC. The matrix is the source of truth for who can do what;
runtime looks it up via security.rbac.can() (already in place from 0.2).
Refinements land as seed updates, not code changes.
Roles seeded (src/ypms/seed/roles.py):
- DOES_SYSTEM_ADMIN Super-role. Bypasses via ADMIN_ROLE.
- OYP_STAFF General OYP program staff.
- ELIGIBILITY_EXAMINER Certified eligibility staff — only non-admin
role with access to CFSA foster/ward and
OSSE FERPA records.
- PAYROLL_ADMIN Approves payroll batches; banking access.
- REPORTING_ANALYST Read-only across non-sensitive entities.
- HOST_USER External portal: host/employer.
- PARENT_USER External portal: parent/guardian of under-18.
- YOUTH_USER External portal: registrant.
Matrix (src/ypms/seed/permissions_matrix.json):
- Role-first grants map (nested role → entity → actions[]) rather than
OWC's action-vs-entity cell matrix. Reason in permissions.py header:
YPMS's 8 roles have very different scopes.
- 30 entities from ARCH §6.1 declared; 8 canonical actions.
- DOES_SYSTEM_ADMIN deliberately absent from grants — bypasses via the
super-role.
- Row-level scoping ("HOST_USER edits only THEIR host") is service-layer,
not RBAC-layer; Phase 0 is action×entity only, per ARCHITECTURE.md §4.2.
Loader (src/ypms/seed/permissions.py):
- Validates every referenced role / entity / action against the top-level
declared sets — misspelled cells surface at seed time, not later.
- Idempotent: existing (role_id, entity, action) rows are skipped.
- Removals aren't propagated automatically — that's a Phase 8+ admin-UI
responsibility per header note.
Runner (src/ypms/seed/run.py):
- `python -m ypms.seed.run` — idempotent runner. Seeds:
1. Roles from ROLES (updates description drift)
2. Bootstrap admin `chris` with DOES_SYSTEM_ADMIN role
(password from SEED_ADMIN_PASSWORD env, default
change-me-immediately)
3. Permissions matrix
- docker-compose command now chains this after `alembic upgrade head`
so a fresh container is usable.
Tests (53 passing, 92.20% coverage — up from 43 / 93.47%):
- tests/seed/test_seed.py 10 tests covering:
* seed_roles idempotency + description-drift update
* seed_bootstrap_admin creates + is idempotent
* seed_permissions inserts + is idempotent
* matrix declared actions/entities are self-consistent
* matrix grants don't reference unseeded roles
* ADMIN_ROLE NOT in grants (must bypass, not have rows)
* unknown role in matrix raises at seed time
* end-to-end can() decisions per role (~25 assertions):
- OYP_STAFF sees registrants but not FERPA/CFSA/PayrollBatch
- ELIGIBILITY_EXAMINER sees FERPA/CFSA but not banking
- PAYROLL_ADMIN handles PayrollBatch but not FERPA/CFSA
- REPORTING_ANALYST is strictly read-only
- HOST_USER / PARENT_USER / YOUTH_USER can't touch admin surfaces
- DOES_SYSTEM_ADMIN bypasses even sensitive entities
Advances YPMS-POAM-040. Unblocks milestone 0.5 (Registrant vertical slice)
and 0.6 (501-stub routers for the other entities in ARCH §6.1).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>