Plan: Demo-ready, live-verified, dual-persona journey walkthroughs

On this page

Status

Step Description Status

0

Branch + this plan committed/nav-linked; D3 shared e2e helpers built; portal→worker hand-off smoke-tested

Done (2026-07-08) — smoke passed 10/10; helpers in tests/e2e/lib/portal.ts + addAddressViaUi.

1

Workflow A — parallel authoring: per-journey spec upgrade to dual-persona portal flow + walkthrough rewrite to the D2 format

Done (2026-07-08) — 10 journeys via Workflow A + #6 ele-grant authored by the primary (deferred-drain vs pre-determination-consent nuance).

2

Live walk every journey serially; fix-or-unpublish; capture dual-persona screenshots

Done (2026-07-08) — all 12 journey specs pass live (--project journey, 24/24); one selector fix (thirty-day-NOA My-Queue → direct case open); no journey unpublished; screenshots captured + committed.

3

Shared files (nav / index / snap.toml / CHANGELOG / Demo Runbook); scenarios audit; full battery; one MR

Done (2026-07-08) — Demo Runbook + index note + CHANGELOG + two inventory describe-label bindings; scenarios audit clean.

Epic: &61
Issues: #991 (retrofit shipped walkthroughs to the #979 concrete-precondition bar)
Branch: feature/991-demo-ready-journeys

Context

We have 11 shipped SNAP journey walkthroughs, authored against e2e specs that build every case through the endpoint-driven given-library (SnapCaseBuilder) — never the real applicant portal. So the human walkthroughs are caseworker-only, the applicant precondition is vague, and none have been walked by hand end-to-end — several are suspected broken/incomplete. A live demo needs a side-by-side applicant + caseworker experience for every published journey, plus a reliable reset between runs.

Goal: every published journey walkthrough is a complete, live-verified, dual-persona manual script a presenter can drive on demand, plus one Demo Runbook. Any journey that cannot be walked honestly and isn’t fixable in this MR (and isn’t externally blocked) is unpublished — we do not publish broken work.

Owner decisions: applicant side = live front-door + view; #9 = harness bring-up, #12 = dropped; verification = full live walk of every journey; orchestrate with workflows (parallel authoring; the live walk is the one serial bottleneck).

Review resolutions (round-1 findings → fixes baked in below)

Finding Resolution

C1 migrate rollback → xtask e2e collides (e2e always re-seeds reset:false, xtask/src/cmd/e2e.rs)

Two distinct loops. My automated verification runs each journey via cargo xtask e2e … --project journey (e2e owns seeding; byte-stable across consecutive runs — NO migrate rollback in this loop). The manual demo uses migrate snapshot/rollback — the presenter never runs xtask e2e, so no collision.

C2 portal helper on wrong origin/auth (journey project = worker :8080 + caseworker storageState; portal = :8090)

The journey page stays the worker page. The spec opens a separate applicant context browser.newContext() on the portal baseURL (CANOPY_PORTAL_CSP_BASE_URL/:8090, unauthenticated), mirroring `worker-determination-ele.spec.ts’s second-context pattern; the portal helpers take that page.

C3 householdId insufficient — worker helpers need personId/headPersonId; finalize returns only app+household ids

The portal helper adds a roster-resolution step: after finalize it calls GET /v1/households/{id}/full (canopy-persons service token) and returns {householdId, applicationId, referenceCode, passcode, headPersonId, memberPersonIds} (head = the member whose relationship is self).

H4 even householdId needs interception — the WASM client discards the finalize body; the UI shows only code/passcode

The helper waits for + parses POST /apply/finalize (page.waitForResponse, 2xx — finalize is 201) → FinalizeResponse{application_id, household_id}; then C3’s roster read. (Reference code + passcode still scraped from the post-submit reveal for the applicant re-login.)

H5 #6 ELE order wrong — grant fires from a deferred prior approval, drained by consent (journey-snap-ele-grant.spec.ts)

#6 order corrected: front-door (2 kids, no ELE opt-in) → worker Run Determination → Approved (defers the grant) → worker Record ELE consent → badge (drains the deferral, BEFORE re-determination)re-determine (re-affirms).

H6 global "every journey through the portal, both personas" contradicts #8/#9/#10

Tier-specific verification gates (see Verification): Fully-manual, Worker-driven, and Harness tiers each have their own acceptance criteria; notice-silent journeys assert no notice step.

M7 migrate rollback is DB-only (rabbitmq/redis/redis-sessions/garage/keycloak stay live)

Runbook states rollback = the Postgres DBs only; use a fresh browser context / incognito per journey (fresh applicant + worker sessions); in-flight events drain harmlessly; Garage PDFs accumulate harmlessly; Keycloak unaffected. Heavier pristine reset (dev clean --confirm && dev start && seed) documented for a cold start.

M8 runbook commands not real (cargo xtask prefix; seed defaults non-reset)

All commands prefixed cargo xtask …; explicit fresh (seed) vs idempotent (seed --reset).

M9 #4 drops seedAddress but changeAddressViaUi needs an existing row

Add addAddressViaUi to the shared helpers; #4 = worker adds the baseline address (the applicant portal drops the apply address at finalize) then edits it.

M10 #3 "file recert" overstates (records intent only)

The #3 walkthrough preserves the honest-scope caveat: filing records intent (filed_recert) and does not provision a recert application.

L11 screenshot path differs container vs host

Use cargo xtask e2e (container) so shots land in /e2e/results/walkthroughs/<j>/ → host test-results/e2e/walkthroughs/<j>/; the existing shot() OUT_DIR branch handles both. Commit-copy to assets/images/walkthroughs/<j>/.

Scope

In scope:

  • D1 — a new Demo Runbook (runbooks/demo-runbook.adoc, nav-linked).

  • D2 — a uniform dual-persona walkthrough format applied to every published journey.

  • D3 — shared e2e helpers + per-journey spec upgrades that drive the real applicant portal front door.

  • Live verification of every published journey; fix-in-MR or unpublish.

Out of scope:

  • #12 (ADH→IHE) — no walkthrough, no UI; tracked by #995. Nothing to publish or unpublish.

  • New applicant-portal capabilities (change reporting, appeals filing) — worker-driven beats stay worker-side (honest scope).

Governing rules (non-negotiable)

  1. One MR. No batching unless a fix is externally blocked (partner integration / large new subsystem). Test/doc hardening is not "external".

  2. Fix-or-unpublish. Live-walk breakage → root-cause → fix in this MR if fixable; if externally blocked, keep only if still honestly walkable (documented); else unpublish.

  3. Unpublish mechanics (reversible): move the page to docs/…/walkthroughs/unpublished/, remove its nav.adoc xref
    walkthroughs/index.adoc row, and in compliance/scenario-inventory/snap.toml swap the walkthrough binding for walkthrough_blocked_by = ["#<issue>"] (issue-backed → pairing gate stays green; scenario stays Covered [Journey] via its still-passing e2e-spec binding). File the breakage issue; note in CHANGELOG.

  4. No deferral of critical functionality unless externally blocked.

Design

D1 — Demo Runbook (new page runbooks/demo-runbook.adoc, nav-linked)

  • One-time bring-up: cargo xtask dev start --profile fullcargo xtask seed --seed 42 --households 50 (fresh stack) → cargo xtask migrate snapshot (~5-10s golden state) → cargo xtask dev status (read ephemeral ports).

  • Reset between MANUAL journeys: cargo xtask migrate rollback (~5-10s; pg_restore --clean per DB). DB-only (M7): also open a fresh browser context / incognito per journey for clean applicant + worker sessions; queued events + Garage PDFs accumulate harmlessly; Keycloak/ports unchanged. For a fully-cold pristine start: cargo xtask dev clean --confirm && cargo xtask dev start --profile full && cargo xtask seed --seed 42 (~minutes).

  • Credentials: worker table (all password; jane.caseworker) from user-testing-guide.adoc; applicant cast HH-ca570001..04 + passcodes from runbooks/demo-applicant-credentials.adoc; live-front-door applicants use the HH-…+passcode from the post-submit reveal.

  • Per-journey index table: journey | tier | exact starting point | link.

  • Gotchas: ephemeral ports (never hardcode); cross-program REQUIRES --profile full; do not git push during the demo (pre-push reseeds the live stack — re-snapshot after); SOPS/keycloak cold-start → prefer dev refresh; 30s JWKS debounce on first login.

D2 — Uniform dual-persona walkthrough format (all published journeys)

  1. Reproducibility tier admonition (Fully manual / Worker-driven / Harness).

  2. Personas & credentials — Applicant (/apply/lookup) + Caseworker.

  3. Bring-up & reset — exact cargo xtask … starting commands + migrate rollback.

  4. Concrete precondition (#991/#979) — exact applicant inputs (composition, DOBs, the income/expense/address the worker will record) from the paired spec.

  5. Step-by-step, interleaved — one ordered table # | Screen | Action | Expected | Screenshot, Screen ∈ {Applicant, Caseworker}; every action in sequence.

  6. Applicant post-outcome views (/home hero, /letters PDF), Expected outcome / oracle, Honest scope, Verify (the --project journey command).

D3 — Shared e2e helpers + journey-spec upgrades

Build once, first in tests/e2e/lib/ (serialize — shared file), smoke-test, then fan out:

  • fileSnapApplicationViaPortal(applicantPage, {applicant, members}) — drives the real /apply wizard on a portal context, waits for + parses POST /apply/finalize (H4), scrapes the reveal code+passcode, then GET /v1/households/{id}/full (C3) → returns {householdId, applicationId, referenceCode, passcode, headPersonId, memberPersonIds}.

  • viewApplicantHome(applicantPage, {referenceCode, passcode})/lookup login → /home; openLatestLetter(applicantPage)/letters → open the notice PDF.

  • addAddressViaUi(workerPage, {…​}) (M9) — worker adds a baseline address via the #983 add-form (/actions/address/add), sibling to changeAddressViaUi.

Then upgrade each portal-reproducible journey spec: default page = worker (caseworker auth, :8080); open a separate applicant context on CANOPY_PORTAL_CSP_BASE_URL (:8090) for the portal beats (C2). Flow: applicant files front-door → worker records facts + acts → applicant signs back in to view, capturing both personas' shots (app- applicant, step- worker) via the container path (L11). Backdated journeys keep the given-lib for the aged parts (portal apply is now()-dated).

Per-journey disposition (all 12)

# Journey Tier Plan / live-walk risk

1

thirty-day-determination-noa

Fully manual

Portal front-door ($900 recorded by worker) → determine → NOA; applicant views approved hero + letter.

2

lifecycle / lottery-winnings

Fully manual

Front-door ($300) → cert → worker adds income other 12000 → deny → adverse NOA; applicant views denial.

3

income-materiality

Fully manual

Front-door ($800) → cert → worker adds wages 9000 → recert nudge → file recert (records intent only, M10) → change notice.

4

shelter-cascade (#983)

Fully manual

Front-door (size-3) → cert → worker adds baseline address (addAddressViaUi, M9) then edits it (move) + rent 600→+300 → benefit rises. Verify add-then-edit live.

5

change-during-pending-hearing

Worker-driven

Front-door (size-3); worker records rent + files 2 appeals (future effective date). No applicant appeal UI (honest scope).

6

ele-grant

Fully manual

Front-door: head + 2 children under ELE age, no Express-Lane opt-in. Worker Run Determination → Approved (defers)Record ELE consent → badge (drains, pre-re-determination, H5) → re-determine.

7

cross-program-report

Worker-driven

Portal SNAP front-door; worker files TANF (2nd program, worker-only) + income → both deny.

8

recert-churn (#979 exemplar)

Worker-driven

RESOLVED to dual-persona. The feared received_at DESC selector tie is not real — canopy-applications orders at timestamp precision, so the worker reapplication (filed after the portal original) binds correctly with no application backdate. The lapse lives in the worker-backdated certification end date, so the original is a now-dated portal filing. Applicant files front-door + views home; worker does the churn beats. Verified live (6.7s).

9

overpayment-recompute

Harness (worker-only)

Confirmed architecturally worker-only (owner-approved kept as Harness). The recompute needs a determination whose effective date is months in the past (the claim looks back over prior issuances); a now-dated portal application yields a zero-width lookback window, and no worker backdate-determination action exists — so the aged case is harness-built and mints no /lookup credential (no applicant view). Follow-up #999 filed for a worker backdate-determination affordance that would later enable an applicant side; #998 tracks the /home/state fresh-case latency + duplicate-card cosmetics.

10

upheld-decision-overpayment

Worker-driven (notice-silent)

Front-door (head $800 + 1 child); worker enrollment + issuances + backdated-timely appeal (worker-typed dates) + record decision Upheld. No notice step asserted (notice-silent path, H6/honest scope).

11

transitional-benefits-on-tanf-closure

Worker-driven

Portal SNAP front-door (head $0 + 2 children); worker files TANF, adds wages, re-determines TANF → denied → TSNAP freeze.

12

adh-not-established-ihe-claim (#981)

DROP

No walkthrough; no UI (service-only). Leave the orphan spec; never present it as a published journey. Tracked by #995. Nothing to unpublish.

Steps

Step 0 — primary, serial

Branch feature/991-demo-ready-journeys; build the D3 shared helpers; smoke-test fileSnapApplicationViaPortal end-to-end (proves the portal→worker hand-off — the root-cause check) before anything else.

Step 1 — Workflow A (parallel authoring), one agent per journey

Each reads its walkthrough + spec + given-lib, upgrades its journey spec to the dual-persona portal flow (Step-0 helpers) and rewrites its walkthrough .adoc to D2. Agents touch only their own journey files (their .adoc + .spec.ts) — never the shared files (nav/index/snap.toml/CHANGELOG/runbook/helpers), which the primary owns. Each returns a report + "looks broken / needs code fix" flags.

Step 2 — live walk (primary, serial on the one shared stack)

Prep: cargo xtask dev start --profile fullseed --seed 42. For each journey (order below): run its upgraded spec cargo xtask e2e --devstack-profile full — specs/journey-<n>.spec.ts --project journey (e2e self-seeds; C1) → confirm every step + oracle + both personas' screenshots → apply the captured PNGs. On breakage: diagnose → fix in-MR (spawn a focused fix agent; Workflow B fans out disjoint service-code fixes in parallel with worktree isolation) → re-run; if unfixable/externally-blocked → unpublish + file the issue.

Order: #1 #2 #3 #4 #6 (fully manual) → #5 #7 #10 #11 (worker-driven) → #8 (selector risk) → #9 (harness) → runbook → audit → battery.

Step 3 — primary

Shared-file updates (nav, index, snap.toml, CHANGELOG, Demo Runbook), scenarios audit, full battery, MR.

Files Touched

File Change

docs/…/walkthroughs/journey-*.adoc

All rewritten to D2 (or moved to unpublished/).

docs/…/runbooks/demo-runbook.adoc

New Demo Runbook (bring-up + reset + per-journey index).

docs/…/nav.adoc, docs/…/walkthroughs/index.adoc

Runbook nav-link; any unpublished-journey removals.

tests/e2e/lib/portal.ts (new), tests/e2e/lib/helpers.ts

fileSnapApplicationViaPortal / viewApplicantHome / openLatestLetter; addAddressViaUi.

tests/e2e/specs/journey-*.spec.ts

Upgraded to the dual-persona portal flow.

docs/…/assets/images/walkthroughs/<journey>/*.png

Regenerated (plain PNG).

compliance/scenario-inventory/snap.toml

Concrete-precondition comments; walkthrough_blocked_by for any unpublished journey.

service source (canopy-web / canopy-portal / …)

Only as needed to fix a broken step.

CHANGELOG.adoc

== Unreleased entry.

Verification — tier-specific gates (H6)

  • Fully manual (#1,#2,#3,#4,#6): the upgraded spec passes end-to-end — applicant files via the portal context, worker records facts + acts, applicant signs back in to view /home + /letters; both-persona screenshots render; journey oracle holds.

  • Worker-driven (#5,#7,#8,#10,#11): applicant files the SNAP front door via the portal context + can view /home//letters; all substantive steps are worker-side and pass; no applicant mid-journey action asserted. #10 asserts no notice (notice-silent). #8’s feared selector tie proved not real (timestamp- precision received_at DESC), so it converted cleanly to dual-persona.

  • Harness (#9, worker-only): the harness constructs the aged case; the worker recompute + notice steps pass live; no applicant view (the harness-built case mints no /lookup credential — owner-approved, follow-up issue filed).

  • Cross-cutting: cargo xtask scenarios audit clean (no dangling shots; unpublished journeys carry issue-backed walkthrough_blocked_by); quality-budgets LOCKED; clippy/fmt clean; ADR-011 clean; full pre-push battery green (journey specs are demo-gated — run explicitly in the walk, not in the battery).

Documentation Updates

  • Antora walkthroughs — all published journeys rewritten to D2; new Demo Runbook.

  • nav.adoc + walkthroughs/index.adoc — runbook link; any unpublish removals.

  • compliance/scenario-inventory/snap.toml — preconditions; walkthrough_blocked_by on unpublish.

  • CHANGELOG.adoc — entry under == Unreleased.

Edit this page · default