T2-3 — Determination Snapshot: Cross-Program Input Capture (raw SOLQ) (#684)
On this page
Epic &56 / Track 2, T2-3 (#684) —
ADR-028 cross-program capture.
T1-10 (#678) + T2-4 (#685) gave every program service an immutable, signature-bound
input snapshot. ADR-028 named cross-program inputs (EE15 assigned_coa, ELE, TMA,
SOLQ/FDSH) as a Track-2 gap. Scope-reconciled against code reality, the one genuinely
unfrozen input a determination consumes is the raw SSA SOLQ record (only the five
derived ABD booleans were frozen). T2-3 freezes a by-value SOLQ projection into a new
typed cross_program_inputs field. Single MR; closes #684.
Scope boundary
T2-3 is snapshot capture of the cross-program/external inputs a determination
actually consumes today. Realized: raw SOLQ by value (canopy-medicaid). Already
satisfied (no new capture): EE15 assigned_coa (the determination’s own output, in
program_input); ELE (no snapshot — its linkage lives in
ele_grant_events.source_determination_id/application_id); the TMA inputs
(tanf_termination_date, had_tanf_in_prior_months, already in program_input).
OUT of scope (filed follow-ups): the TMA upstream determination id by-reference (no
TANF det-id reaches the Medicaid TMA flow — needs a tanf.case_closed event-contract
change); FDSH capture (a stub — not consumed by determine(), so nothing to freeze
until the gating feature exists); the per-fact derivation graph (T2-2 #679); provenance-
enrichment of the tanf/medicaid wire (#884).
Status
| Step | Description | Status |
|---|---|---|
(plan) |
This execution plan + nav entry. |
Done (2026-06-21) — the T2-3 (plan) commit. |
contract |
|
Done (2026-06-21) — the T2-3 implementation commit. |
producers |
The 4 non-medicaid producers + 2 proptest literals get |
Done (2026-06-21) — the T2-3 implementation commit. |
tests |
Proptest arb extension ( |
Done (2026-06-21) — the T2-3 implementation commit. |
docs |
ADR-028 Amendment 1; |
Done (2026-06-21) — the T2-3 implementation commit. |
Context
The Medicaid ABD cascade consumes SSA SOLQ data: derive_abd_flags_from_solq projects
a SolqRecord onto five Phase-D booleans (lost_ssi_due_to_cola, …). Before T2-3 only
those derived booleans were frozen (in program_input.member_flags), so a verdict could
not be replayed if the derivation logic changed. T2-3 freezes the source record by
value alongside the derived flags — the same self-containment posture as the IEVS
raw-reconstruction-plus-resolved-value pattern (snapshot.rs).
Decisions
| Topic | Decision |
|---|---|
Shape |
A typed |
Canonical empty |
A custom |
|
Conditional: |
Vec ordering |
|
Subject vs household |
Capture the whole-household SOLQ map (sorted), matching |
FTI audit |
|
Migration |
None — the field lives in the existing |
Snapshot types
DeterminationSnapshot.cross_program_inputs: Option<CrossProgramInputs> (additive,
skip_serializing_if = cross_program_inputs_is_empty). CrossProgramInputs { solq:
Option<Vec<SolqLeaf>> } with is_empty(). SolqLeaf { person_id, ssi_active,
monthly_ssi_amount, lost_ssi_due_to_cola_flag, benefit_category, monthly_benefit_amount,
disability_onset_date, lost_ssi_as_disabled_child_flag } (money rescale(2)-d; 4
optionals skip_serializing_if). See
crates/canopy-contracts-eligibility/src/snapshot.rs.
Verification
cargo build --workspace --exclude canopy-portal; cargo test -p
canopy-contracts-eligibility --test snapshot_roundtrip (roundtrip + the pinned
byte-stability golden hash); cargo xtask quality-budgets (all LOCKED — typed, no new
serde_json::Value); cargo xtask dev refresh then cargo nextest run -p
canopy-medicaid (the SOLQ-freeze + no-SOLQ tests); cargo insta test shows ZERO new
diffs (no-SOLQ snapshots are byte-stable — make_body passes ssa_solq: None); full
cargo xtask validate + check-docs.
As-built notes
Built as a single MR closing #684, mirroring the T2-4 mechanics.
-
Custom skip predicate.
cross_program_inputs_is_empty(&Option<CrossProgramInputs>)carries[expect(clippy::ref_option, …)]— serde’sskip_serializing_ifdictates the&Option<T>signature (the repo precedent is[expect], not#[allow]). -
Byte-stability proof. The vector test asserts the canonical bytes of a no-cross-program snapshot contain no
cross_program_inputskey, and pins the golden canonical hashc6b7e983…db3e— so the additive field is provably invisible to every existing snapshot’s hash (no churn). Confirmed:cargo insta testshows zero diffs. -
Quality budgets flat (B2 123, B3 src 757, B4 137, B5 309) — the typed types add no
serde_json::Value; the producer logic lives in a helper soevaluate_memberstays under the B2 ceiling. -
Deferred (filed +
/relate #684): TMA upstream-determination-id by-reference; FDSH consumption → snapshot capture.
Follow-ups
File each as a GitLab issue and /relate #684:
-
feat:TMA upstream-determination-id by-reference — thread the source TANF determination id throughtanf.case_closed+tanf_tma_coverage+ the orchestrator so the Medicaid TMA snapshot can record it. -
feat:FDSH consumption → snapshot capture — once FDSH results gate a determination, freeze them like SOLQ (todaystore/fdsh.rsis a stub;determine()never reads it). -
(Already filed) provenance-enrichment of the tanf/medicaid wire #884.