Roadmap

On this page

Strategic Context

Canopy’s architecture is driven by four ADRs (see ADR-001 through ADR-004) and two deployment principles:

  1. SNAP-only UAT, September 2026 — The Month 1–6 critical path below (Foundation → SNAP UAT) is complete; the phases were built out ahead of schedule in Q1–Q2 2026. The work now standing between here and UAT is correctness + scale hardening, not net-new feature build-out — see Current Status.

  2. Any jurisdiction, any program — Per ADR-005 and ADR-006, any jurisdiction may deploy any program subset. Georgia DHS is the reference implementation; other jurisdictions require only ruleset files and configuration.

Current Status (2026-08-03)

The Month 1–6 SNAP-UAT critical path and the post-UAT program build-out (TANF, Medicaid/CHIP, CAPS, WIC, applicant portal) are substantially complete; those milestones below are historical records. Canopy is in a scale-hardening + correctness phase ahead of the September 2026 SNAP UAT.

Canonical work-stream state lives in GitLab, not in this file (per the memory-hygiene / plan-lifecycle conventions). This roadmap is the phase narrative; for the live open-issue list use glab issue list --state opened (or the GitLab epic boards). The two active engineering streams:

  • Epic &73 — Scale readiness (the "ankle-biter lane"). The burndown of the 2026-07-25 80-agent scale-readiness audit: indexes, pagination, streaming, job models, scheduler fences, backpressure, bulk contracts — making everything already built rock-solid at the ~3M-applicant Georgia / 10–15M single-deployment horizon before adding capability.

  • Epic &74 — Configurable logging + jurisdiction-owned redaction (ADR-041). Retires the special-cased FTI-audit hash chain (ADR-014) and the chain-v2 external-anchor machinery in favor of a general, configurable structured-logging facility with per-field, jurisdiction-owned redaction; external tamper-evidence + retention are delegated to the deployment’s logging facility (canopy = mechanism, deployment = policy). Sequenced Decision MR → redaction mechanism → audit-export channel → conformance gate → retention/legal-hold → retirement (last, gated). See ADR-041.

The near-term execution order across the open backlog is: (1) correctness + safety defects in shipped code, (2) test/CI-harness reliability (the pre-push battery is the merge gate), (3) the ADR-041 stream, (4) the remaining &73 scale hardening, then compliance/docs correctness. The large program-coverage cohort (per-program regulatory gaps across SNAP/TANF/CAPS/WIC/CHIP/Medicaid) and the worker-portal/Studio feature backlog are SME- and roadmap-driven and largely post-UAT.

SNAP UAT Critical Path

Milestone Map (Target: September 2026)

NOTE
Historical record — all six months are COMPLETE. The Month 1–6 build-out landed in Q1–Q2 2026 (see the per-month Status: COMPLETE markers and the Go/No-Go checkpoints); the map is retained for traceability. Current work is the scale-hardening + correctness phase in Current Status.
Month Target Key Deliverables Exit Criteria

1 (April 2026)

Foundation

Reference extensions, session middleware, persons model, rules engine, determination signing, security audit, application intake

canopy-persons serves person data; canopy-rules evaluates rulesets; signing infrastructure compiles; canopy-applications accepts intake

2 (May 2026)

SNAP Core + Compliance

SNAP eligibility, eligibility orchestrator, categorical eligibility + BBCE, ABAWD tracking

Application submitted to canopy-eligibility for SNAP produces a signed, verified, benefit-calculated determination

3 (June 2026)

Verification

SNAP IEVS integration (SWR, UI, SSA SDX/BENDEX)

SNAP eligibility verifies income against Georgia DOL wage records and SSA benefits; discrepancies flagged and tracked

4 (July 2026)

Notices + Appeals

Notice generation (NOA), fair hearings and appeals

10-day advance notices issued; fair hearing requests accepted; continued benefits tracked

5 (August 2026)

Enrollment + Renewals

SNAP EBT enrollment, SNAP certification periods, simplified reporting

Benefits issued via EBT (NoopAdapter for UAT); certification periods tracked; renewal notices generated

6 (September 2026)

Reporting + Portal + UAT

SNAP federal reporting (FNS-388, FNS-7176 QC), worker portal (canopy-web), UAT execution

All FNS-required SNAP test scenarios pass; FNS-7176 QC extract generates valid CSV; workers can process cases end-to-end

Month 1: Foundation

All four items can proceed in parallel. Reference extensions must complete in week 1 — it is a blocker for all downstream work.

Plan Delivers ADRs Dependencies

Reference Type Extensions

Missing enum variants (DeterminationStatus, IncomeType, AssetType, NoticeType, VerificationItem type), Determination struct updates

None — must complete in week 1

Session Middleware Wiring

tower-sessions-sqlx-store wired in canopy-web and canopy-portal; sessions table migration; 8-hour worker TTL, 30-minute applicant TTL

None — security gap, complete early

Person and Household Data Model

canopy-persons with full CRUD, person/household/income/asset/expense/address schema, MAGI tax-filing-status fields, event publishing

Reference extensions (week 1)

Rules Engine

canopy-rules with zen-engine, CRUD, evaluation API, audit trail, ruleset import from rulesets/georgia/, jurisdiction.toml loading

ADR-003, ADR-006

Reference extensions (week 1)

Determination Signing Infrastructure

canopy-signing crate (ECDSA P-256), key generation tooling, DeterminationSigner/DeterminationVerifier trait implementations

ADR-002

Reference extensions (week 1)

Security Audit Subscriber

canopy-security wildcard subscriber, event persistence to DB, audit log query API, breach detection rules, NIST control mapping

ADR-004

None — listens to events as other services come online

Application Intake

canopy-applications with single-streamlined-application (ACA §1413), expedited screening, authorized representative, event publishing

Persons model (for household_id foreign key)

Exit criteria: canopy-persons serves person data; canopy-rules evaluates rulesets; signing infrastructure compiles and passes key generation tests; canopy-security captures all published events; canopy-applications accepts SNAP applications.

Status: COMPLETE (March 2026). All exit criteria met:

  • canopy-persons: 13 API endpoints, 7-table schema, event publishing (MR !6)

  • canopy-rules: zen-engine integration, CRUD + evaluate, audit trail (MR !8)

  • canopy-signing: ECDSA P-256 keygen, signer, verifier (commit eda958f)

  • canopy-security: wildcard subscriber, audit persistence, NIST controls (MR !9)

  • canopy-applications: ACA 1413 intake, expedited screening, 8 endpoints (MR !10)

  • Session middleware: PostgreSQL-backed sessions in canopy-web and canopy-portal (MR !5)

  • Reference extensions: all enum variants, types.rs, Determination struct fields (MR !1)

Month 2: SNAP Core + Compliance

Plan Delivers ADRs Dependencies

SNAP Eligibility

canopy-snap with all mandatory income/deduction tests, asset test, signed determinations, IEVS data isolation

ADR-001, ADR-002, ADR-003, ADR-004

Month 1 complete

Eligibility Orchestrator

canopy-eligibility calling program services, verifying signatures, assembling combined results

ADR-002

Month 1 (signing), SNAP eligibility

SNAP Categorical Eligibility and BBCE

Standard categorical eligibility (TANF cash, SSI), BBCE (Georgia 130% FPL, asset test eliminated), student exclusion rules

ADR-003

SNAP eligibility

SNAP Income Deductions and Benefit Calculation

All 6 mandatory deductions (earned income, standard, dependent care, medical, excess shelter, homeless shelter), net income test, benefit calculation from max allotment minus 30% net income

ADR-003

SNAP eligibility

SNAP ABAWD Work Requirements

ABAWD identification, 80-hour/month tracking, 3-month time limit in 36-month window, discretionary exemptions, waiver area support

ADR-003

SNAP eligibility

SNAP Special Situations

Drug felon screening (21 USC 862a), fleeing felon/probation violator, striker pre-strike income preservation, jurisdiction-specific reinstatement options

ADR-003

SNAP eligibility

Exit criteria: An application for a household with wages at 125% FPL produces an approved SNAP determination with a calculated benefit amount. A household with wages at 135% FPL is denied at the gross income test. A TANF cash recipient is auto-approved via categorical eligibility. An ABAWD with no work activity in month 3 receives AbawdExceeded status.

Status: COMPLETE (March 2026).

Month 3: Verification

Plan Delivers ADRs Dependencies

SNAP IEVS Verification

State DOL SWR and UI adapters, SSA SDX/BENDEX adapters, IEVS discrepancy tracking, verification workflow in canopy-snap

ADR-004

Month 2 complete (SNAP eligibility, canopy-snap database schema)

SAVE Immigration Status Verification

SaveAdapter trait, NoopSaveAdapter, DHS SAVE step 1-3 verification, citizenship_verification table, alien eligibility rules (7 CFR 273.4)

Reference extensions (VerificationSource enum)

Exit criteria: SNAP eligibility evaluation runs IEVS match against (NoopAdapter returning deterministic test data for UAT); discrepancies between self-reported income and IEVS data generate VerificationItem entries on the determination; workers can view and resolve discrepancies.

Status: COMPLETE (March 2026). NoopIevsAdapter and NoopSaveAdapter implemented with deterministic test data. 24 verification tests.

Month 4: Notices and Appeals

Both can proceed in parallel.

Plan Delivers ADRs Dependencies

Notice Generation / Typst Architecture

canopy-notices with Typst PDF generation, Orchard-branded templates, 10-day advance notice enforcement, delivery queue, SNAP approval/denial/termination/ABAWD/expedited/expungement notices

Month 2 (determination events to subscribe to)

Fair Hearings and Appeals

canopy-appeals with appeal request intake, continued benefits logic, 90-day decision clock, timeline enforcement

Notice generation (for AppealAcknowledgment notice)

IPV and Administrative Disqualification

IPV case tracking, ADH workflow with 30-day notice and waiver, disqualification penalties (1yr/2yr/permanent), benefit recalculation, overpayment computation

Fair hearings (shared canopy-appeals service)

Exit criteria: A SNAP denial produces a denial NOA with regulatory basis cited. An adverse action generates a 10-day advance notice with the effective date pushed forward if needed. An appeal request filed before the adverse action effective date results in continued benefits. Notice history is visible in the worker portal.

Status: COMPLETE (March 2026). Typst notice generation (14 templates), fair hearings with continued benefits, IPV/ADH with penalty calculator. MRs !22-!25.

Month 5: Enrollment and Renewals

Both can proceed in parallel.

Plan Delivers ADRs Dependencies

SNAP Enrollment and EBT Issuance

canopy-enrollment with EBT issuance pipeline, benefit proration, NoopEbtAdapter, 12-month stale benefit expungement, Conduent adapter interface

Month 2 complete (determinations to trigger enrollment)

SNAP Renewals and Certification Periods

canopy-renewals with 12-month cert periods (24-month for elderly/disabled), renewal notice scheduling, interim contact management, simplified reporting model, redetermination flow

Month 1 (application intake for renewal reapplication)

Exit criteria: An approved SNAP determination creates an enrollment record with the correct initial issuance deadline. A certification period is created with the correct end date. Renewal notices are generated 75 days before certification end. An interim contact is scheduled at 6 months for standard households.

Status: COMPLETE (March 2026). EBT enrollment with proration and expungement job. Certification periods with interim contacts and renewal scheduling. MRs !26-!27.

Month 6: Reporting, Worker Portal, and UAT

Reporting and worker portal can proceed in parallel. UAT begins when both are complete.

Plan Delivers ADRs Dependencies

SNAP Federal Reporting

canopy-reporting with FNS-388 monthly aggregate report, FNS-7176 QC universe extract (50+ elements per case), cross-service data assembly

Months 1-5 complete (data to aggregate)

Worker Portal — SNAP Case Management

canopy-web with dashboard, case search, case detail, application processing, notice management, ABAWD tracking, renewal queue (Askama + htmx + Alpine.js)

Session middleware, months 1-5 (APIs to call)

UAT Execution

All FNS-required SNAP test scenarios; parallel run if legacy system available; FNS regional review documentation

All month 1-6 plans complete

Exit criteria — SNAP UAT ready:

  • Happy path: single-adult household with wages at 100% FPL → approved, benefit calculated, EBT issued

  • Denial path: household at 140% FPL → denied at gross income test, denial NOA generated

  • Expedited path: household with income < $150 and assets < $100 → expedited flag, 7-day processing

  • Categorical eligibility: TANF cash recipient → auto-approved, no income/asset test

  • ABAWD path: able-bodied adult, no work in 3 consecutive months → AbawdExceeded, termination NOA

  • Appeal path: adverse action appealed before effective date → continued benefits, hearing scheduled

  • QC extract: FNS-7176 pull produces valid CSV for all active certifications

  • Worker portal: caseworker can process application end-to-end without API calls

Post-SNAP-UAT Phases

Phase 3 — FTI Compliance and TANF (Month 7-9)

Introduces the most compliance-sensitive service. Pattern proven by SNAP; TANF adds FTI complexity and SSA CMA data.

Plan Delivers ADRs Dependencies

FTI Audit Logging (delivered; hash-chain mechanism superseded by ADR-041, epic &74)

Shared FTI audit logging pattern, IRS Pub 1075 audit log, auditor endpoints, canopy-tanf and canopy-medicaid integration. The FTI-specific hash chain (ADR-014) is being retired for ADR-041’s general logging + redaction facility; the Pub 1075 §4 audit obligation is met by the general audit-export channel + per-field redaction.

ADR-004, ADR-041

None — infrastructure pattern

TANF Eligibility

canopy-tanf with FTI, SSA SOLQ/BINDEX, 60-month time limit, 12-activity work requirements, IV-D referral, TANF data reporting (ACF-199)

ADR-001, ADR-002, ADR-003, ADR-004

SNAP UAT complete (pattern proven), FTI audit logging

TANF Federal Reporting

ACF-199 quarterly individual-level extract, ACF-196 expenditure aggregation, work participation rate (WPR) calculation (45 CFR 261.22)

TANF eligibility

Phase 4 — Medicaid/CHIP (Month 10-14)

The most complex eligibility logic. Medicaid and CHIP are co-deployed (ADR-001 notwithstanding, they share eligibility hierarchy logic).

Plan Delivers ADRs Dependencies

Medicaid/CHIP Eligibility

canopy-medicaid with MAGI/non-MAGI, CHIP, EE15 hierarchy, FTI + FDSH + HIPAA, ex parte renewal support, Georgia Pathways work requirement

ADR-001, ADR-002, ADR-003, ADR-004

FTI audit logging, SNAP UAT (FDSH pattern from IEVS work)

FFE Account Transfer

canopy-exchange with FfeAccountTransferAdapter implementation, inbound/outbound ACPT XML, No Wrong Door workflow (42 CFR 435.1200)

Medicaid eligibility (ACA §1413 transfer only applies when Medicaid is live)

Medicaid/CHIP Federal Reporting

T-MSIS monthly eligibility extract, CMS-64 quarterly expenditure aggregation, CMS-416 EPSDT screening rates

Medicaid eligibility

Phase 5 — CAPS and WIC (Month 15+)

Simpler compliance posture — no FTI, no FDSH, no CMA. CAPS (DECAL) and WIC (DPH) require cross-agency data flows specific to Georgia.

Plan Delivers ADRs Dependencies

CAPS Eligibility

canopy-caps with CCDF income test (50%/85% SMI), copayment tiers, activity requirements, provider authorization, 12-month authorization periods

ADR-001, ADR-002, ADR-003

Application intake, persons model, rules engine

WIC Eligibility

canopy-wic with 5 participant categories, 185% FPL income test, adjunctive eligibility (SNAP/Medicaid/TANF), nutritional risk assessment, food-package families per 7 CFR 246.10(e)(1)-(7), certification periods (7 CFR 246.7(g))

ADR-001, ADR-002, ADR-003

Application intake, persons model, SNAP eligibility (adjunctive eligibility query)

Phase 6 — Applicant Portal (Post-CAPS/WIC)

NOTE

Delivered ahead of the original phasing via Plan 3 — applicant-intake-and-verification (complete 2026-06-01: online application, document upload, determination status, notice inbox, reference-number auth, Fluent plumbing, WCAG 2.1 AA + axe e2e). Remaining follow-ups are tracked as issues, not plan rows: #666 (data-rich Home sections + the Spanish/es Fluent catalog) and #667 (tabs-shell get_tab composition).

Plan Delivers ADRs Dependencies

Applicant Portal

canopy-portal with Fluent i18n (English + Spanish), WCAG 2.1 AA, online application flow, document upload, determination status tracking, notice inbox

All program services (portal exposes all programs), session middleware

Modular Deployment Summary

Per ADR-005, any jurisdiction deploys a profile:

Profile Required services

snap-only

canopy-auth, canopy-persons, canopy-applications, canopy-rules, canopy-eligibility, canopy-snap, canopy-verification, canopy-notices, canopy-appeals, canopy-security, canopy-enrollment, canopy-renewals, canopy-reporting, canopy-web

tanf-only

As snap-only but canopy-tanf replaces canopy-snap

medicaid-chip

As snap-only plus canopy-exchange; canopy-medicaid replaces canopy-snap

caps-only

canopy-auth, canopy-persons, canopy-applications, canopy-rules, canopy-caps, canopy-eligibility, canopy-notices, canopy-appeals, canopy-security, canopy-enrollment, canopy-renewals, canopy-reporting, canopy-web

full

All 21 services (Georgia DHS production)

A new jurisdiction adds their configuration:

mkdir rulesets/my-state
cp rulesets/georgia/* rulesets/my-state/
# Edit jurisdiction.toml with state-specific thresholds and options
CANOPY_JURISDICTION=my-state COMPOSE_PROFILES=snap-only docker compose up -d

Dependency Graph

MONTH 1 (parallel): ✓ COMPLETE
  reference-extensions ─── ✓ MR !1
  session-middleware ───── ✓ MR !5
  persons ──────────────── ✓ MR !6
  rules ────────────────── ✓ MR !8
  signing ──────────────── ✓ commit eda958f
  security-audit ──────── ✓ MR !9
  application-intake ───── ✓ MR !10

MONTH 2 (sequential after month 1):
  snap-eligibility ─────── depends: persons, rules, signing
  snap-deduction-calc ──── depends: snap-eligibility
  eligibility-orchestrator  depends: signing, snap-eligibility
  snap-categorical ─────── depends: snap-eligibility (parallel with orchestrator)
  snap-abawd ──────────── depends: snap-eligibility (parallel with orchestrator)
  snap-special-situations ─ depends: snap-eligibility (parallel with orchestrator)

MONTH 3:
  snap-ievs-verification ── depends: snap-eligibility schema
  save-adapter ──────────── depends: reference-extensions (parallel with IEVS)

MONTH 4 (parallel):
  notice-generation ────── depends: determination events from month 2
  fair-hearings-appeals ── depends: notices (for AppealAcknowledgment)
  ipv-disqualification ─── depends: fair-hearings-appeals (shared canopy-appeals)

MONTH 5 (parallel):
  snap-enrollment-ebt ──── depends: month 2 determinations
  snap-renewals ──────────  depends: month 1 application-intake

MONTH 6 (parallel):
  snap-federal-reporting ── depends: months 1-5 data
  worker-portal-snap ────── depends: session-middleware + months 1-5 APIs
  UAT execution ──────────  depends: month 6 complete

Post-UAT:
  fti-audit-logging ─────── can start anytime
  tanf-eligibility ─────── depends: fti-audit-logging + SNAP UAT
  tanf-federal-reporting ── depends: tanf-eligibility
  medicaid-eligibility ───── depends: tanf (FTI pattern) + SNAP UAT
  ffe-exchange ──────────── depends: medicaid-eligibility
  medicaid-federal-reporting depends: medicaid-eligibility
  caps-eligibility ─────── depends: persons + applications (can parallel with TANF)
  wic-eligibility ──────── depends: persons + applications + SNAP (adjunctive)
  applicant-portal ─────── depends: all program services

Remaining Work Tracker

Comprehensive list of all outstanding work, organized by priority tier. Updated by each implementation session. Canonical source of truth for "what’s left."

Tiers 0-8 below capture historical work identified at specific past dates (most are now Done). For the current snapshot of pending work — open GitLab issues, open plan Status rows, and ADR follow-throughs — see Tier 9 — Open work snapshot further down.

Tier 0 — Hygiene (stale plans, no code changes)

Item Effort Status

Update jdm-ruleset-rewrite plan status table (Steps 5-8 → Complete)

5 min

Done (2026-04-12)

Update documentation-completeness plan (~10 of 18 steps actually done)

15 min

Done (2026-04-12)

Correct tanf-pamms-alignment Steps 3-4 → "Partial (migration only)"

5 min

Done (2026-04-12)

Correct tanf-federal-reporting Step 7 → "Partial (no integration tests)"

5 min

Done (2026-04-12)

Correct medicaid-federal-reporting Step 7 → "Partial (no integration tests)"

5 min

Done (2026-04-12)

Correct snap-deduction-calculation plan to reflect JSON-file approach

10 min

Done (2026-04-12)

Tier 1 — Gaps in "complete" work

Item Plan / Source Status

tanf-pamms-alignment Steps 3-4: GRG API endpoints + personal-responsibilities store fns (migrations exist, no Rust)

tanf-pamms-alignment

Done (2026-04-12)

tanf-federal-reporting: write the 9 integration test scenarios (ACF-199/WPR/ACF-196)

tanf-federal-reporting

Done (2026-04-12) — structural content tests added

medicaid-federal-reporting: write the 10 integration test scenarios (T-MSIS/CMS-64/CMS-416)

medicaid-federal-reporting

Done (2026-04-12) — structural content tests added

Dead cascade_result.assigned_coa field — add debug_assert or remove

Code review

Done (2026-04-12) — debug_assert added

serialize_as_number silent 0.0 fallback → return S::Error::custom

Code review

Done (2026-04-12)

chip_lower_pct_fpl hardcoded 134 → move to jurisdiction.toml + citations.toml

Code review

Done (2026-04-12) — 151/151 citations

Error messages from evaluate don’t identify which ruleset failed

Code review

Done (2026-04-12) — ruleset name in source chain

parent_caretaker COA integration test

Code review

Done (2026-04-12)

snap-alien-eligibility.json inputs missing type field (cosmetic)

Code review

Done (2026-04-12)

openapi-contract-testing Step 1: add canopy-tanf + canopy-medicaid to api_docs.rs SERVICES

openapi-contract-testing

Done (2026-04-12)

crate-quality-parity Step 1: #![warn(missing_docs)] on shared crates

crate-quality-parity

Done (2026-04-12) — 243 warnings; fixing incrementally

workflow-guidance-templates Steps 5-6: canopy-web guidance panel template

workflow-guidance-templates

Done (2026-04-12)

Tier 2 — Phase 3 plan residuals

Item Plan Status

tanf-pamms-alignment Steps 3-4 endpoints (GRG MSP+CRISP API, personal responsibilities CRUD)

tanf-pamms-alignment

Done (2026-04-12) — completed in Tier 1

Medicaid 38-COA expansion Phase A: FormerFosterCare, Newborn, WHM, P4HB (FP/IPC/RM), FourMonthsExtended, Refugee (8 COAs — boolean flags + simple income tests)

medicaid-implementation

Done (2026-04-12) — 15/38 COAs now have real evaluation logic

Medicaid 38-COA expansion Phase B: Q-Track (QMB/SLMB/QI-1) + Family MN spenddown (9 steps)

Phase B plan

Done (2026-04-13) — 20/38 COAs evaluable

Medicaid 38-COA expansion Phase C: TMA cross-program (8 steps)

Phase C plan

Done (2026-04-13) — 21/38 COAs evaluable

Medicaid 38-COA expansion Phase D: ABD FBR SSA-linked (8 steps)

Phase D plan

Done (2026-04-13) — 26/38 COAs evaluable. Orchestrator SSA wiring shipped 2026-05-11 against NoopSolqAdapter in #384; real-SSA cutover stays Blocked on CMA.

Medicaid 38-COA expansion Phase E: ABD waivers + AMN spenddown (10 steps)

Phase E plan

Done (2026-04-13) — 35/38 COAs evaluable

Medicaid 38-COA expansion Phase F: FosterCare/Adoption/Chafee (6 steps)

Phase F plan

Done (2026-04-13) — 38/38 COAs evaluable

Tier 3 — New program services

Plan Steps Issues Status

CAPS Eligibility

12

#203-#210

Done (2026-04-13) — 3 tables, 3 API routes, 7 unit tests, JDM ruleset

WIC Eligibility

8

#211-#218

Done (2026-04-13) — 3 tables, 4 API routes, 6 unit tests, JDM ruleset

NOTE
FFE Account Transfer moved to Tier 8 — Externally blocked (waits on Georgia Access readiness).

Tier 4 — Federal reporting completion

Item Plan Status

Medicaid T-MSIS + CMS-64 + CMS-416 full pipeline (Issues #196-#202)

medicaid-federal-reporting

Done (2026-04-13) — T-MSIS 38-COA mapping, CMS-64 enrollment aggregation, CMS-416 EPSDT by age band, CSV export. Limitations in errata.

TANF ACF-199/WPR/ACF-196 enrichment + WPR calculation

tanf-federal-reporting

Done (2026-04-13) — ACF-199 enriched (14 cols), WPR calculation, ACF-196 stubs, CSV export. Limitations in errata.

SNAP FNS-388 endpoint content assembly (some return stub data)

snap-federal-reporting Step 8

Partial

Tier 4.6 — Worker portal multi-program expansion

Item Plan Status

canopy-web: TANF/Medicaid/CAPS/WIC case detail tabs + program-aware routing (7 steps)

worker-portal-expansion

Done (2026-04-14) — Program enum, ServiceClients expansion, 4 new program tabs, dashboard multi-program

Tier 5 — Cross-program and integration

Item Plan Status

End-to-end cross-program functional testing (TSNAP/TMA/Express Lane with real determination flows)

cross-program-functional-testing

Done (2026-04-17) — All 9 steps complete. 877/877 tests pass end-to-end. Includes TSNAP/TMA/ELE E2E tests, TMA negative test, ELE positive + negative test, and express_lane_evaluations persistence layer. Actual Medicaid/PeachCare enrollment remains an orchestrator referral concern (ADR-005) and is tracked separately.

Ephemeral port allocation (xtask port reservation + discovery + .ports.env persistence)

ephemeral-port-allocation

Done (2026-04-14) — Verified with dev restart + full test battery, no port collisions with craig

OpenAPI contract breaking-change detection CI gate

openapi-contract-testing

Moved to Post-1.0 — premature while we’re making breaking changes regularly

Tier 5.5 — Deferred placeholders / simplifications

Code items tagged TODO / placeholder / stub that must be addressed before 1.0.

NOTE
ACF-196 state-accounting stub (services/canopy-reporting/src/reporting/tanf.rs:287) moved to Tier 8 — Externally blocked (waits on state accounting system integration).
Location Issue Tracking

services/canopy-medicaid/src/main.rs (was line 138)

TMA subscriber now iterates per-member person_ids from the expanded tanf.case_closed payload; writes one tanf_tma_coverage row per person

Done (2026-04-18) — tma-subscriber-person-lookup. Backfill xtask deferred (no historical placeholder rows exist pre-UAT).

services/canopy-appeals/src/api/mod.rs:388 + continued_benefits.rs:29

Continued-benefits overpayment now sums real issuances via GET /v1/households/{household_id}/issuances on canopy-enrollment; monthly/30*days placeholder removed

Done (2026-04-20) — canopy-enrollment-household-issuances

services/canopy-reporting/src/reporting/tanf.rs:81

Work hours placeholder replaced — ACF-199 now aggregates real per-activity hours via GET /v1/work-requirements/{person_id}/activities/summary on canopy-tanf

Done (2026-04-19) — canopy-tanf-work-activities-list

services/canopy-web/src/api/case_detail.rs:799

Program-specific income display — income tab now fetches per-member self-reported income from canopy-persons, renders program-specific rule pointer (citations only, no hardcoded thresholds), hides IEVS column for non-SNAP programs

Done (2026-04-20) — canopy-web-persons-wiring

CAPS authorization tab (canopy-web)

Live — two new canopy-caps list endpoints wired through canopy-web. Template + view-model reconciled against the DB schema (Option A — no care_type column; authorization_status / end_date / rate_display / copayment_display).

Done (2026-04-21) — canopy-caps-list-endpoints. Playwright E2E landed 2026-04-21 via canopy-seed-caps-wic-fixtures.

WIC nutritional risk tab (canopy-web)

Live — canopy-wic grew GET /determinations?household_id, GET /nutritional-risk-assessments?person_id, and GET /nutritional-risk-assessments/{id}. canopy-web walks household members and aggregates assessments across all participants.

Done (2026-04-21) — canopy-wic-list-endpoints. Playwright E2E landed 2026-04-21 via canopy-seed-caps-wic-fixtures.

Medicaid person names (canopy-web)

Medicaid determination tab resolves real names via ServiceClients::resolve_nameGET /v1/persons/{id} with UUID-prefix fallback only on API error; same resolver wires the household members tab for consistency

Done (2026-04-20) — canopy-web-persons-wiring

services/canopy-portal/src/i18n.rs:18

Fluent .ftl bundles loaded at startup (main.rs); page-level i18n delivered (Plan 3, English) — the Spanish (es) catalog is the remaining follow-up

Delivered (Plan 3); es catalog tracked in #666

Tier 5.6 — Confirmed hacks and workarounds

Intentional shortcuts documented in code comments and plan errata.

Workaround Location Reason

cached_entry_expires_after_ttl uses checked_sub + early return

crates/canopy-api/src/idempotency.rs

Fresh-boot Instant::now() - 24h overflow when uptime < CACHE_TTL

tanf_grant_amount: Option<Decimal> with serde(default)

services/canopy-snap/src/tsnap.rs

FTI-scrubbed from wire payload per ADR-004; treated as Decimal::ZERO in subscriber

categorize_closure_reason() maps TANF denial strings to TSNAP/TMA keywords

tanf-denial-reason-code-from-jdm

Done (2026-04-22) — JDM o-denial-code column emits canonical codes directly; hack deleted; DenialReasonCode enum lives in canopy-reference with Other(String) ADR-011 escape hatch

Payload field closure_date renamed from wire termination_date

services/canopy-tanf/src/events.rs + services/canopy-snap/src/tsnap.rs + services/canopy-medicaid/src/main.rs

Done (2026-04-22) — publisher now emits closure_date (matches tanf.case_closed event name); #[serde(rename)] hack deleted from canopy-snap; canopy-medicaid ad-hoc JSON lookup renamed

max_connections=400 on shared postgres

docker-compose.yml

17 services × per-service pool exhausts default 100 under integration load

ZEN expression unquoting workarounds

JDM rulesets

zen-engine 0.x limitations (see jdm-ruleset-rewrite plan errata)

Tier 5.7 — Plan errata cross-reference

Plans with active errata (limitations, shortcuts, or deviations documented at end of plan):

Plan Errata topics

cross-program-functional-testing

Denial-reason categorization, payload schema mismatch, max_connections tuning, ADR-011 constants migration follow-up

jdm-ruleset-rewrite

zen-engine workarounds, quoting behavior

tanf-federal-reporting

WPR formula simplifications, ACF-196 category stubs

medicaid-federal-reporting

T-MSIS COA-to-coverage-group mapping limitations, CMS-64 expenditure data gaps

eligibility-orchestrator

SSA pre-dispatch wiring for Medicaid Phase D landed 2026-05-11 against NoopSolqAdapter (#384 deliverable (a)). Deliverable (b) — real-SSA cutover — stays Blocked on CMA.

medicaid-coa-phase-c-tma

Placeholder person_id in TMA subscriber

medicaid-coa-phase-d-abd-fbr-ssa

SSA SOLQ data flow shipped 2026-05-11 (#384). Pickle / DAC / DW / Widow 60-64 / Former SSI Disabled Child COAs now evaluable end-to-end against the Noop adapter.

playwright-e2e

SNAP-only coverage; multi-program expansion pending (T7)

shared-db-devstack

Per-program postgres containers suppressed but not removed from compose file

snap-categorical-eligibility

PAMMS 3210 edge cases not fully covered

application-intake

ACA §1413 single-streamlined-application edge cases

security-ci-remediation

CI pipeline sequencing notes

Tier 6 — Infrastructure and operations (post-UAT)

Item Plan Status

operational-infrastructure Steps 2-14: crypto key mgmt, backup/restore, migration CLI, monitoring, log aggregation, canary deploys, blue-green, secrets, load testing, DR, perf baselines, SLA dashboards, capacity planning

operational-infrastructure (archived 2026-04-30)

14/14 complete

documentation-completeness residual ~8 steps: data dictionary, testing strategy, contributor guide, i18n guide, dependency update policy, monitoring guide, capacity planning guide, applicant help content

documentation-completeness

~10/18 complete

Applicant portal (Dioxus fullstack, reference-number auth, Fluent i18n, WCAG 2.1 AA)

ADR-008

Implemented (Dioxus fullstack, Plan 3 complete)

Tier 7 — Pre-1.0 quality gate

Item Source Status

Playwright E2E multi-program expansion (TANF/Medicaid/CAPS/WIC flows)

playwright-e2e

Partial (2026-04-21) — SNAP + CAPS + WIC case-detail tab coverage landed via canopy-seed-caps-wic-fixtures (MR !104). TANF + Medicaid program-specific tabs still rely on permissive "renders without error" assertions against SNAP-seeded data.

YAML config migration

Open issue #291 + ADR-012

Direction ratified 2026-04-23 via ADR-012 (layered YAML + env overrides, struct schema, secrets-env-only). Implementation not started — per-service rollout tracked in issue #291. No scheduled cutover; each service’s migration MR is independently reviewable.

Flaky application tests (2 tests fail under 8-thread parallel load)

canopy-applications::application_test

Done (2026-04-22) — not reproducible after the test-threads = 8 cap in .config/nextest.toml (commit 62010ab) addressed the underlying devstack connection-pool exhaustion. 20 targeted iterations of the canopy-applications suite + 1001/1001 full-workspace runs under the cap all pass. No code fix needed; the concurrency cap is the permanent mitigation.

#![warn(missing_docs)] on all shared crates

crate-quality-parity Step 1

Done (2026-04-14)

Plan Status-vocabulary lint + archive directory

ADR-013

Done (2026-04-23) — closed-set vocabulary ratified; cargo xtask docs plan-lint lands advisory (allow_failure) with 600-violation baseline; docs/…​/plans/archive/ scaffolded; precommit Q4/Q6 rewritten. 2026-04-23 sweep resolved the 600-violation baseline. 2026-04-24 archive pass moved 70 fully-Done plans into plans/archive/ with 148 xrefs rewritten. Advisory→blocking promotion is tracked separately.

Shared-db postgres suppression

Open issue #297

Done (2026-04-14) — --shared-db suppresses per-program postgres containers

cargo xtask validate passes clean

validate CI

Done (2026-04-14) — no clippy/missing-docs blockers

Ephemeral port allocation

ephemeral-port-allocation

Done (2026-04-14)

self_employment (gross) income pooled into PAMMS 1615 $250 disregard without PAMMS 1540 cost-of-doing-business deduction (PAMMS 1540/1615)

tanf-self-employment-net-disregard

Done (2026-04-21) — roadmap citation corrected from PAMMS 1605/1611 (1611 does not exist) to PAMMS 1540/1615

.ports.env reconciliation at test entry (silent-drift auto-fix when the file disagrees with actual bindings)

xtask reconcile_ports_env wired into validate / test / e2e

Done (2026-04-18)

JDM ruleset end-to-end happy-path tests (12 rulesets)

jdm-ruleset-happy-path-tests

Done (2026-04-27) — all 12 fixtures evaluable, drift gate live

Orchestrator parallel-dispatch and circuit-breaker tests

orchestrator-dispatch-tests

Partial (3 of 7) — parallel fan-out + per-service timeout isolation + signature-tamper quarantine tests landed (MR !55). Circuit-breaker tests (Steps 4-5) and optional-service degradation integration test (Step 7) deferred with plan errata.

FTI / audit-events hash-chain verification (mechanism superseded by ADR-041, epic &74)

fti-audit-hash-chain-test

Done (2026-04-18) — 3 DB-backed tests on audit_events hash chain: sequential verify, tamper detection, concurrent-insert chain integrity. Two pre-existing bugs fixed (timestamp-precision drift, created_at vs commit-order ordering). The hash chain is being retired under ADR-041 (#1304, gated behind a proven replacement); the FTI-specific chain extension once tracked as #311 is moot.

ADR-004 SSA / IEVS / FTI authorisation audit (manifest + CI gate)

adr-004-ssa-authorization-audit

Planned (2026-04-18)

ADR-005 graceful-degradation verification (capability flags + Compose-profile matrix)

adr-005-graceful-degradation-verification

Done (2026-04-26) — Steps 1, 2, 5 of plan verified: compliance/deployment-profile-capabilities.toml manifest + 7 capability-flag tests in services/canopy-eligibility/tests/capability_flag_test.rs + cross-reference in deployment-profiles-event-wiring (archived). Profile-matrix integration tests (Step 3) + compose-profile-matrix CI job (Step 4) remain Deferred per plan errata; the plan stays in plans/ since Deferred rows exclude archival.

Tier 8 — Externally blocked

Work that cannot progress without a dependency outside the Canopy team’s control — partner-system readiness, signed CMAs, live production credentials, or state-level data integrations. Each row names the blocker explicitly so reviewers can see why the item sits here rather than in an earlier tier. Unblock-and-go plans exist where noted; the remaining work is purely the unblock step.

Item External dependency Canopy-side readiness Plan

FFE Account Transfer (CMS-to-state account transfers)

Georgia Access (state health exchange) not yet live; CMS-side FFE endpoint credentials pending state SOC-2 review

Stub service exists (canopy-exchange) with the FfeAccountTransferAdapter trait. 7-step implementation plan ready to pick up once Georgia Access is online.

ffe-account-transfer

ACF-196 TANF federal financial report (state-accounting integration)

State accounting system data pipeline — categorized expenditure amounts come from Georgia’s financial system, not Canopy

Stub at services/canopy-reporting/src/reporting/tanf.rs:287 emits category labels with zero amounts. Data pipeline contract is documented in tanf-federal-reporting.adoc errata; waiting on state accounting to expose a read feed.

tanf-federal-reporting

CMS-64 Medicaid quarterly expenditure data (MMIS integration)

MMIS (Georgia Medicaid Management Information System) — dollar expenditures flow from MMIS claim processing, not Canopy

Canopy-owned enrollment counts + member-month aggregation complete via T-MSIS extracts. Expenditure dollar columns are NULL pending MMIS feed. Documented in medicaid-federal-reporting.adoc Step 34 + errata.

medicaid-federal-reporting

CMS-416 Medicaid EPSDT screening counts (MMIS clinical/claims integration)

MMIS (same source as CMS-64) — screening-service utilization comes from claims, not eligibility

Enrolled-children denominator + age-band breakdown complete from T-MSIS. Screening-numerator columns are NULL. Documented in medicaid-federal-reporting.adoc Step 35 + errata.

medicaid-federal-reporting

SSA SOLQ / BINDEX wiring (Medicaid ABD income verification)

Executed Computer Matching Agreement (CMA) with SSA + live SSA production credentials (42 USC §1320b-7, SSA §1106)

NoopIevsAdapter + NoopSaveAdapter produce deterministic stand-in data so the SSA-linked COAs (Medicaid Phase D) evaluate end-to-end in UAT. Real adapters land once CMA executes. Orchestrator SSA data flow tracked in medicaid-coa-phase-d-abd-fbr-ssa.adoc errata.

medicaid-coa-phase-d-abd-fbr-ssa

IEVS live adapters (Georgia DOL SWR + UI; SSA SDX + BENDEX)

Georgia DOL credentials + SSA CMA (same as above). Same data-sharing agreements that gate SSA SOLQ.

NoopIevsAdapter in canopy-verification gives deterministic wage/UI/benefit data for every E2E test. snap-verification-ievs.adoc documents the adapter seams; swapping in a real implementation is a credential-swap plus secret-management + retry-logic exercise.

snap-verification-ievs

Tier 9 — Open work snapshot (historical; 2026-05-04)

CAUTION
This snapshot is historical (2026-05-04 → 2026-05-12) and is NOT maintained in-place. Do not read the counts or enumerated issues below as current. As of 2026-08-03 the project is in the scale-hardening + correctness phase; the canonical open-work state lives in GitLab, organized under epic &73 (scale readiness) and epic &74 (ADR-041 logging + redaction) — see Current Status. For the live list use glab issue list --state opened (or the epic boards). The 2026-05 enumeration is retained below only for historical traceability of the May build-out wave.

Live state of pending work as of 2026-05-04, complementing the historical Tiers 0-8 above. The earlier tiers track work identified and prioritized at specific past dates; this section enumerates everything currently still open across plan Status tables, GitLab issues, and ADR follow-throughs.

CAUTION
Snapshot drift since 2026-05-04 — the issue counts and listings below have not been refreshed in-place; treat them as historical. As of 2026-05-10 the canonical open-issue list is glab issue list --state opened (56 open). Closed since the 2026-05-04 snapshot (newest first, see git log for MR refs):
  • #399 / #400 / #402 canopy-applications validation + PUT programs sync + caseload search (!248, 2026-05-10).

  • #382 follow-onappeal.overpayment_assessed auto-opens claims in the right program-service DB (!247, 2026-05-10).

  • Per-service API reference pages for canopy-tanf / canopy-medicaid / canopy-caps / canopy-wic (!249, 2026-05-10) — partial address of #265.

  • Epic !49 (ADR-003 cross-service hygiene pass, closed 2026-05-10) bundling #443 canopy-notices event routing, #444 keycloak realm port-drift enforcement, #440 canopy-snap IEVS source names, #441 canopy-tanf federal time-limit, #442 canopy-appeals ADH + decision-clock.

  • #382 canopy-overpayments shared crate + per-program recovery pipeline + canopy-reporting roll-up CSV (2026-05-10).

  • #385 canopy-cli ADR-007 parity catchup, #401 canopy-applications authorized-rep CRUD, #339 xtask validate-in-network runner, #411 canopy-web BFF service-to-service auth, #424 canopy-identity service-class token migration, #422 provider-agnostic OIDC, #439 ADR-019 hard cutover, #427 RFC 8785 canonical-JSON envelope, #428 scheduler leader election, #429 canopy-rules per-endpoint RBAC, #430 OTLP-direct observability migration, #432 SNAP income_type field alignment, #426 SNAP params jurisdiction-toml path correction, #425 dev-key fallback hardening.

Cumulative effect: every Phase B / C / D / Phase-E item from Tier 10 below has shipped, plus the Phase E.2 canopy-applications batch (#399 / #400 / #402 closed by !248) and per-service API reference pages (!249). The open queue is now (a) the FFE-blocked Phase G cluster, (b) the four Phase-F deferred trackers (#349-#352), and (c) the Phase E.2 residual (#265 partial / #267 / #308 / the reserve_ports race).

2026-05-11 → 2026-05-12 session delta (Tier B Wave 2-4 + #448 follow-up):

  • Wave 2 — !260 closes #384 deliverable (a) Medicaid SSA SOLQ pre-dispatch wiring (NoopSolqAdapter live; CMA-blocked real cutover stays open on #384).

  • Wave 3 — !261 closes #407 SNAP partial-month retention (filed follow-up #447 for canopy-appeals overpayment-math consumption); !262 chore: idempotency cache lookup logging (replaces silent .ok()? with diagnostic WARN per two parallel agent investigations); !263 closes #408 enrollment-household-rbac with household_assignments table in canopy-applications + Pub 1075 §9.3.1 gate on GET /v1/households/{id}/issuances.

  • Wave 4 — !264 closes #420 worker portal design mockups; !265 closes #392 worker portal program action handlers (20 handlers × 4 programs + tab affordances; filed follow-ups #448 upstream gaps + #449 Playwright specs).

  • #448 follow-up to #392 — !266 closes 11 of 14 upstream gaps (canopy-renewals path-parameterization, canopy-caps PUT mutations, canopy-web WIC nutritional-risk BFF path bugfix); !267 closes the remaining 3 with new schema work (canopy-tanf tanf_discrepancies + resolve handler; canopy-medicaid medicaid_cmd_events + cmd-ingest + determination requeue; canopy-wic wic_appointments + schedule-appointment).

  • Wave 5 — #396 closed: caps-provider-registry plan landed (caps_providers table, 5 CRUD endpoints, FK retypes on caps_applications/caps_authorizations, FK-validated #448 switch-provider, 422 mapping for FK/unique violations). canopy-caps route count 7 → 12; canopy-seed seeds a 5-row provider catalogue.

  • Architectural follow-up filed: #450 test-seed harness needs atomic manifest+DB seeding + random-by-default + predicate fixtures (root cause of the WIC/CAPS Playwright flake during the session).

NOTE
The operational-infrastructure plan (14 steps) closed out 2026-04-30 — it now lives under plans/archive/. The Tier 7 row below preserves its position for historical traceability. The canopy-caps-list-endpoints and canopy-wic-list-endpoints plans were archived 2026-05-01. The secret-and-config-migration plan closed out 2026-05-02 across MRs !163 through !183 (23 steps; 22 step-tracking issues #354-#375 all closed; ADR-017 ratified; ADR-012 implementation complete; multi-key SSN encryption rotation support shipped).

Open GitLab issue counts as of 2026-05-12 end-of-session (after Wave 2-4 + #448 closures, plus #447 / #449 / #450 follow-up filings): roughly 49 open standalone + 7 plan-step (the FFE issues #189-#195). The 2026-05-03 snapshot above (62 / 55 standalone) is preserved for delta reference; the actual count fluctuates as follow-ups are filed and closed. Distribution still skews FFE-blocked at the high-priority end; consult glab issue list for the live count.

The 2026-05-03 → 2026-05-04 delta: 33 new issues filed (#378-#390 production-readiness gaps from contextless audits; #391-#418 second-wave Potential Improvements bullets; #419-#421 documentation-completeness Not started rows). The PI sweep tied off all known plan "Potential Improvements" backlogs — every remaining bullet is either a tracked issue or an explicit deferral with rationale.

Active plan open rows

Plan Open rows Open work summary

documentation-completeness

1 (3 of original 4 now have GitLab issues)

Step 1: Create role-based user guides (caseworker, eligibility specialist, supervisor, auditor) — only the caseworker guide exists. Steps 7/9/14 split out to #419 / #420 / #421 (data-model docs + ERDs, UI mockups, screenshots).

ffe-account-transfer

7

Step 1: Define FfeAccountTransferAdapter trait methods for inbound a; Step 2: Create account_transfers table migration in canopy-exchang; Step 3: Implement outbound transfer: serialize determination + demog; Step 4: Implement inbound transfer: parse ACPT XML, create applicati; Step 5: Wire event publishing for exchange.transfer_sent and `exch …​ +2 more

Standalone GitLab issues (not plan-step issues)

iid priority workflow type title

#267

low

ready

documentation

Applicant-facing help content and accessibility guide

#308

medium

needs-spec

feature

Standalone EC2 Deployment Pipeline for Canopy project

#323

low

ready

refactor(canopy-tanf): typed expense_type enum replacing stringly-typed ExpenseItem.expense_type

#325

low

ready

refactor(canopy-medicaid): switch extract_person_ids to typed UUID when MemberContext.person_id becomes typed

#337

low

ready

chore

chore(canopy-notices): PDF/A conformance for archival-grade notice PDFs

#339

low

needs-spec

chore

Flip cargo xtask validate to in-network integration runner (ADR-015 follow-up)

#348

low

needs-spec

feature

SSE caseload filtering (op-infra Step 9 follow-up)

#349

low

needs-spec

feature

feat(canopy-web): render workflow guidance panel UI

#350

low

needs-spec

compliance

test(canopy-eligibility): compose-profile-matrix integration tests + CI job

#351

low

needs-spec

feature

feat(canopy-mq): compile-time EventPayload macro + CI grep lint (event-bus-enforcement Steps 2+4)

#352

low

needs-spec

chore

chore(post-UAT): revive openapi-contract-testing plan after September 2026 UAT

#378

medium

needs-spec

feature

feat(canopy-reporting): wire ACF-196 expenditures from state accounting

#379

medium

needs-spec

feature

feat(canopy-reporting): wire CMS-64 expenditure aggregation from MMIS

#380

medium

needs-spec

feature

feat(canopy-reporting): wire CMS-416 EPSDT screening data from clinical systems

#381

low

needs-spec

feature

feat(canopy-portal): wire Fluent i18n loader (replace stub LocaleManager)

#382

medium

needs-spec

feature

feat(canopy-enrollment): wire overpayment recovery pipeline (currently log-only)

#383

medium

ready

security

security(fti): dedicated fti_auditor role (currently uses admin)

#384

medium

needs-spec

feature

feat(canopy-eligibility): wire SSA-linked flags through orchestrator to canopy-medicaid Phase D

#385

medium

needs-spec

feature

feat(canopy-cli): catch up to ADR-007 — add household / income / asset / interview / determine subcommands

#386

medium

needs-spec

refactor(canopy-medicaid): move CMD cascade priority + TMA branching + denial-reason synthesis from Rust to JDM (ADR-003 drift)

#387

high

needs-spec

fix: normalised determination envelope for orchestrator signature verification (medicaid signatures broken in transit)

#388

medium

needs-spec

feature

perf(canopy-mq): persistent outbox table for at-least-once durability across crash

#389

low

ready

feature

feat(canopy-mq): configurable buffer-full strategy (drop-newest / drop-oldest / block)

#390

low

ready

feature

feat(canopy-mq): backpressure signal when publish buffer crosses threshold

#391

medium

ready

feature

feat(canopy-caps,canopy-wic): GET /v1/determinations?household_id=X list endpoints

#392

medium

ready

feature

feat(canopy-web): program-specific action handlers (TANF work-activity, WIC nutritional risk, CAPS authorization)

#393

low

ready

feature

feat(canopy-web): cross-program dashboard stats (TANF/Medicaid/CAPS/WIC widgets)

#394

low

ready

feature

feat(canopy-web): cross-program summary view per household

#395

low

ready

feature

feat(canopy-seed): multi-determination per CAPS household

#396

low

done (2026-05-12)

feature

feat(canopy-caps): provider registry — closed by caps-provider-registry plan; caps_providers table + provider CRUD + UUID FKs on caps_applications.provider_id / caps_authorizations.provider_id, FK violations surface as 422

#397

low

ready

feature

feat(canopy-seed): WIC multi-participant households (pregnant mother + infant)

#398

low

ready

feature

feat(canopy-seed): CAPS/WIC renewals, transfers, and termination fixtures

#403

low

ready

chore

feat(canopy-typst): bundle Montserrat TTF in rulesets/georgia/notices/fonts/

#404

low

needs-spec

chore

feat(canopy-typst): agency seal / logo SVG assets for letterhead.typ

#405

medium

ready

feature

feat(canopy-typst): form-building Typst components (checkbox-grid, data-table, field-row, conditional-section)

#406

low

ready

feature

feat(canopy-tanf): per-row activity_breakdown drill-down on work-activity summary endpoint

#407

medium

needs-spec

feature

feat(canopy-enrollment): partial-month retention rule per PAMMS 2415

#408

low

needs-spec

feature

feat(canopy-enrollment): household-scope RBAC for issuance listing

#409

medium

ready

feature

feat(canopy-web): per-member income editing UI for caseworkers

#410

low

needs-spec

feature

feat(canopy-persons): historical income versioning with superseded_by pointer

#411

high

needs-spec

feature

feat(canopy-web): service-to-service auth for BFF → upstream API calls

#412

low

ready

chore

feat(jurisdiction.toml): consolidated [shared.timing] section for renewal/expungement/dashboard windows

#413

medium

ready

compliance

compliance(adr-011): formalise ATO evidence statement for policy-trace coverage

#414

medium

needs-spec

feature

feat(canopy-snap): self-employment standard deduction parity (PAMMS 3425 / 7 CFR 273.11(a)(2))

#415

low

ready

feature

feat(canopy-medicaid): denial-code parity with canopy-tanf (DenialReasonCode enum)

#416

low

needs-spec

feature

feat(canopy-tanf): sanction denial path through eligibility ruleset

#417

low

ready

feature

feat(canopy-medicaid): dead-letter on missing person_ids in tanf.case_closed subscriber

#418

medium

ready

feature

feat(xtask): cargo xtask compliance capabilities — manifest enforcement in CI

#419

low

ready

documentation

docs: per-service data model documentation with column descriptions + ERDs

#420

low

needs-spec

documentation

docs: UI mockup / design docs for worker portal modules

#421

low

ready

documentation

docs: screenshots page organised by portal module

NOTE
7 additional GitLab issues are plan-step issues ([<plan>] Step N) tracked under the Active plan rows table above (the 7 ffe-account-transfer issues #189-#195). The 22 secret-and-config-migration step issues (#354-#375) all closed across MRs !163-!183 on 2026-05-02. Op-infra Step 12 follow-ups #341/#342 closed via !157, #343 closed via !156, #344 closed via !158 — all merged 2026-05-01. #291 and #346 closed via !163 (2026-05-02) as superseded by the secret-and-config-migration plan. #340 closed via !160, #345 closed via !162 (Phase B).

ADR follow-throughs broken into plans + complete

ADR follow-throughs not yet broken into plans

Tier 10 — Master Tackle Order (phases, not snapshots)

Tier 9 captures what’s open right now. This tier captures the order to tackle it in — independent of churn in the issue list. Phases are ordered by dependency chains, not re-evaluated priority: existing GitLab priority::* labels are taken as authoritative within each phase.

Phase A — Plan archival housekeeping ✅ DONE

MR !159 (commit 441c3df, 2026-05-01) — archived 2 functionally-Done plans (canopy-caps-list-endpoints, canopy-wic-list-endpoints), shipped cargo xtask docs plan-lint Deferred-tracker enforcement, refreshed Tier 9 above.

Phase B — Op-infra follow-ups (RESIDUAL — both gated)

Done:

  • MR !160 — #340 idempotency replica-restart integration test + production bug fix.

  • MR !161 — QC half of #347 + rustdoc half of #348.

  • MR !162 — #345 forward-only migrations / ADR-016.

  • MR !186 — #353 PITR runbook for production schema rollback (ADR-016 follow-up).

  • MR !187 — #347 address-join half of person export.

  • MR !188 — #348 htmx-sse extension wiring half.

Folded into Phase C:

  • #346 (Vault SecretProvider) → superseded by ADR-017 in the combined plan; closed 2026-05-02.

Residual (both gated; don’t pick early):

  1. #348 (priority::low) — SSE caseload filtering. Gated on the per-worker caseload-membership store (post-UAT case-management work).

  2. #339 (priority::low) — flip cargo xtask validate integration step to in-network runner (ADR-015 follow-up). Deferred — needs CI infra changes that haven’t happened yet.

Phase C — Foundational ✅ DONE (2026-05-02)

secret-and-config-migration plan — coordinated 23-step rollout of ADR-012 (layered YAML config) + ADR-017 (SOPS+age secrets) shipped across MRs !163 through !183. All 22 step-tracking issues (#354-#375) closed. Plan archived under plans/archive/.

Highlights:

  • MR !163 — Step 0: ADR-017 ratification + plan + #291/#346 superseded.

  • MR !166 — Step 1: cargo xtask secrets tooling + canopy-devtools compose service + secrets-yaml-lint CI job.

  • MR !168 — Step 2: layered YAML loader in canopy-common.

  • MR !170 + !171 fix — Step 3: canopy-snap canary.

  • MR !172 — Step 4: canopy-exchange empty-case template.

  • MR !173 — Step 5: canopy-persons + multi-key SSN encryption rotation support (canopy-common::crypto EncryptionKeys/decrypt_with_rotation).

  • MR !174 — Step 6: canopy-verification.

  • MR !175 — Step 7: canopy-applications.

  • MR !176 — Steps 8-10: canopy-enrollment + canopy-renewals + canopy-notices.

  • MR !177 — Steps 11-12: canopy-rules + canopy-appeals.

  • MR !178 — Step 13: canopy-security FTI URLs as secrets.

  • MR !179 — Steps 14-16: canopy-tanf + canopy-caps + canopy-wic.

  • MR !180 — Step 17: canopy-medicaid.

  • MR !181 — Step 18: canopy-eligibility.

  • MR !182 — Steps 19-21: canopy-portal + canopy-web + canopy-reporting.

  • MR !183 — Step 22: cleanup, .env.example retired, plan archived, developer-guide updated.

Material plan deviations (preserved here so a future audit doesn’t need to walk all 22 commits): serde_yaml dropped (archived upstream) → sops --output-type json + serde_json::Value; age + sops live in canopy-devtools compose service rather than as host requirements; deny_unknown_fields deferred from per-service structs and ServiceSettings because the prefix-shared env source feeds shared baseline keys (port, database_url, rabbitmq_url, keycloak_*) through every service’s prefix — restoration requires a ServiceSettings-flatten refactor first; internal_api_key lives under shared: in dev.yaml (not canopy-snap:) so it emits as CANOPY_INTERNAL_API_KEY (no prefix) matching every service’s existing read site; multi-key SSN encryption rotation support implemented inline rather than deferred per feedback_no_deferral; canopy-portal kept lightweight (groundwork only — single-tunable Rust refactor was minimal-ROI); canopy-eligibility verifying keys (5 programs) stay env-or-.keys/-routed because they’re public keys, not secrets.

Residual operator action: set the GitLab masked variable CANOPY_CI_AGE_KEY from cargo xtask secrets init --for-ci output. Without this, the integration-tests CI job fails when sops-decrypting secrets/dev.yaml. The CI keypair private was emitted during MR !166’s bootstrap.

Out-of-window follow-ups (NOT separate issues; tracked here as the cleanup phase trigger):

  • Restore [serde(deny_unknown_fields)] on ServiceSettings and per-service structs — requires the per-service config struct to flatten the shared baseline keys via [serde(flatten)] so the env source’s prefix-shared reads have somewhere to land.

  • Retire residual CANOPY_<SVC>__JURISDICTION env vars — same precondition; jurisdiction currently feeds ServiceSettings.jurisdiction and the per-service struct.

Phase D — Tier 7 polish ✅ ESSENTIALLY DONE (2026-05-04)

17 of 20 issues closed across MRs !189 through !206 (16 MRs, including the JDM-namespacing Path B sweep that #331 blocked on). The 3 residual items are externally gated.

Shipped:

  • xtask ADR-004 audit cluster: #326 (!189), #327 (closed 2026-05-04 — no concrete bug), #328 (!189).

  • xtask rules-check cluster: #329 (!190), #331 (!196), #330 (!206 — input-side drift gate symmetric to #331’s threshold-side gate).

  • canopy-reference build.rs: #324 (!197).

  • canopy-eligibility test infra: #332 (!201), #333 (!201), #334 (!198).

  • canopy-notices cluster: #321 (closed 2026-05-04 — typst-render bench in !203 showed engine reuse saves <1ms per render; no ROI without state-scale profiling), #335 (closed — code review showed templates already hot-reloaded; only the manifest needs restart).

  • canopy-tanf/medicaid refactors: #320 (closed 2026-05-04 — !204 bench shows aggregate_summary is sub-millisecond, and ACF-199 hits each (person, month) at most once so cache hit-rate is near zero; bulk query is the right fix if profiling later flags it).

  • persons/enrollment perf cluster: #318 (closed — !205 load profile p95 0.31ms per income GET; N+1 fan-out costs ~6ms even at N=20), #319 (closed — !205 load profile p95 0.91ms for the household-scoped issuance list; bulk-fetch saves nothing).

  • e2e cluster: #322 (!200 — Playwright storage-state cached across runs, auth-setup 1.81s → 0.59s), #336 (!199 — vendor.toml + sha256 drift gate).

Residual (all externally gated; don’t pick early):

  1. #337 (priority::low) — PDF/A conformance for archival-grade notice PDFs. Upstream support landed (typst-pdf 0.12+; workspace on 0.14.2); enforced 2026-07-25 — the engine exports PDF/A-2b with a baseline-PDF loud fallback, and every template render test validates conformance.

  2. #323 (priority::low) — typed expense_type enum. Explicitly deferred to the crate-quality-parity sweep (the same pattern appears across IncomeItem.income_type, AssetItem.asset_type, and should be swept together).

  3. #325 (priority::low) — switch extract_person_ids to typed UUID. Conditional on the orchestrator-side MemberContext.person_id typing refactor happening first.

Phase D enabling work also shipped this session (not Tier 7 polish, but landed alongside):

  • Path B sweep — !191-!195 — namespaced every JDM ruleset (input. / context.thresholds.) so the #331 / #330 drift gates have a stable surface to assert against.

  • k6 perf scripts !202 (smoke / load / stress / soak) — fixed the wired-but-never-written cargo xtask perf harness; load profile is what produced the data closing #318/#319.

  • Criterion benches !203 (typst) and !204 (tanf summary) — same role for #321 and #320.

  • k6 load auth + URL fixes !205 — made the load profile actually exercise the deferred-on-profiling endpoints.

Phase E — Bigger needs-spec (write plan, then build)

Done:

  • #424 canopy-identity service-class token migration (MRs !225-!232, !238-!239 across the ADR-019 cutover sequence; 2026-05-08 → 2026-05-09).

  • #439 ADR-019 hard cutover — drop transitional gates, require_service_caller-only on internal endpoints (MR !238).

  • #422 Provider-agnostic OIDC (discovery + neutral naming).

  • #411 canopy-web BFF service-to-service auth.

  • #427 RFC 8785 canonical-JSON envelope, #428 scheduler leader election, #429 canopy-rules per-endpoint RBAC, #430 OTLP-direct observability migration (E0.4 / E0.5 / E0.6 follow-ups; 2026-05-07).

  • #432 SNAP income_type field alignment, #426 SNAP params jurisdiction-toml path correction, #425 dev-key fallback hardening (regression triage 2026-05-07).

  • #386 medicaid-jdm-completion — 3 new JDM rulesets (CMD cascade priority + TMA phase + denial reasons) replacing the last Rust-side decision logic in canopy-medicaid.

Phase E.2 — ADR-003 cross-service hygiene + cross-program overpayments (Done 2026-05-10)

  • Epic !49 (closed 2026-05-10) — multi-agent ADR-003 audit produced 5 child issues; all shipped:

    • #382 cross-program overpayment recovery (MR !245) — new crates/canopy-overpayments shared crate, 5 endpoints × 3 program services (SNAP / TANF / Medicaid), canopy-reporting roll-up CSV, integration test for SNAP IHE full lifecycle.

    • #443 canopy-notices config-driven event-to-notice routing (MR !240) — moved 7-arm match block from Rust to notices/manifest.toml .

    • #444 keycloak realm port-drift detection has enforcement authority (MR !241) — ReconcileResult::keycloak_realm_drifts + auto-heal in cmd/e2e.rs + bail in cmd/validate.rs / cmd/test.rs.

    • #440 canopy-snap IEVS source names from config (MR !242) — [snap.verification] + VerificationClient::new(.., wage_match_source, unemployment_match_source).

    • #441 canopy-tanf federal time-limit from config (MR !243) — tanf_time_limits.federal_limit_months DEFAULT dropped; value now snapshotted from params.federal_time_limit_months() at INSERT.

    • #442 canopy-appeals ADH + decision-clock from config (MR !244) — adh_notice_advance_days wired into ipv/api.rs::send_notice; new approaching_deadline_warning_days in [appeals] threaded into clock.rs::run_daily_check.

  • #385 canopy-cli ADR-007 parity catchup (MR !237).

  • #401 canopy-applications authorized-rep CRUD (MR !236).

  • #339 cargo xtask validate-in-network opt-in runner (MR !235).

Residual:

  • #265 Published API reference from OpenAPI specs (priority::medium) — partially addressed by !249 (hand-written per-service ref pages for tanf / medicaid / caps / wic); OpenAPI-generated half (Redoc / Rapidoc in Antora) still open.

  • #267 Applicant-facing help content + accessibility guide (priority::low)

  • #308 Standalone EC2 deployment pipeline (priority::medium)

  • reserve_ports() TcpListener hold-through-compose-up hardening — out-of-scope of #444 per its CHANGELOG; file separately if reservation race reproduces.

Closed since snapshot:

  • canopy-applications batch #399 / #400 / #402 — closed by !248 (commit afa543f, 2026-05-10): programs_requested validation → HTTP 422, PUT /v1/applications/{id} accepts programs_requested with transactional sync, GET /v1/applications gains submitted_by / program / status / from / to filters.

  • #382 follow-on — !247 (commit 23ed004, 2026-05-10) wires appeal.overpayment_assessed directly into each program service’s overpayment-claim store, replacing the canopy-enrollment log-only handler; canopy-appeals event payload extended to carry person_id / program / determination_id / amount_cents.

Phase E.3 — Tier B Wave execution + cleanup (Done 2026-05-11 through 2026-05-14)

12-plan Tier B refresh (MRs !252-!254 lint-pass) executed across five waves and a tail of small bundles. All plans now archived; supporting follow-ups closed inline.

Wave 1 — Eligibility-rules pair (2026-05-10 → -11):

  • #416 PAMMS 1351 + 1345-1370 gates, DeterminationStatus::Sanctioned (!259).

  • #386 3 JDM rulesets: cmd-cascade-priority + tma-phase + denial-reasons (!239).

Wave 2 — Medicaid SSA flow (2026-05-11):

  • #384 deliverable (a) — NoopSolqAdapter in canopy-verification, orchestrator pre-dispatch SOLQ for elderly/disabled Medicaid, ssa_solq on dispatch payload, derive_abd_flags_from_solq in canopy-medicaid (!260). Deliverable (b) stays Blocked (CMA execution).

Wave 3 — Enrollment (2026-05-11):

  • #407 partial_retention + retained_through on snap_enrollments, typed TerminateEnrollmentRequest, closure.rs helper, PAMMS 2415 cutoff (!261). Follow-up #447 tracks canopy-appeals overpayment-math consumption.

  • #408 household_assignments table + 4 CRUD endpoints + service-class outbound client + inline RBAC gate + audit events (!263).

  • Chore !262 — idempotency check_cache Err-path logging.

Wave 4 — Worker portal (2026-05-11 → -12):

  • #420 8 page surfaces + login as Mermaid diagrams with Orchard color tokens (!264).

  • #392 20 BFF handlers + tab affordances on TANF/Medicaid/CAPS/WIC, inline <details> form pattern (!265). Generated #448 (upstream endpoint gaps) + #449 (Playwright specs).

#448 upstream endpoint gaps for #392 — FULLY CLOSED (2026-05-12):

  • Batch 1 (!266): canopy-renewals path-parameterization (8/8 interim-contact + change-report via /v1/renewals/{program}/…​), canopy-caps PUT mutations, canopy-web WIC nutritional-risk BFF path fix. Migration adds program column + nullable certification_id to snap_change_reports.

  • Batch 2 (!267): canopy-tanf tanf_discrepancies + POST /v1/verification/discrepancies/{id}/resolve, canopy-medicaid medicaid_cmd_events + POST /v1/cmd/ingest + POST /v1/determinations/{id}/requeue, canopy-wic wic_appointments + POST /v1/wic/certifications/{id}/appointments.

Wave 5 — Standalone (2026-05-12):

  • #396 caps_providers table + FK refactor + CRUD endpoints (!269).

  • #449 Playwright specs for the 20 #392 handlers (!270).

Other Tier B closures alongside the Waves (2026-05-11):

  • #381 real LocaleManager + en/es bundles (!255).

  • #414 40% SE deduction ruleset (!256).

  • #446 canopy-persons PUT/DELETE income endpoints (!257) — unblocked #409.

  • #409 htmx add/edit/remove income forms (!258).

Cross-program views + small bundles (2026-05-13):

  • #393 #394 worker-portal cross-program landing pages (feat/worker-portal-cross-program).

  • #395 #397 #398 canopy-seed CAPS/WIC fixture richness (feat/seed-caps-wic-multi-fixtures).

  • #418 cargo xtask compliance capabilities ADR-005 drift gate (feat/xtask-compliance-capabilities).

  • #383 #423 fti_auditor role + hash-derived FTI lock ids (feat/fti-auditor-role-and-advisory-naming).

  • #417 TMA-subscriber DLQ routing (feat/tma-dlx-routing).

  • #412 consolidated [shared.timing] section in jurisdiction.toml (feat/shared-timing-section).

  • #431 canopy-snap SUA tier selection (fix/canopy-snap-sua-tier-selection; PAMMS 3617).

  • #406 #415 E3 small bundle (feat/e3-bundle-415-406); #391 closed as already-done.

  • #413 #419 #421 ATO evidence + data models + screenshots docs trio (docs/docs-trio-413-419-421).

  • End-of-session docs sweep (chore/docs-sweep-end-of-session).

Tier A follow-up bundle (2026-05-14):

  • #451 canopy-eligibility infer utility_tier from expense profile.

  • #452 canopy-mq per-queue DLQ-depth metrics for Prometheus.

  • #453 canopy-web per-program pending-action counts on dashboard cards.

Test-seed harness refactor (2026-05-14):

  • #450 three-layer fix: single source-of-truth seed call, deterministic replay via test-results/seed/last.txt, stable fixture surface at tests/e2e/lib/fixtures.ts. Always-reseed + TRUNCATE-CASCADE before INSERT.

Tier 2 sweep + operational hardening (2026-05-14 second pass):

  • MR !282 (docs/end-of-session-sweep-2026-05-14) — Antora nav gains ADR-018 + ADR-019; this Phase E.3 section authored; services.md gains 3 missing endpoint rows + WIC appointments table; #455 filed for missing canopy-tanf section.

  • MR !283 (feat/tier-2-bundle-445-447) — Closes #447 (canopy-appeals consumes partial_retention in continued-benefits overpayment math; SnapBenefitIssuance.retained field + LEFT JOIN in list_issuances_for_household + filter in compute_overpayment + 3 new unit tests) and #445 (manifest form_number audit comment block: 0 CONFIRMED / 8 UNVERIFIED / 7 SYNTHESIZED; values not changed since they flow to rendered PDFs).

  • MR !284 (chore/precommit-subagent-verification) — .githooks/pre-commit gains an AI-agent verification block printed before Q1-Q8: non-trivial commits must answer from a fresh Explore subagent’s findings against the staged diff, not from primary memory. Conditioned on "If you are an AI agent that made these changes" — no human-committer impact.

  • MR !285 (docs/data-models-fill-13-services) — Closes #454. Fills in the 13 service data-model ERDs to canopy-caps reference depth (+2,591 LOC across canopy-applications, canopy-snap, canopy-tanf, canopy-medicaid, canopy-enrollment, canopy-renewals, canopy-appeals, canopy-notices, canopy-persons, canopy-reporting, canopy-rules, canopy-security, canopy-wic). Authored by 3 parallel agents against migration SQL; verifier subagent confirmed 13/13 PASS. First commit to exercise the new precommit-hook rule.

Phase E.4 — Operational follow-ups + outbox-drainer correctness (Done 2026-05-15 through 2026-05-18)

Series of ops-correctness fixes triggered by validate flakes that traced to structural antipatterns in the canopy-mq layer, plus the canopy-test-lib port’s final close-out.

canopy-test-lib port closure (2026-05-15 → -16):

  • MR !322 (feat/phase-e-flake-fixes) — Phase E ephemeral-schema backfill across 3 direct-pool integration tests (capability_flag_test.rs, orchestrator_dispatch_test.rs, fti_audit_hash_chain_test.rs); Phase D primitive hardening (EphemeralSchema::cleanup(self).await sync DROP, sweep_orphans(base_url) static helper, schema-name suffix switched UUID v7 → v4 for collision robustness); xtask .ports.env auto-reconcile in both auto_refresh and ensure_ready cold-start so every validate/test/e2e invocation gets a coherent ports file before any test reads it; IdempotencyCache::with_pool CREATE INDEX race fixed via pg_advisory_xact_lock(IDEMPOTENCY_DDL_LOCK_ID=9999) (since each service runs its own sqlx::migrate!, layering canopy-api’s migrator on top breaks _sqlx_migrations validation; advisory-lock + raw DDL avoids the conflict).

  • MR !323 (fix/canopy-rules-coalesce-audit-outbox-tx) — canopy-rules record_audit + publish_evaluation_completed coalesced into a single tx around the eval call. Integration suite 12.4s → 8.2s (-34%) under workspace nextest. Wired through Publisher::publish_tx-shaped pattern as a pilot for the broader publish_tx migration on the chore branch.

  • MR !324 (chore/436-step-10-closeout) — closes #436 (canopy-test-lib port). Plan archived to plans/archive/; Testing gains durable sections for every primitive the port introduced (EphemeralSchema + cleanup/sweep contract, EvilLayer + SpanCapture + time-mocking + goldenfile + insta + per-service typed clients). Net: contributors no longer need to read the archived plan to know which test pattern to reach for. Also includes #476 SOPS-empty-stdout fallback.

outbox-drainer lease refactor (2026-05-17 → -18):

  • MR !325 (docs/outbox-drainer-lease-plan) — diagnostic plan + .diagnostics/pg_wait_event_poller.sh for the held-tx-across-broker-roundtrip antipattern discovered when validate kept failing on 10s timeouts in canopy-tanf/canopy-snap post_determine tests. pg_stat_activity polling at 100ms captured drainer sessions sitting idle in transaction on per-row UPDATE event_outbox SET published_at = now() while domain sessions waited on COMMIT — multi-second WAL writer serialization. NOT hardware / NVMe / Keycloak / pool starvation (all four chased and ruled out); the drainer’s drain_once opened one Postgres tx around N RabbitMQ publishes per batch.

  • MR !326 (fix/outbox-drainer-lease) — closes #478. Three-phase lease-based drainer: Phase 1 claims rows via a CTE (FOR UPDATE SKIP LOCKED inside the CTE; outer UPDATE stamps two new claimed_at / claimed_by columns), Phase 2 publishes outside any DB tx with channel-per-batch publisher confirms (confirm_select once, pipeline up to CANOPY_MQ_DRAINER_PIPELINE_DEPTH deep, mandatory deliberately not set), Phase 3 marks results in two short bulk UPDATEs both guarded by claimed_by = $drainer_id. attempts increments only on per-message publish failure — crashed drainers' reclaimed batches don’t inflate the counter. Lease recovery via the next claim cycle, not the janitor. 19 byte-identical forward-only migrations adding the lease columns + a partial index. 4 in-source lease_tests (lease reclaim with attempts unchanged, happy-path bulk confirm, deserialise-failure path, two-drainer no-double-publish). 3 new env vars (CANOPY_MQ_DRAINER_BATCH_SIZE / LEASE_TTL_SECS / PIPELINE_DEPTH) with DrainerConfig::from_env asserting boot-time invariants. ADR-018 amended. Plan archived per ADR-013.

publish_tx + sqlx::migrate centralization (2026-05-18):

  • MR !327 (chore/centralize-sqlx-migrate-bootstrap) — closes 473 + #477. (a) canopy_api::bootstrap takes a sqlx::migrate::Migrator parameter; all 19 services pass sqlx::migrate!("./migrations") and drop the explicit .run(&*boot.db) prologue. Closes the gap that produced #471. (b) All Publisher::publish callsites migrated to publish_tx — 13 services, 55 sites — collapsing two separately-fsync’d COMMITs per request (domain write + autocommit outbox INSERT) into one atomic tx. Closes the silent atomicity gap where an audit row could land without its event when the autocommit hiccupped. IPV impose_disqualification collapsed 5 fsync’d COMMITs to 1. (c) dispatch_is_parallel_not_sequential test refactored from wall-clock budget (1100ms) to SpanCapture-based assertion observing orchestrator.dispatch start/complete events; insensitive to mock delay, DB latency, JWS verify cost; doubles as a permanent production observability signal. 5 [allow(clippy::too_many_arguments)] replaced with named-field structs along the way; .githooks/pre-commit uses git rev-parse --git-dir for worktree compatibility.

  • Issue #479 (workflow::needs-spec) — per-commit performance baseline storage filed as follow-up. Architecture: .perf/baselines.jsonl (append-only, one row per main commit), cargo xtask perf check/chart/record (Rust-native HTTP harness for prepush gate + SVG chart generator), Antora trend page rendering from the JSONL. k6 in CI for fine-grained detection on bounded hardware; criterion microbenches for pure-CPU code paths. Discussion concluded k6 in prepush is structurally wrong for "subtle change" detection due to dev-workstation noise floor + time-budget conflict — telemetry belongs in CI / dashboards, not gates.

Phase E.5 — Chaos epic closure + #460 worker portal redesign epic & planning (Done 2026-05-19 through 2026-05-21)

Two large architectural deliverables back-to-back: closing the chaos observability epic that had been blocking deterministic assertions in 3 of 4 chaos tests, then launching the worker portal redesign as a 21-child epic with ratified architecture.

Chaos epic &50 closure (2026-05-19):

  • MR !333 (#480) — cross-process chaos observability harness. canopy_test_lib::chaos module + 2 helper functions (spawn_jwks_provider_for_chaos, spawn_outbox_drainer_for_chaos) that spawn production JwksProvider / OutboxDrainer in the test process pointed at EvilLayer-wrapped endpoints. Unblocks 3 chaos contracts (#481/#482 + multi-replica work) previously blocked because SpanCapture::install_scoped is thread-local and could not observe events fired inside devstack containers.

  • MR !334 (#481) — JWKS chaos contract. 5 target: "jwks" emit sites in canopy-auth/src/jwks.rs; refresh() refactored from ?-shortcut to match so both success and failure paths fire structured events.

  • MR !335 (#482) — outbox chaos contract. 4 target: "outbox" emit sites in canopy-mq/src/outbox_drainer.rs. AMQP-transparent EvilLayer for transient-failure injection deferred (evil_proxy is JSON-only).

  • MR !336 (#483) — durable chaos-observability docs + runbook. docs/modules/ROOT/pages/runbooks/chaos-observability-contract.adoc with step-by-step adding-new-contract process; ADR-020 documents the strategy decision (in-process production fixtures over OTEL export vs Docker log scraping); Testing + Shared Crates cross-linked. Closes epic &50.

#484 Phase 1 — CHANGELOG render hygiene (2026-05-19):

  • MR !337 — render-bug hygiene sweep in CHANGELOG.adoc (escaped #NNN issue refs to avoid AsciiDoc #text# mark collisions; passthrough ... around code-like strings with underscores/asterisks; list-continuation replaces indented-Markdown sub-bullets). 19 <mark> artifacts + 3 broken-code-em + 5 broken-code-strong + 7 literalblock divs all eliminated. Issue stays open; Phases 2-4 (terse-bullet entry reflow + {issue-base}NNN attribute links + ADR ratifying the convention) pending.

Epic &51 — worker portal redesign (2026-05-19 through 2026-05-21):

  • MR !338 (commit ecfab75) — epic &51 filed at gadhs/application/eligibility + 20 child issues #485-#504 filed via glab issue create and linked to epic via epic_id. Plan at docs/modules/ROOT/pages/plans/worker-portal-redesign.adoc (initially landed pre-epic-filing; updated with epic + child issue table + ADR numbering coordination note). Originated from a May 2026 design exploration; all load-bearing design context now lives in this plan + ratified ADRs. 21 children total weight 73; 7 stages.

  • MR !340 (commit 5716d45) — design decisions from design’s review locked durably. ADR-021 Stage 1 primitive list extracted as-is (the 8 primitives all read from --orchard-* variables, palette via !295 didn’t change their internals); ADR-021 Stage 5 case-detail shell strategy is jurisdiction-selectable per role via [shell.<role>] TOML (NOT progressive enhancement at viewport breakpoints). Georgia defaults to strategy = "tabs"; scroll and card_grid ship as opt-in. NEW child issue #505 Stage 1.5 (panel-state primitives upgrade) filed — 4 utility classes upgrade to first-class Askama-macro primitives (EmptyState / Skeleton / SkeletonRow / ErrorBlock). Epic carries 21 children / weight 73.

  • MR !341 (commit 990250e) — E2E subscribe-after-fire race fix (closes #506). tests/e2e/specs/workflow.spec.ts:32-40 previously used await page.click; await page.waitForResponse anti-pattern; htmx kicks off the XHR synchronously so the response could land in-browser BEFORE waitForResponse attached its listener. Three contextless subagent investigations converged on the root cause (Subagent 3); Subagent 1 ruled out appeals-internal code-path race; Subagent 2 corrected my "validate-sweep contention" framing (Playwright and nextest do NOT run concurrently during prepush). Real fix: Promise.all([waitForResponse, click]) applied across 4 anti-pattern surfaces (workflow.spec, lib/helpers.ts::clickTab, 5 instances in case-search.spec, lib/pages/case-search.page.ts::search). Reverted misleading 30s → 60s timeout bump from afdb2f7. The 10s → 30s (2026-05-14) → 60s (2026-05-19) journey was a series of band-aids on a real race that the user caught: "just because something passes intermittently doesn’t mean that there isn’t a race condition somewhere. This is dedicated hardware."

  • MR !342 (commit 0a1d743) — ADR-021 ratifies composability runtime + plugin model. Three decisions locked: (1) Sandboxing posture — Option A′ in-process trusted Askama partials behind a PluginSource trait; v1 ships only CompileTimePluginSource; v2 federation (WasmPluginSource etc) is additive. (2) Plugin discovery — #[canopy_plugin] proc-macro + linkme distributed slice with compile-time Plugin.toml ↔ Rust handler signature validation. (3) Composition reload — invalidate-on-write single-replica v1; Studio writes invalidate the in-process cache; multi-replica RabbitMQ-fanout deferred post-UAT. Plugin.toml schema specified; composition loader contract load_composition(jurisdiction, role, user_id, surface) → Result<ComposedSurface, CompositionLoadError>; role filter applies AFTER override merge (silently drops items the role can’t use). Three open design questions routed via #486 comment.

  • MR !343 (commit 3865b88) — ADR-022 ratifies composition override storage layering. Four decisions locked: (1) Schema — unified composition_documents table for all three DB-backed layers, polymorphic scope_key (user UUID / role slug / 'jurisdiction' sentinel) with app-layer validation. (2) Merge — RFC 6902 JSON Patch op lists; Studio "add one panel" maps to one {"op":"add","path":"/items/-",…} op; test ops support optimistic concurrency. (3) Lifecycle — explicit Studio Archive after promote-merge (no canopy-core-repo watcher in v1). (4) Audit retention — uniform 1-year for all override-layer events, every write emits a JWS-signed AuditEvent per ADR-014. Forward-only schema per ADR-016.

  • MR !344 (commit f77bf9b) — Stage-2 ADR-023 deferred; #507 filed for unified canopy config backend ADR. User reframe during ADR-023 drafting recognized composition isn’t a special config concern — canopy has 7 config domains today (service config / secrets / jurisdiction policy / rulesets / theme / IDP / composition), each handling backend concerns (filesystem vs git vs HTTP, reload, versioning) independently. Composition is the 7th instance of an under-generalized pattern. The right shape is a shared ConfigBackend abstraction (plumbing) with domain-specific overlays (precedence, audit, mutation) staying per-domain. Filed as #507. Composition v1 ships using existing filesystem rulesets/{juris}/ pattern; Studio promote affordance descoped from v1 (admin uses jurisdiction’s existing baseline workflow external to canopy). #488 + #492 closed-deferred. #500 weight 5 → 3. Epic Stage 2 closes at 2 of 3 ratified; epic carries 19 active children / weight 64.

Net epic state as of 2026-05-21: Stage 1 (#485 design system extraction) is workflow::ready and unblocked. Stage 1.5 (#505) non-blocking follow-up. Stage 2 closed at 2 of 3 ADRs ratified + 1 deferred to #507. Stage 3 has 3 active issues unblocked by ADR ratification (#489/#490/#491). Stages 4-7 await Stage 3.

Phase E.6 — Epic &51 Stage 1.5 / 3 / 4 / 5 deliveries + Epic &52 ADR ratification (Done 2026-05-21 through 2026-05-23)

Eight MRs over three days closing the foundational + dashboard-surface deliverables of the worker portal redesign epic. Stage 6/7 + Stage 5 MR4 case detail (#497) remain.

Stage 1.5 (#505) — panel-state primitives (2026-05-21, !350): 4 macros (empty_state / skeleton / skeleton_row / error_block) wrap existing utility classes. 19 of 20 consumers migrated. Server-side branching on Err(_) from canopy-persons; no htmx-response-targets dependency. 36 unit + 4-test panel-states.spec.ts.

Stage 3 MR1 (489 + #490, 2026-05-21, !351): NEW crates canopy-composition (5-layer composition resolver: system defaults via LazyLock<Value> → jurisdiction baseline TOML via RFC 7396 → DB-backed jurisdiction_live → role → user via RFC 6902) + canopy-plugin-macros ([canopy_plugin] minimal proc-macro). Forward-only migration creating composition_documents + composition_documents_archive. Georgia shell-only baselines + roles-only idp.toml. CompileTimePluginSource empty in MR1 (real plugins land Stage 5). 63 unit/integration tests. Drive-by fix: closed #511 (deny-modal x-transition race — 4-line CSS transition addition; root cause traced via 5 contextless subagents converging on pre-Stage-1 strict-CSP refactor b4ee102 that externalized inline styles to utility classes but didn’t preserve transition timing).

Stage 3 MR2 (#491, 2026-05-22, !352): 11 HTTP live-override APIs on canopy-web. RFC 7232/6585-clean PUT preconditions (If-Match / If-None-Match: * / 428 when missing / 400 when both); PATCH validates application/json-patch+json Content-Type before body deserialization (415 not 400). Per-row strict ETag monotonicity via SQL GREATEST(clock_timestamp(), updated_at + interval '1 microsecond'). Atomic mutation audit via publisher.publish_tx (outbox row commits with composition row, so the JWS hash chain integrity per ADR-014 extends across composition mutations without special-casing). Surgical cache invalidation: new invalidate_user(juris, user_id) for /user/me writes. JSON-aware session extractors share HTML BFF refresh semantics via resolve_worker_or_fail helper. canopy-web restructured as lib+bin. OpenAPI JSON-only (Swagger UI descoped in v1 due to strict CSP). 53 new tests.

Stage 4 MR1 (#493, 2026-05-22, !353, force-merged via glab API after cancelling pipeline): N-OIDC genericization aligned with CRAIG. rulesets/{juris}/idp.toml gains table-array + [local_accounts] table. v1 ProviderType narrowed to keycloak | oidc-generic; full multi-shape claims deferred to #515. NEW canopy-auth constructors: from_discovery_with_client (shared reqwest::Client) + from_split_discovery(external, internal, http) (iss from external, jwks_uri from internal — split-DNS correctness). NEW canopy-web IdpRuntime with per-IdP Arc<JwksProvider>; 3-rung startup ladder (multi-IdP / synthetic-single-IdP / empty). OidcConfig DELETED. /auth/callback validates via per-IdP JwksProvider + copies idp_slug into SessionData before OAuth-flow cleanup so /logout (4-branch tree) + slow-path refresh (Extension<Arc<IdpRuntime>>) find the right entry. Parse-time validation (slug regex/uniqueness/reserved, audience required, domain @ prefix). 7 follow-ups filed: #512 (SAML federation spike), #513 (local accounts password auth), #514 (introspection mode), #515 (multi-shape claims), #516 (multi-jurisdiction sign-in), #517 (cargo xtask identity verify), #518 (env-var retirement).

Stage 4 MR2 (#494, 2026-05-22, !354 / 786ea96): Sign-in template + chip-list + email-first IdP discovery (htmx hx-get="/v1/auth/discover" hx-trigger="input changed delay:300ms"). 3 inline-SVG IdP icon macros. New .idp-chip CSS (~60 lines, data-attribute selectors binding to Orchard tokens — matches existing .status-pill[data-kind] precedent; no inline styles). 4 handlers (sign_in_page / discover / select / local_login_stub) with pre-resolved ChipView view model (Askama 0.15 doesn’t allow method calls in templates — see reference_askama_015_canopy). Zero-IdP renders o::empty_state BEFORE .card wrapper to avoid double-nesting. is_safe_return_to rejects open-redirects. 142/142 canopy-web tests + 4 new unit tests.

Stage 5 MR1 (495, 2026-05-22, !355 / 7ae3f7f): Composition-driven 12-panel worker dashboard — first canopy-web surface consuming the Stage 3 composition runtime. 12 panel plugins registered via [canopy_plugin] + linkme; each panel is its own #[derive(Template)] struct rendered to a String in Rust and embedded in templates/dashboard/worker.html via {{ panel.html|safe }} (sidesteps askama {% include %} parent-context limitation). Georgia baseline at rulesets/georgia/composition/worker_dashboard.toml declares all 12 with row/span per ADR-021 breakpoint set; system defaults stay empty (jurisdiction baselines overlay them via RFC 7396). New services/canopy-web/src/dashboard/ module (panels/ with 12 + unknown_panel, role_map, util). 6 panels wire existing endpoints; 6 ship state = "empty" placeholders pending FU-1..FU-6 (#519-#524). Drive-by Stage-4 MR1 fix: IdpRuntime::build Rung 1 now applies CANOPY_WEB__OIDC_* env vars as in-memory overrides for the matching idp.toml entry (Stage 4 MR1 had hardcoded host.docker.internal:8180 in idp.toml which broke OIDC discovery on every devstack reload). 164/164 canopy-web + 95/95 canopy-composition tests pass. FUs #519-#530 filed.

Stage 5 MR2 (496, 2026-05-22, !356 / fbc5e29, force-merged): Supervisor + analyst dashboard surfaces via new surface_for_role(&WorkerRole) free fn + DashboardTemplate enum + 3 sibling [derive(Template)] structs sharing one _panel_grid.html Askama macro (FU-16). Sidebar + topbar nav adapt to worker role via new worker_role_slug: String context field plumbed through 11 base.html-extending templates. Worker role display strings are human-readable (FU-19); topbar gains Notices + Appeals to reach sidebar parity (FU-20). Renamed WorkerRole::QualityControl::Analyst across enum + Keycloak realm + idp.toml + Plugin.toml + tests (FU-15). 8 new panel plugins (5 supervisor + 3 analyst); 2 Georgia baselines; 3 dashboard templates. Real upstream endpoints: canopy-tanf /v1/tanf/sanctions/rollup (FU-9), canopy-reporting /v1/reporting/overpayments/summary (FU-10), real fetchers for team_queue (FU-7) + pending_hearings + analyst_case_search. 9 of 14 FUs landed in-MR; 5 deferred with scope-corrected comments. Composition audit envelope carries 2 new surface values. 185+ tests. New Playwright projects (supervisor, analyst).

Stage 5 MR2.1 (2026-05-23, !357 / a24856f) — drive-by visual fix + 4 design primitives: .worker-dashboard-grid 12-col CSS grid honoring [data-span="1..12"] (without it, all 3 dashboards collapsed to a single-column vertical stack regardless of declared spans; mobile <1024px collapses to single col). Supervisor Overpayment roll-up 403 fixed: canopy-reporting::get_overpayments_summary swapped require_supervisor_or_aboverequire_service_or_caseworker_or_above (matches canopy-tanf::get_sanctions_rollup); the service-class token canopy-web fans out with now passes. 4 new design-system primitive Askama macros (delta / editorial_flag / program_stripe / program_tag) matching canopy-design package’s primitives.jsx contract. Companion --sp-1..--sp-10 + --r-sm..--r-3xl CSS tokens.

Epic &52 / ADR-023 (2026-05-23, !358 / b8bd27a): ADR-023 OIDC validation at service boundaries + citizen-upload isolation (amends ADR-019). Preemptive security amendment routed to a new epic before Stage 5 MR3 (the ADR-019 service-class-token model assumed program services are reachable only by trusted internal callers; that assumption breaks once canopy-portal ships citizen document uploads per ADR-008). Decision: push OIDC validation to every program service via the shared canopy-auth Axum middleware (discovery-based config, normalized claims, IdP adapter pattern preserving Keycloak/Authentik/Kanidm/Zitadel portability); RFC 8693 token exchange for user-context requests with per-(request, audience) caching; narrowly-scoped credentials for citizen-upload processing; service-class credential narrowed to background/scheduled work only; auth.token_exchange audit events extending the ADR-014 hash chain. Migration sequenced over 3-6 months calendar (FTI-touching services first). Stub remediation plan at docs/modules/ROOT/pages/plans/oidc-at-services-and-citizen-upload-isolation.adoc with 8 steps + architect-input flags + off-ramps. No code in this MR — ADR + plan only. Placeholder issue #546. 5 architect-input flags await user decision.

Stage 5 MR3 (#498, 2026-05-23, !359 / 8e53848, force-merged): Customize My Dashboard. New GET /dashboard/customize page + sidebar / topbar nav link. New DELETE /v1/composition/{surface}/user/me (idempotent reset). New ADR-024 ratifying the user_delta_v1 semantic envelope (amends ADR-022): {"type":"user_delta_v1","hidden_slugs":[…​],"span_overrides":{slug:span},"slug_order":[…​]} body on user-layer composition rows for dashboard surfaces only (worker_dashboard / supervisor_dashboard / analyst_dashboard); case_detail and other surfaces continue using RFC 6902. Loader shape-detects per (layer, surface) at the new post-role-filter apply step. HTML5 native DnD + keyboard pickup (Space + arrows). 12 Playwright specs × 2 color schemes (28/28 incl. critical + serious axe AA). Bonus deliverables from the mid-implementation design-mock review (the user redirected me to Playwright-screenshot the in-flight page vs the design package mock + flag any deviations on already-completed work): theme system expanded from 13 → 23 tokens per design-system reference §3.1 — semantic -bg / -text triples + text-body + border-soft + nav-bg / -muted / -muted-dim; auto-theme data-theme="system" half-state bug fixed structurally (chrome migrated from [data-theme="dark"]-scoped overrides to first-class palette tokens — fixes the OS-dark + auto-mode mismatch); pre-existing dark-mode nav-chrome WCAG AA contrast failure fixed (was #a8d5c8 on #3a9080 = 2.37:1 → now #7faa9a on #0e1612 = 7.5:1); orphan cy-btn--* classes (zero CSS rules) purged in favor of established btn btn-primary / btn btn-ghost. 10 follow-ups filed before commit: #547 (pinning), #548 (required-panel lockout), #549 (touch DnD), #550 (CSRF + session ID rotation on auth), #551 (role-change row cleanup), #552 (global csrf meta + htmx listener), #553 (Fluent panel titles), #554 (live cell previews), #555 (Preview-as-worker button), #556 (locked-item tooltip). Plan: docs/modules/ROOT/pages/plans/archive/worker-portal-redesign-stage5-customize-my-dashboard.adoc.

Stage 5 MR3 follow-up — humanize panel slugs + case numbers (2026-05-24, !361 / 732c340, force-merged): Mid-implementation Playwright screenshot review (user redirect: 'just look at your screenshots, you’ll see where it populates') surfaced that the customize page cells, dashboard panels (my_queue / recent_applications / recent_determinations / recent_notices), the notices list, and 4 other surfaces were leaking raw compose-time slugs (worker-dashboard-my-queue-panel) and UUID-prefix truncations (HH-018cc251 repeated for every row of 10 visible cases on the dashboard) as visible text. Two new helpers in canopy-web::dashboard::util: humanize_panel_slug (strips surface/-panel affixes + Title-Cases + keeps known acronyms IEVS / SNAP / TANF / etc. uppercase) and presentational_case_number (takes the last 8 hex chars of a UUID — random bytes for both v4 and v7 — instead of the first 8 which is the timestamp prefix that collides across UUID v7 inserts in the same millisecond bucket). 12 sites migrated. 9 new unit tests. All 28 customize Playwright specs × 2 color schemes still green. 4 new follow-ups filed before commit + linked to Epic &51: #557 (UI identifier-leak sweep — medium-severity items: snake_case notice types, FU-N placeholder copy, at-a-glance / my-queue stat mismatch, audit-events panel showing framework telemetry), #558 (ADR-007 parity: move helpers to canopy-common + add case_number field to backend response structs so CLI sees the same value), #559 (architectural spike: canopy-workers / canopy-identity service consideration — user later proposed cookies + canopy-security audit API as the cleaner long-term path), #560 (responsive design: sidebar dominates + tables overflow below ~900px viewport, surfaced via Playwright capture at 1920 / 1440 / 1024 / 768 / 375).

Net epic state as of 2026-05-24: Epic &51 is 14/35 children closed (14 new follow-up issues filed across MR3 + the humanize/case-number sweep; all linked to the epic). Remaining open (21): Stage 5 MR4 #497 (case detail with 3 shell strategies + 13 section types; weight 8), Stage 6 #499 / #500 / #501, Stage 7 #502 / #503 / #504, plus 14 follow-ups #547-#560 (mix of feature, refactor, spike, ux, security; all priority::low except #550 priority::high security + #557 / #558 / #559 / #560 priority::medium). Stage 6 Plugin Studio (#501) needs design questions resolved first (routed via #486 comment). All composition runtime (Stage 3) + auth (Stage 4) + 3 dashboard surfaces (Stage 5 MR1 / MR2 / MR3) are on main. Epic &52 ADR-023 awaiting 5 architect-input flags.

Phase E.7 — Demo dataset sequence (Done 2026-05-26 through ongoing)

Five MRs over one day standing up a hand-curated demo dataset for the 2026-05-28 stakeholder demo. The random-seed default emits 71 orphan cross-service refs out of 300 sampled rows; the demo profile reaches 0 orphans across 1,863 rows.

Plan (2026-05-26, !375 / 3020ef7): docs/modules/ROOT/pages/plans/demo-dataset-seed.adoc ratifies 24 archetypes × ~400 households × 13 SQL files with cross-service-ref ledger + drift gate + MR slicing (a/b/c/d).

MR-a (2026-05-26, !376 / db421ff): Plumbing. cargo xtask demo verify cross-service ref auditor (10 ref pairs, exits non-zero on orphans). cargo xtask seed --profile {default,demo} + --reset flags. DATABASES const expanded with canopy_verification + canopy_tanf + canopy_medicaid (previously silently skipped). Baseline measurement captured at docs/modules/ROOT/pages/plans/demo-dataset-baselines/random-seed-baseline-2026-05-26.txt (71 orphans / 300 rows).

MR-d 9c-A (2026-05-26, !377 / 18a6965): Cases-search status badges. New GET /v1/eligibility/case-status?household_id={hid} returns latest program_determinations row. canopy-web’s cases-search replaces hardcoded status: "Active" with parallel per-result fetches + 5-state color badges via pure render_status_badge (6 unit tests). Required for archetypes 4-7 (Denied / Sanctioned / Terminated / ABAWD-exceeded) to show real lifecycle.

MR-d 9c-B (2026-05-26, !378 / 08d9d6f): Auto-issuance subscriber. publish_determination_completed now carries monthly_allotment + effective_date via new DeterminationCompleted struct payload. canopy-enrollment’s determination.completed.snap subscriber reads both, uses real monthly_allotment (was Decimal::ZERO), and auto-creates the first snap_benefit_issuances row atomically in the same inbox transaction — closes Workflow 1 from the demo audit ("approve doesn’t issue benefits"). Pre-9c-B this required a separate /v1/enrollments/{id}/issue_benefits POST no demo-flow caller made.

MR-b (2026-05-26): Hand-curated 401-household dataset. New tools/canopy-seed/src/demo/ (personas + names + 8-phase orchestrator + sql_extras writers) + tools/canopy-seed/src/bin/demo.rs emit 13 deterministic SQL files (RNG seed 0xDE40_DA7A_5EED) into devstack/demo-dataset/. 24 archetypes (Marcus Williams, Sarah Johnson, …) × cohorts summing to 401 instances. New cargo xtask demo regenerate shells the bin; new cargo xtask demo check-drift diffs the regenerated tree (CI gate). Verifier: 10/10 cross-service ref checks pass, 0 orphans across 1,863 rows. Program-service-local rows (snap_applications / tanf_* / medicaid_* / caps_authorizations / wic_participants) deferred to follow-up #579 — not in the 10 verifier checks, worker portal cases-search queries through canopy-eligibility’s combined view.

MR-d 9a / 9d / 9e / 9b (2026-05-26): the four remaining demo-dataset-seed Step 9 surfaces shipped — Run Determination button on case-detail (9a, household-scoped; later superseded by Plan 1 MR4a’s per-application/per-program handler), Pending Hearings server-side filter (GET /v1/appeals/hearings/upcoming, 9d), the Action ▾ dropdown wired to five real handlers (9e), and the chain-through ?notice=eligibility-changed banner (9b). Per-MR detail in CHANGELOG.adoc.

MR-e — demo deferred-cleanup + polish (2026-05-26, feat/demo-deferred-cleanup): ~30 worker-portal demo bugs fixed (clickable case numbers, ?focus_section= deep-links, Process-button state-gating, friendly not-applicable placeholders, styled error pages) plus demo income rows (phase1b, ~333 earned_wages rows across the 24 archetypes). Closes #531 / #581 / #582 / #583; filed #584 / #585 as post-demo follow-ups.

Demo polish rounds 5–8 (2026-05-27) — five user walk-through passes hardening the recorded surfaces:

  • Round 5 (fix/demo-polish-audit-pdf-chips): SNAP on-demand PDF render 404 gap (3 templates missing ~23 inputs.* keys), WIC appointment-type humanizer, Audit Events domain-first display with telemetry toggle, Recent Applications outcome status, program-chip underline fix. #581 stays open for the S3-persist backfill.

  • Round 6 (fix/process-app-hero-banner-and-determination-amount): Process Application page — duplicated Run Determination button (htmx swap target), $0.00 income/benefit bugs (frequency-normalized member income + parse_decimal_amount for string-encoded Decimal), verification-panel layout + 5 canonical SNAP verification items (7 CFR 273.2(f)(1)), eligibility-verdict hero banner.

  • Round 7 (fix/case-detail-activity-tab-telemetry-filter): Activity tab telemetry leak — domain rows shown, composition.render / rules.evaluated gated behind a client-side toggle via the new shared crate::audit module. #598 (household filter) + #599 (self-as-"you") filed.

  • Round 8 (fix/list-humanization-and-demo-notice-subjects): humanize raw enum slugs across every list page via the new shared crate::format module; demo notice subjects regenerated from {program}: {slug} to readable headlines.

Cross-program-alerts auth fix (#590, fix/cross-program-alerts-worker-id-auth): GET /v1/eligibility/cross-program-alerts now enforces worker_id == token sub for caseworker / eligibility_specialist / quality_control (was a decorative param exposing the jurisdiction-wide list); supervisor / admin / service callers pass through. Result-set scoping by household_assignments landed with #596 (2026-08-19): the endpoint restructured into the assignment-scoped /v1/eligibility/workers/{worker_id}/cross-program-alerts + the supervisor-only /all view.

cargo xtask e2e --profile {default,demo} (#592, feat/xtask-e2e-profile-flag): the e2e runner no longer silently wipes a loaded demo dataset; --profile demo loads devstack/demo-dataset/.sql with auto---reset. *(Retired in #716 MR4e — the demo dataset folded into the default seed’s cast; the --profile flag and the committed devstack/demo-dataset/ tree were removed.)

Caseworker wishlist (filed 2026-05-27, #600–#608): stakeholder-sourced backlog captured as discrete issues — per-program case numbers, notice scoping, client-data isolation, Studio editing, shared-content single-source, trading-partner framework, batch elimination, work-tracking revamp, audit-log-level reporting. Queued for post-demo prioritization; not on the September UAT critical path.

Infra debt (filed 2026-05-27): #609 (xtask refresh SHA-gap) + #610 (pre-push validate flake-state — the seed-profile brittleness of cargo xtask validate). The sender-side signing-key-deletion facet once bundled with #610 (a cached service token outliving its deleted IdP signing key → determine-500) is a distinct root cause, split into epic &70 / ADR-037; #610 remains the seed-profile item.

Phase E.8 — SNAP+TANF+ELE demo-video three-plan split (Started 2026-05-27)

After the 2026-05-28 stakeholder demo landed, the team committed to a recorded 10-minute end-to-end video showing SNAP and TANF intake worked separately by different workers, then ELE auto-grant. The combined plan was rejected by external reviewer with 20+ findings (factual errors, internal contradictions, repo-drift). Re-scoped 2026-05-27 into three independently-shippable plans.

Three-plan split, all three now committed to Antora:

  • Plan 1 — Worker intake + program independenceCOMPLETE, archived 2026-05-28. 8 MRs, ~5,650 LOC: !390 (SectionName + SectionPayload + DocumentId contracts) → !391 (application_sections table + axe contrast fix) → !393 (Claims.primary_programs + Keycloak mapper + snap/tanf worker fixtures + plural ListParams + MyQueue rewire) → !394 (per-program Run Determination) → !395 (intake page UI, 9 SNAP / 10 TANF sections) → !396 (audit_events.household_id) → !397 (Audit section + ChainVerificationResponse contract) → !398 (multi-program intake demo seed + the load-bearing axum_extra::extract::Query fix for the MR3-era latent regression where serde_urlencoded 400’d on repeated Vec<T> keys, silently emptying MyQueue for per-program workers). Plan 1 alone yields the recordable SNAP-worker/TANF-worker independence cut (~4.5 min). Plan committed MR !387 (2026-05-27) after 7 reviewer-pass iterations + 2 external user-side passes (26 findings); implemented !390–!398 (2026-05-28). Plan 3 unblocker: MR1 (!390) shipped the SectionName + SectionPayload + DocumentId contracts first.

  • Plan 2 — ELE 1-year-flag expansionCOMPLETE, archived 2026-05-29. 7 MRs (#642–#648): #642 (ELE wire contracts + typed IDs + hash-chain primitive) → #643 (ele_consents/ele_status/ele_grant_events tables + store layer + advisory-locked hash-chain INSERT) → #644 (POST /v1/applications/{id}/ele-consent + Claims guard + consent subscriber — the Plan 3 dependency) → #645 (three federal JDM rulesets ele-grant-2026/ele-renewal-2026/ele-lapse-2026, ADR-003) → #646 (grant subscriber over service-token persons HTTP + deletion of the pre-Plan-2 hardcoded express_lane.rs tier ladder — the decision now runs entirely through JDM) → #647 (source-closure lapse folded into the canopy-medicaid.tma tanf.case_closed handler — one consumer group per event per service, the event_inbox event-id-only dedup constraint — plus admin POST /v1/ele/{person_id}/revoke) → #648 (advisory-locked daily renewal scheduler + GET /v1/ele/household/{id} + case-detail identity-hero ELE badge + admin POST /v1/ele/renewals/run). Durable 1-year flag (42 CFR 435.1102), hash-chain integrity per ADR-014. Demo seed carries ele_consents preconditions only — the durable flag is derived live by the grant subscriber, never fabricated into seed SQL. Follow-ups: #649 (consent-after-approval re-eval), #650 (provisional-value signaling), #651 (snap.case_closed publisher), #652 (persons.income_changed publisher), #654 (coherent-scenario seed generator — unblocks the full approve→grant→badge Playwright walk). v6 plan: 7 reviewer rounds (5 internal + 2 user-external; 26 issues caught by user that internal subagents missed).

  • Plan 3 — Applicant intake + verification (Dioxus 0.7+). 11 MRs (MR1 split into 1a/1b/1c), ~10,600 LOC. canopy-portal Dioxus 0.7+ rewrite + reference-number applicant auth (HH-[a-f0-9]{8} + word-word-word-NN passcode — NEVER DOB per user 2026-05-27) + document upload + verification round-trip + scripted IEVS/SAVE adapters + lost-credential recovery with 24h pending + side-channel notification + kill-switch (applicant-portal design ref §3.4-3.8 intimate-threat protection). 6 reviewer rounds (4 internal + 2 user-external; 11 blockers/P1s caught by user that internal subagents missed). v6 READY-TO-IMPLEMENT 2026-05-27. Depends on Plan 2 MR3 (POST /v1/applications/{id}/ele-consent endpoint).

Strict CSP regime locked per user 2026-05-27: wasm-unsafe-eval is the only -unsafe- directive allowed across the applicant portal. ADR-008 §214 (which permits 'unsafe-inline' for styles) is amended in Plan 3 MR1a. Tracking issue #630.

Predecessor: archived combined plan (superseded; not implementable as-written).

Meta-plan handoff: durable memory file at ~/.claude/projects/-home-bitskrieg-code-canopy/memory/project_demo_video_3plan_handoff.md carries the 20+ original findings + locked decisions + verification command blocks for each child plan.

Phase E.9 — Epic &52 OIDC-at-services program activation (2026-08-11)

The 2026-08-10 maintainer ruling on #546 (note 3666918785) activated the full ADR-023 program and resolved all five architect-input flags: full program now, foundations first; the citizen-path mechanism swapped from RFC 8693 exchange to a dedicated narrow IdP service account (ADR-026 opaque sessions mean no citizen token exists — recorded in amending ADR-043 riding the first implementing MR); sequential FTI-first rollout; per-request exchanged-token cache; Keycloak-only v1; X-Canopy-Actor retained until migration completes; mTLS post-migration stretch.

Plan rewrite + decomposition (2026-08-11): the <TBD> stub at the program plan replaced with the ratified, receiver-first program spec (typed EffectiveUser resolution over the fleet’s four incompatible no-actor readings; exact-audience
authorized-azp receiver contract; TokenExchanger broker with runtime output validation + audit-before-use; portal credential narrowing as a rotation sequence + ownership binding absorbing #665; conformance harness before the first flip). 34 child issues filed under epic &52 (#1418–#1451: 5 foundations, 2 realm/audit, 15 per-service receiver slices FTI-first, 3 portal, 4 cutover/closure, 5 honest-scope deferrals at T5), full blocks/is_blocked_by DAG wired, adjacent backlog swept (#665 re-pointed to #1442; #1008, #985, #874, #731, #1356, #512, #514, #515, #518 related). #546 closes with the plan MR as a decomposed placeholder. Docs-only MR — no product code, no realm changes; implementation proceeds through the epic’s DAG starting at #1418.

Phase E.10 — Epic &52 OIDC-at-services program COMPLETE (2026-08-17 through 2026-08-24)

The full ADR-023/ADR-043 program shipped in 13 days: foundations (F1a inventory 467 branches read-verified; F1b EffectiveUser; F2 policy primitives; F3 TokenExchanger broker with output validation + audit-commit-before-release; F4 conformance harness), realm wiring + A1 chain audit (R1 — hop-2 chained exchange proven live on KC 26.5), fifteen receiver slices FTI-first (every service on the ADR-043 §C ReceiverContract; user-only routes mechanically enforced), the portal isolation trio (P1 narrow per-target credential; P2 receiver-side citizen class + 12-scope portal:* vocabulary; P3 origin-verified X-Canopy-Applicant ownership binding — closed the #665 IDOR debt), and the C1 cutover (#1443: X-Canopy-Actor retired unconditionally — middleware 401s the header, ViaActor deleted, legacy service-token write arms drained fail-closed, the document review trio rides exchanged bearers). N1 consolidated the deployable realm contract in idp-integration (stand-up checklist + the honest no-exchange off-ramp); S6 closed the conformance matrix fleet-wide (no-slice proofs for rules/exchange, mixed-version kind retired, floor 1046). T1 (#1446) archived the program plan and closed the epic. End state: worker identity ONLY in tokens (direct or RFC 8693 exchanged); citizen identity ONLY in the narrow portal credential
signed ownership claims; service identity ONLY for system traffic. Honest-scope deferrals live on as standalone issues: FU-A #1447 (per-service audience for service-class tokens; relates #1571 admin-replay hardening), FU-B #1448 (nested-hop exchange + attribution), FU-C #1449 (citizen-content process isolation, needs-spec), FU-D #1450 (RFC 7009 revocation guidance), non-KC #1451 (non-Keycloak deployment notes).

Phase F — Plan Deferred-row trackers (post-UAT or pre-1.0; don’t pick early)

Externally bounded by UAT entry, 1.0 release, or post-UAT scope. Don’t graduate without their gating event.

  • #349 Workflow guidance panel UI — post-UAT, depends on canopy-portal Dioxus work.

  • #350 Compose-profile-matrix integration tests + CI — post-graduate; lift only when capability-flag tests miss something.

  • #351 Event-bus compile-time macro + CI lint — post-UAT defense-in-depth.

  • #352 OpenAPI contract testing — post-1.0 by design (pre-1.0 contracts are expected to break).

Phase G — Externally blocked (monitor only)

  • #189-#195 — FFE account transfer (7 issues), all priority::high, blocked on Georgia Access readiness.

Post-1.0 — Deferred to after first stable release

Item Reason deferred

OpenAPI contract breaking-change detection CI gate (6 steps)

Premature — breaking changes are expected pre-1.0; gate blocks iteration

ADR-011 migration: move cross-program constants from canopy-reference to federal JSON loaders

Pattern established (SnapParameterTable, MedicaidParameterTable); retrofit when stable

Go / No-Go Checkpoints

Week 4 (end of month 1):

  • canopy-persons CRUD endpoints working (POST /v1/persons, GET /v1/persons/{id}) — MR !6

  • canopy-rules evaluating stub SNAP ruleset (POST /v1/evaluate returns output) — MR !8

  • Determination signing compiles and passes key generation + sign/verify tests — commit eda958f

  • canopy-security captures and persists events from canopy-persons — MR !9

  • canopy-applications accepts SNAP applications (POST /v1/applications → 201) — MR !10

Checkpoint PASSED (March 2026).

Week 10 (end of month 2):

  • End-to-end determination: application → eligibility → snap → rules → signed determination

  • Gross income test working (130% FPL deny/approve)

  • Net income test with all 6 mandatory deductions

  • Asset test working ($2,750 limit)

  • Categorical eligibility: TANF cash receipt → auto-approved

  • ABAWD: month 3 exhausted → AbawdExceeded status

Status: COMPLETE (March 2026).

Week 26 (UAT entry):

All month 6 exit criteria met (see above). FNS-7176 QC extract validated. Worker portal caseworker walkthrough complete without errors.

Edit this page · default