Plan: Scenario Inventory & Human-Fidelity E2E (epic &61)
On this page
Implements ADR-031 §3 for epic &61 (parent &58), under the corpus architecture ratified by ADR-032: an engine corpus (synthetic test-min/test-max fixtures, universal + election-dependent scenarios, canopy CI) and per-jurisdiction conformance packs (Georgia first — the UAT suite and onboarding template). Consumes &60’s action catalogue (scenario steps reference catalogued actions; state-manual rows feed conformance scenarios) and &59’s currency assurance (conformance journeys assert against current values). Grounding below is code-verified (2026-06-09; ADR-032 amendments 2026-06-10). Issues are cut from the Status rows per ADR-013 once this plan lands.
Status
| MR | Description | Status |
|---|---|---|
MR1 (inventory schema + gate) |
Scenario-inventory schema in |
Done (2026-06-10) — |
MR2 (SNAP inventory) |
Author the SNAP scenario inventory — the policy-reading deliverable. Enumerate from 7 CFR 273 + PAMMS: the 273.12 change-type space (income up/down, member add/remove, address, shelter/utility, dependent-care, child-support changes — recon: today only a free-string |
Done (2026-06-10) — 5-slice wave: 201 engine rows (204 with MR1 seeds) + 20 Georgia conformance rows (PAMMS-procedural, |
MR3 (gap triage + remaining programs) |
TANF / Medicaid (incl. ELE/TMA/EE15 cross-program rows) / CAPS / WIC inventories, same discipline as MR2 (scope-tagged, runnability-mapped). State-provision scenarios derived from the pinned state manuals (DECAL 13-week job-search grace, DPH category-anchored cert expirations, Pathways…) land in the Georgia conformance pack ( |
Done (2026-06-10) — 4-program wave: TANF 84 / Medicaid 58 / CHIP 14 / CAPS 39 / WIC 49 / cross-program 13 engine rows + 111 Georgia conformance rows + 31 new registry options (45 total). Full inventory now 461 engine + 111 Georgia = 572 scenarios across all 5 programs; gate clean: 95 covered / 59 partial / 418 uncovered. TANF agent modeled the thin TANF federal floor (need standard, deprivation, sanction structure left to states per 42 USC 602) as registry option keys so |
MR4 (journey harness pattern) |
Depends on the generative-seed-harness plan (ADR-033) through its MR3 — do not start before the endpoint-driven given library + step-primitives exist; the work-list is #849-#854. Establish the multi-life-event journey pattern in the E2E harness: a |
In progress — slices 1–7 Done (2026-06-14): the |
MR5 (synthetic engine fixtures — supersedes the single- |
The adversarial pair: |
Done (2026-08-27) — the #761 MR (both SHAs in the issue’s closing receipt). Fixtures authored + adversarially verified (test-min 125/125, test-max 246/246 citations; audit clean over all five families); citation kinds ( |
MR6 (integration-test jurisdiction hygiene) |
Burn down the hardcoded |
Done (2026-08-27) — the #762 MR (both SHAs in the issue’s closing receipt); 50 literals swept across 20 files, swept-crate suites green on the default; the override var is the MR5 seam. |
MR7 (journey walkthrough pairing gate + UI-gap backlog — #972, ADR-031 Amendment 1) |
Realize the epic-&61 human-fidelity half: every covered journey must ship a human-followable Antora walkthrough paired with its |
Done (2026-07-05) — !<mr> ; gate + 22 |
Design — grounded current state (code-verified)
-
E2E harness: Playwright projects (
tests/e2e/playwright.config.ts); auth-setup chains 9 users; on-demand visual-baseline projects (vb-*) key offCANOPY_E2E_VISUAL_BASELINE(set byxtask e2e --visual); full-stack projects (journey, worker-determination-ele) offCANOPY_E2E_DEVSTACK_PROFILE=full. The longest specs prove dual-context (:8090applicant +:8080worker) and cross-service-event journeys already work. -
Coverage today is default-seed-driven: the generative seeder’s random bulk +
phase1bscenario-targeted households + thephase14_castlogin-capable applicants (the demo dataset was retired into the default seed in #716); 19 JDM math fixtures (2 shells:snap-eligibility,medicaid-non-magi—crates/canopy-rules-client/tests/ruleset_happy_path_test.rs:52-53); the spec suite. No artifact links any of these to the scenario space; JUnit output lists spec names, not scenarios. -
Change-reporting surface: endpoints exist (
POST …/change-report+ program-parameterized variants,canopy-renewals/src/api/mod.rs:545,677) butchange_typeis a free string and onlyincome_changemarksrequires_redetermination— most of the 273.12 space is recordable but not actionable; the inventory makes that visible per-type. -
Jurisdiction: threaded per-service at startup via
CANOPY_*__JURISDICTION(single-tenant per ADR-006); seed/e2e hardcode georgia;rulesets/default/is a verbatim georgia copy; the test literals (23/19 at recon, 46/19 by MR6’s landing) now route throughcanopy_test_lib::test_jurisdiction()(MR6, #762). The per-jurisdiction artifact set is known (georgia: 34 files — jurisdiction.toml, citations.toml, theme, idp, composition x5, workflows x9, notices tree, param JSONs).
Design — decisions
-
The inventory tracks scenarios, not personas. Personas are seeded instances; a scenario is the policy-derived situation class. A scenario row may bind to a persona (via the demo seed) as its fixture, but the inventory axis is the CFR/PAMMS-derived space, so "what’s missing" is measured against policy, not against what we happened to stage.
-
Three complexity tiers with tier-appropriate coverage:
unitscenarios are satisfied by JDM fixtures (cheap, exhaustive math edges);flowby existing-style specs;journeyonly by multi-life-event specs. The gate’s "partial" status (journey bound only to a fixture) prevents tier-laundering. -
Uncovered is a report, dangling is an error. A binding that doesn’t resolve is a lie and fails CI; an honest gap is the burndown metric (mirrors quality-budgets philosophy — debt visible and monotonically shrinking, not hidden).
-
Journeys are full-stack-gated, not pre-push. The pre-push battery stays fast; journeys run on the full devstack in CI and locally on demand (
cargo xtask e2e --devstack-profile full — --project journey). -
Two corpora, one schema (ADR-032). The engine corpus (universal + election-dependent scenarios against synthetic
test-min/test-max) answers "does canopy implement the federal option space?" and runs in canopy CI; conformance packs (rulesets/{jurisdiction}/scenarios/) answer "does this configured deployment behave per its policy?" and are each jurisdiction’s ship gate — Georgia’s pack is the UAT suite and the onboarding template. Georgia policy is deliberately NOT the engine corpus: un-elected option arms would go permanently untested, and real-value churn would rot the goldens. -
Synthetic pair, not a second real state (supersedes the earlier single-
testlandsketch). Adversarially-electedtest-min/test-maxfixtures exercise both arms of every referenced election (a single fixture cannot); round numbers + federally-cited elections keep ADR-011 discipline without importing a second state’s policy-reading cost. A real jurisdiction onboarding later inherits the conformance-pack path instead.
Verification
-
Gate unit tests (fixture inventory + fixture spec tree: resolving binding, dangling binding → error, tier-mismatch → partial; shell-fixture binding rejected).
-
MR4’s journey runs green in the demo pipeline; screenshots + the journey’s step assertions reviewed against the design renders where UI is touched (project screenshot-verify convention).
-
MR5: test-min + test-max smokes green in their scheduled job; at least one asserted test-min-vs-test-max behavioral divergence per elected option arm (proves the elections actually flow);
policy auditgreen over both fixtures (election keys cite the option registry, values cite the fixture-design appendix,fixturesource kind rejected outsiderulesets/test-*). -
Each MR through the standard gate (validate + D1-D8 + force-merge squash=false).
Appendix — fixture design (MR5, #761; ADR-032 §2/§4)
The design authority every authority = "fixture" citation in
rulesets/test-min/citations.toml and rulesets/test-max/citations.toml
references (source_ref = "plans/scenario-inventory-e2e.adoc#fixture-design").
The VALUES themselves live in the fixture files (the audit’s ValueMismatch
check keeps citation and file in lockstep); this appendix records the rules
that produced them and the election matrix.
Personalities
-
test-min — the smallest legal deployment:
enabled_programs = ["snap"]only (its jurisdiction.toml carries ONLY the snap + shared/jurisdiction/ notices/appeals sections — the ADR-005 degradation arm made concrete); every federal option DECLINED where law permits (no BBCE ⇒ live asset test, no simplified reporting, no heat-and-eat, no TSNAP, no ABAWD waiver, actual-cost self-employment, SUA mandatory, dependent care capped). -
test-max — every program enabled, every optional surface on, most permissive elections (BBCE at the 200% lawful ceiling with the asset test eliminated, simplified reporting, statewide ABAWD waiver, standard self-employment percentage, MAGI adult expansion adopted).
Value rules
-
Round numbers only — auditable by inspection, immune to indexing churn (money in whole hundreds of cents; percentages in fives/tens; day counts from {5,7,10,14,15,30,45,60,90,180,270}; months from {3,4,6,12,24,36,48,60}).
-
Federal floors/ceilings are inviolable and identical in BOTH fixtures (expedited 7 days, expedited screening $150/$100, ABAWD 80h/3-in-36, IPV 12/24 months, CHIP floor 134% FPL, TANF federal 60-month limit, WPR 50/90, two-parent 35/55 hours, core-hours 20, processing SOPs 30/7/30/45/45 days, appeal window 90 + decision clock 60 days; the SNAP minimum benefit 2400¢ — 8% of the one-person max allotment, 7 CFR 273.10(e)(2)(ii)©, a floor the adversarial review caught the first draft violating; BBCE gross spans exactly the lawful 130→200 range across the pair).
-
Wherever an election has an engine or service realization, the pair MUST differ on it — that difference is what the divergence assertions consume.
-
Identity strings are obviously synthetic (Test-Min/Test-Max Fixture Agency, 1-555-0100, Testville, fips 00/99, UTC; holidays are six round dates as OBSERVED weekday shifts — the workday calendar refuses weekend entries at load);
[policy_source] type = "manual", no repos (fixtures have no PAMMS). -
Structure-preserving tables: money/limit arrays keep georgia’s LENGTH and monotonic shape with round values; the adverse-action reason vocabulary and the TANF work-activity vocabulary copy georgia verbatim (product surface, not policy values).
-
Federal-file-mirrored keys keep the shared federal files' EXACT values — the snap param-set loader refuses cross-source divergence (#1467 C5; single-sourcing is #1478):
minimum_benefit_amount_cents2400,homeless_shelter_deduction_monthly_cents19900 (both caught live by the divergence tests, not the citation audit). -
Copied JDM files carry the FIXTURE’s jurisdiction prefix in their embedded
namefield where georgia’s carriedgeorgia-(services look up{jurisdiction}-<ruleset>); the federal alien-eligibility file’s wrongly georgia-prefixed name is pre-existing debt, filed as #1613.
Election matrix (compliance/federal-options/ ↔ fixture arms)
| Registry option | Realizing jurisdiction key(s) | test-min arm | test-max arm | Realization |
|---|---|---|---|---|
|
|
not-elected |
elected |
engine/service |
|
|
not-elected |
elected |
engine/service |
|
|
not-elected |
elected |
registry-only |
|
|
not-elected |
elected |
registry-only |
|
|
no-waiver |
waiver-in-effect |
registry-only |
|
|
not-utilized |
utilized |
registry-only |
|
none (registry-only) |
not-elected |
elected |
registry-only |
|
|
actual-costs |
standard-percentage |
engine/service |
|
|
full-ban |
opt-out |
registry-only |
|
|
mandatory-sua |
household-choice |
registry-only |
|
none (registry-only) |
deduction |
income-exclusion |
registry-only |
|
|
snap-standard |
tanf-rule-substitution |
registry-only |
|
none (registry-only) |
not-elected |
elected |
registry-only |
|
|
default-125 |
approved-plan-threshold |
registry-only |
|
|
not-applicable (program not enabled) |
federal-60-months |
engine/service |
|
|
not-applicable (program not enabled) |
elected |
registry-only |
|
none (registry-only) |
not-applicable (program not enabled) |
elected |
registry-only |
|
|
not-applicable (program not enabled) |
graduated-partial-then-full-family |
registry-only |
|
|
not-applicable (program not enabled) |
elected |
engine/service |
|
none (registry-only) |
not-applicable (program not enabled) |
reduce-at-least-25pct |
registry-only |
|
none (registry-only) |
not-applicable (program not enabled) |
opt-out |
registry-only |
|
none (registry-only) |
not-applicable (program not enabled) |
elected |
registry-only |
|
none (registry-only) |
not-applicable (program not enabled) |
required |
registry-only |
|
none (registry-only) |
not-applicable (program not enabled) |
mandatory-standard-filing-unit |
registry-only |
|
|
not-applicable (program not enabled) |
gross-ceiling-and-standard-of-need |
engine/service |
|
|
not-applicable (program not enabled) |
standard-work-deduction |
engine/service |
|
none (registry-only) |
not-applicable (program not enabled) |
elected |
registry-only |
|
none (registry-only) |
not-applicable (program not enabled) |
covered |
registry-only |
|
|
not-applicable (program not enabled) |
elected |
engine/service |
|
none (registry-only) |
not-applicable (program not enabled) |
special-income-level-elected |
registry-only |
|
|
not-applicable (program not enabled) |
elected |
registry-only |
|
|
not-applicable (program not enabled) |
no-demonstration |
engine/service |
|
none (registry-only) |
not-applicable (program not enabled) |
elected |
registry-only |
|
none (registry-only) |
not-applicable (program not enabled) |
none |
registry-only |
|
|
not-applicable (program not enabled) |
imposed-per-public-schedule |
engine/service |
|
|
not-applicable (program not enabled) |
85-pct-smi-maximum |
engine/service |
|
|
not-applicable (program not enabled) |
85-pct-smi |
engine/service |
|
none (registry-only) |
not-applicable (program not enabled) |
extended |
registry-only |
|
none (registry-only) |
not-applicable (program not enabled) |
waivers-elected |
registry-only |
|
none (registry-only) |
not-applicable (program not enabled) |
elected |
registry-only |
|
none (registry-only) |
not-applicable (program not enabled) |
elected |
registry-only |
|
none (registry-only) |
not-applicable (program not enabled) |
elected |
registry-only |
|
none (registry-only) |
not-applicable (program not enabled) |
elected |
registry-only |
|
none (registry-only) |
not-applicable (program not enabled) |
extension-permitted |
registry-only |
|
none (registry-only) |
not-applicable (program not enabled) |
retail |
registry-only |
not-applicable (program not enabled) rows are the program-subset
divergence itself: test-min proves the stack RUNS without those services
(ADR-005), which is the elected-arm assertion for every option of a
program test-min omits.
Divergence assertions (the "elections actually flow" proof)
Scoped to options with a live realization on BOTH sides of the pair — narrower than the MR row’s original "per elected option arm" phrasing, and deliberately so (a registry-only option has no realization to assert; recorded here per ADR-013 living-spec):
-
snap.self-employment-expense-method— the engine-registered SNAP divergence:SnapParameterSets::load(the REAL boot loader) yieldsstandard_deduction_enabledfalse/true and the elected 50% only under test-max (in-crate canopy-snap testfixture_pair_diverges_on_the_self_employment_election— battery-gated). These values feedse_deductionand thesnap-self-employment-deductionJDM. -
snap.bbce— runtime realization is the renewals gross screen:RenewalParams::loadyieldsgross_income_limit_pct130 (no BBCE, federal base) vs 200 (the lawful BBCE ceiling) — in-crate canopy-renewals testfixture_pair_diverges_on_the_bbce_gross_screen. (The engine’s asset-test thresholds come from the SHARED federal deductions file, not jurisdiction.toml — the loader REFUSES divergence on federal-mirrored keys, see value rule 6 — so the bbce booleans' remaining realization is the seed/scenario layer, as the matrix records.) -
Corpus bootability —
NamedFilesystemLoader::scanover[federal, test-*]scans clean and registers each fixture’s OWN jurisdiction-prefixed SNAP eligibility ruleset (in-crate canopy-rules testfixture_corpora_scan_clean_like_a_service_boot): exactly the load the rules service performs at boot. -
Program-subset options — existential: the scheduled fixture-smoke lane boots test-min snap-only and test-max full and both smoke green (sign-in, dashboard, one determination, one notice).
Boot seams this MR added
-
docker-compose.yml: every jurisdiction consumer reads${CANOPY_JURISDICTION:-georgia}(one export switches the stack). -
cargo xtask e2e --jurisdictionparameterizes the SEED (default georgia; the bare battery is byte-identical). -
canopy-notices falls back to
rulesets/default/noticestemplates when a jurisdiction ships none — fail-closed outsideCANOPY_ENV=devbehindCANOPY_NOTICES__ALLOW_DEFAULT_TEMPLATE_FALLBACK(#1265 doctrine); the fixtures deliberately ship no template tree. -
Citation kinds:
fixture(this appendix; staleness-exempt; rejected outsiderulesets/test-*) andoption_registry(source_ref must be a livecompliance/federal-options/key) — both audit-enforced.