epic: worker program scope, enforced (#742)
## Summary canopy-web's program scope is **advisory**, not enforced. Three defect classes share one root: 1. **Fail-open** — `session::program_in_scope` and `audit::event_visible_to_programs` both treat an empty `primary_programs` claim as *see everything*. An IdP without the claim mapper silently grants jurisdiction-wide read+write. 2. **The gate checks a label, not a resource** — 13 production mutations pass caller-supplied `form.program` to the scope helper (income x3, members x3, assets x2, expenses x2, ievs x2, address x1). A SNAP-only worker posts `program=snap` and edits person facts on a TANF-only household, or resolves a SNAP IEVS discrepancy out of scope. `ensure_household_member` (#996) binds person -> household but never household -> scope. Three document actions (`accept_document`, `reject_document`, `scan_override_document`) have no gate at all. 3. **Reads are almost entirely unscoped** — case search, command palette, the application / appeals / notices / renewals / team-queue indexes, notice PDFs and document bytes by direct id, every dashboard panel, the audit page + CSV export + citation-by-id, and `/sse`, which broadcasts the full event envelope to every connected worker with no filter. This epic makes program scope a **required, enforced** attribute of worker identity across the whole authorization surface. The decision is recorded in ADR-044: a missing-or-empty `primary_programs` claim is a rejected login, with **no canopy-side override** — the deployment's override is the IdP claim mapper, which already owns role and identity. Driver: IRS Pub 1075 AC-6 (§4.1) least privilege. Also satisfies epic &62 phase B2. ## Plan [docs/modules/ROOT/pages/plans/program-scope-enforcement.adoc](https://gitlab.com/gadhs/application/eligibility/canopy/-/blob/main/docs/modules/ROOT/pages/plans/program-scope-enforcement.adoc) (committed in MR-1) ## Children - [x] #1515 feat(web): require the primary_programs claim at admission (weight: 5) — MR !1173, merge 8e0c073e - [x] #1516 fix(web): authorize mutations against the resource, not the posted program (weight: 8) — MR !1174, merge 71b26326 - [x] #1517 feat(notices): program filter on the notices list endpoint (weight: 2) — MR !1175, merge 9b1d92b8 - [x] #1518 fix(web): scope every protected read surface to the worker's programs (weight: 8) — MR !1179, merge 7e1aae90 - [x] #1519 feat(security): authoritative program metadata on audit rows + scoped audit reads (weight: 8) — MR !1180, merge 156abb1b - [x] #1520 fix(web): stop broadcasting full event envelopes on /sse (weight: 3) — MR !1181, merge b7bd337b Umbrella issue: #742 (closed by the final MR).
epic