ADR-035: Per-Subject Determination + Per-Program Context Mappers
On this page
Accepted (2026-06-16).
Amends
ADR-002 — Black-Box Determination Contract (cardinality) and ADR-034 — Per-Program Determination Context-Mapping Decision 8 (subject-selection ownership).
ADR-002. ADR-002 makes a determination a single signed black-box keyed to the application/household. This ADR amends that cardinality for per-subject (multi-subject) programs — Medicaid (per member), CAPS (per child), WIC (per participant): a determination there is per subject (a person_id), and the program’s POST /v1/determine returns a list of independently-signed per-subject determinations. Household-level programs (SNAP/TANF) keep ADR-002’s single { determination } response unchanged (the AU verdict). The orchestrator normalizes every program’s response into a Vec<SignableDetermination> internally (a 1-element vec for the household programs), so the amendment is scoped to where multi-subject is real and the SNAP/TANF wires do not change. ADR-002’s signing + ADR-028’s snapshot apply per determination — i.e. per subject.
ADR-034 Decision 8 (supersedes). ADR-034 D8 framed CAPS/WIC as single-subject programs whose subject the orchestrator mapper selects. Verification against federal law + the GA policy book (see Context) shows CAPS and WIC are in fact multi-subject — CAPS authorizes each child individually on an independent 12-month period (45 CFR 98.21(a)(1); DECAL CAPS §2.1/§10.5.1/§5.4), and WIC certifies each participant individually with their own category/risk/package/period (7 CFR 246.7; DPH WIC CT-800/810/840). This ADR therefore supersedes D8’s single-subject framing: there are exactly two cardinalities (household-level and per-subject), and the per-subject program — not the orchestrator — enumerates its subjects (Decision 3).
Relates
-
ADR-034 — Per-Program Determination Context-Mapping: this ADR is ADR-034’s implementation. Decision 1 (per-program mappers replace the broadcast) and Decision 4 (complete-or-provisional + input-unsatisfiable carrier) are made concrete here; the multi-subject question D8 "wholly deferred to #860" is resolved by Decision 3 — and D8’s single-subject framing for CAPS/WIC is superseded (they are multi-subject; see Amends).
-
ADR-001 — Program Service Isolation: load-bearing. The orchestrator MUST NOT run program-specific assistance-unit logic; multi-subject member enumeration is owned by the program (Decision 3/5), so the orchestrator never imports or calls
compose_*_au. -
ADR-027 — Worker Fact Authoring: §7 makes AU composition program-owned (Decision 5). The worker-authored fact corpus is the source the mappers read; pre-corpus (epic &56) several CAPS/WIC required inputs are worker-facts the mapper cannot satisfy → input-unsatisfiable (honest, see Consequences).
-
ADR-028 — Determination Input Snapshot: each per-subject determination snapshots its own inputs + the composed AssistanceUnit; the per-subject signing is the coordinated multi-service change ADR-028 anticipates.
-
ADR-016: the
person_idcarrier (Decision 6) lands as a nullable forward-only column add. -
Extends ADR-031: the per-program input-requirements manifest’s
source_classbecomes runtime-enforced by the mapper (aContextErroris the manifest gap made live).
Context
ADR-034 ruled that the orchestrator must build each program’s determine input via a typed per-program mapper, complete-or-provisional, never a silent default — but ratified only the contract, deferring the implementation and (explicitly, Decision 8) the multi-subject Medicaid subject-selection to #860. The verified ground truth (epic &63 recon, 2026-06-16):
-
The broadcast. The orchestrator builds ONE generic
ApplicationContext, setsapplicant_person_id = head_of_household_person_id(members)(services/canopy-eligibility/src/orchestrator.rs:793, hardcoded head), and POSTs the identical.json(&ctx)to every program in thefor program_name in &request.programsloop (:823-994). It already computes a fullmembers[]with per-member age/disability/relationship (fetch_household_context:213-267) but uses it only for household flags, never for subject selection. -
The carriers have no subject dimension.
ProgramResult(crates/canopy-contracts-eligibility/src/determine.rs:44) andSignableDetermination(crates/canopy-signing/src/envelope.rs:69) carryapplication_id/household_idbut noperson_id;program_determinations(DB) has noperson_idcolumn; canopy-webrender_determination_tab(services/canopy-web/src/api/case_detail.rs:2202) finds exactly ONE determination per program slug. So two members cannot have separate determinations for the same program today. -
Per-subject programs 422.
CapsApplicationContextrequireschild_person_id/child_age_years;WicApplicationContextrequiresperson_id/participant_category— the head-centric broadcast omits them, so dispatch deserializes to a 422 the orchestrator mislabelspending_verification(the ADR-034 "required-no-default 422" failure mode). -
CAPS and WIC are multi-subject, not single-subject (verified against law + the policy book). WIC certifies each participant individually — own category, nutritional risk, food package, certification period, priority — with the economic-unit income test the only household-level input (7 CFR 246.7; DPH WIC CT-800.01/810.01/840.01); a household with a pregnant woman + infant + toddler is three certifications. CAPS tests the family for income/assets/activity but authorizes each child on an independent 12-month period with a per-child scholarship (45 CFR 98.21(a)(1); DECAL CAPS §2.1 "Eligibility Period… each individual child receives", §10.5.1 per-child scholarship, §5.4 DFCS siblings get "their own case", §6.4.4 a child turning 13 closes only that child’s scholarship); a 2-child family is two authorizations on independent timelines. The code already keys
caps_determinations/caps_authorizationsbychild_person_idandWicParticipantbyperson_id— the scalar-subject contracts are the simplification, the per-subject reality is not. So ADR-034 D8’s "single-subject CAPS/WIC" conflated "the subject is an individual (not the head)" with "there is exactly one subject." -
AU composition is dead code.
compose_snap_au/compose_tanf_au/ Medicaidcompose_magi_budget_group(+ a sharedAssistanceUnit,crates/canopy-common/src/au_composition.rs:117) are built + unit-tested with zero production callers; ADR-027 §7 says AU composition is program-owned. -
The design fork that broke every naive answer. Specifying per-subject determination "now" tempts three mutually-exclusive moves: (a) the orchestrator fans out N POSTs per subject; (b) the program enumerates subjects internally; (c) the orchestrator calls
compose_magi_budget_groupto learn the cardinality. (a)(c) make the orchestrator run program-specific AU logic — an ADR-001 violation; (a)(b) contradict (who enumerates?). The adversarial critique showed all three panel proposals fell into one of these traps. The only consistent resolution: the program owns enumeration — the orchestrator dispatches once and the program returns N.
Decision
1. Per-program mappers replace the generic broadcast (implements ADR-034 Decision 1)
Introduce a ProgramContextMapper per program behind the existing program registry. The orchestrator, for each requested program, calls map_<program>_context(facts, policy) → Result<ProgramInput, ContextError> and dispatches the typed result instead of broadcasting ApplicationContext. Mapping is orchestrator work, so the mapper trait + the closed-set ContextError + the per-program map_* implementations all live in canopy-eligibility/src/mappers/ — program services neither know nor run mappers (ADR-001 boundary: the program only receives its already-built typed context). What is shared is only the carrier (Decision 4): the person_id field + the input_unsatisfiable status + the missing_inputs payload land on ProgramResult (canopy-contracts-eligibility) and person_id on SignableDetermination (canopy-signing) — the types both sides already exchange. Each ProgramInput a mapper produces is just the program’s existing typed *ApplicationContext (canopy-contracts-{program}), which the orchestrator already depends on. Rollout is incremental: a program without a mapper yet keeps a pass-through mapper that emits today’s generic context (the broadcast becomes opt-out, removed per program as its mapper lands).
2. Two cardinalities; the list response is scoped to the per-subject programs (amends ADR-002 only there)
There are exactly two determination cardinalities:
-
Household-level (SNAP, TANF): one determination — the assistance-unit verdict.
-
Per-subject / multi-subject (Medicaid, CAPS, WIC): N determinations, one per eligible subject (Medicaid member with a COA, CAPS child in care, WIC participant by category), each on its own independent timeline.
person_id: Option<PersonId> becomes a first-class #[serde(default)] field on SignableDetermination and ProgramResult — the determination subject (a scalar per determination): for SNAP/TANF the AU/head (or null), for each per-subject determination the member/child/participant.
The response cardinality:
-
SNAP/TANF keep ADR-002’s single
{ determination: SignableDetermination }response unchanged (structural cardinality exactly 1). -
Medicaid/CAPS/WIC return
{ determinations: Vec<SignableDetermination> }— one per subject, each independently signed.
The orchestrator normalizes both wire shapes into Vec<SignableDetermination> at the dispatch-parse boundary (a household program’s response becomes a 1-element vec), so all downstream aggregation/persistence logic is uniform. This deliberately rejects the "uniform list on all six programs" shape: SNAP/TANF are structurally single-valued, so forcing the list (and the ADR-002 cardinality amendment) onto them is a lowest-common-denominator over-generalization. The amendment is paid only where multi-subject is real — the three per-subject programs.
3. The PROGRAM owns subject enumeration for every per-subject program (resolves #860; supersedes ADR-034 D8)
For every per-subject program (Medicaid, CAPS, WIC) the orchestrator dispatches one POST carrying the household context + the full members[]; the program enumerates its own subjects (Medicaid: members the AU/COA cascade evaluates; CAPS: children in care under the age gate; WIC: participants by category), evaluates each, and returns N independently-signed per-subject determinations. The orchestrator never enumerates subjects, never fans out, and never calls compose_*_au (ADR-001 / ADR-027 §7) — "who is a subject" is program policy. For household programs (SNAP/TANF) the program returns the single AU verdict. This is one uniform model — cardinality (1 → N) is decided by the program that owns the assistance-unit / subject rules, not the orchestrator — and it removes the asymmetry of the earlier draft (no orchestrator "select the one subject" path). It supersedes ADR-034 D8, which had the orchestrator mapper select a single CAPS/WIC subject; per the Context evidence those programs are multi-subject, so the program enumerates them exactly as it does Medicaid members.
This requires the CAPS and WIC determine contracts to reshape from a scalar subject (child_person_id / person_id) to a members[]-based household context (converging on Medicaid’s shape), with the per-subject facts carried per member; the program then returns a list. SNAP/TANF/Medicaid contracts already carry members[].
4. Complete-or-provisional + input-unsatisfiable carrier (implements ADR-034 Decision 4)
Three outcomes, replacing the silent 422 → pending_verification mislabel:
-
Complete — a signed per-subject determination (status
approved/denied), as today but per subject. -
Signed provisional — the program marks missing inputs via the
program_extensionmarker (generalizing canopy-tanf’sdeprivation_provisional); signed, status carries the provisional flag. -
Input-unsatisfiable — when the mapper returns
ContextError(a policy-material required input cannot be sourced), the orchestrator synthesizesProgramResult { status: "input_unsatisfiable", missing_inputs: Some(Vec<MissingInput>), determination_id: <stable hash, unsigned> }, whereMissingInput = { field, source_class, gap_issue }. Input-unsatisfiable results are an in-flight signal, not persisted toprogram_determinations(they are worker-resolvable fact gaps, not audited determinations).
5. Assistance-unit composition is program-resident (ADR-027 §7)
Each program’s determine handler composes its assistance unit / enumerates its subjects from the threaded members[], program-side: SNAP/TANF call their compose_*_au for the household AU (wiring the dead modules); Medicaid uses compose_magi_budget_group for per-member budget groups; CAPS/WIC filter members[] for their subjects (children under the age gate / participants by category). The composed AU / enumerated subject set becomes part of the ADR-028 signed input snapshot (per subject). The orchestrator has zero AU visibility. (The duplicate medicaid compose_magi_budget_group in magi.rs vs au_composition.rs is reconciled to one in the implementing slice.)
Wiring SNAP/TANF AU composition will likely change SNAP/TANF determination outcomes vs. today’s implicit all-members behavior (it can correctly exclude disqualified/ineligible/SSI members per 7 CFR 273.1). That is a correctness migration, not a regression: the AU composition is validated against the policy (the canonically-correct AU), not against the prior golden outputs — anchoring to UAT-validated-but-incorrect numbers would be the error. It is sequenced as its own slice (post-UAT) only because it is orthogonal to the per-subject work, not out of fear of changing validated behavior.
6. Persistence: a nullable person_id, forward-only (ADR-016)
program_determinations gains a nullable person_id UUID column (forward-only add); one row per (eligibility_request_id, program, person_id). Household programs (SNAP/TANF) write one row (person_id = NULL, the AU subject); per-subject programs (Medicaid/CAPS/WIC) write N (one per subject). No destructive change; existing rows backfill person_id = NULL. No missing_inputs column: per Decision 4, input_unsatisfiable is not persisted (in-flight only) and a provisional verdict’s missing-input list rides the signed program_extension — so the missing-inputs data never needs its own persisted column.
Unique-key + NULL handling (implementation fork): a plain UNIQUE (eligibility_request_id, program, person_id) does not enforce one-row-per-household-program because SQL treats NULL person_ids as distinct (two NULL-subject SNAP rows would not collide). Resolve with, in preference order: (1) UNIQUE NULLS NOT DISTINCT (…) if the deployed PostgreSQL is ≥15; else (2) a unique index on COALESCE(person_id, '00000000-0000-0000-0000-000000000000'). Do not substitute the head’s person_id as the SNAP/TANF subject — a household determination is the AU’s, not the head’s, so NULL is the honest marker.
7. Aggregation + UI surface per subject
This ADR owns the data surface only: CombinedResult gains an additive per-(program, person_id) view (the existing program-keyed view stays → non-breaking), and GET /v1/eligibility/determinations?household_id returns the per-subject list. That data machinery is shared by all three per-subject programs and lands with the first per-subject slice.
The presentation is a design deliverable, not decided here, and it spans three user-facing surfaces — not only the worker portal:
-
canopy-web (worker, internal) — design determination received (2026-06-16). Orchard design issued the per-subject Determination-tab spec (high-fidelity handoff): (Q1) a roster grouped by program → subject (one expandable row per subject;
denied+input_unsatisfiablerows open by default), with SNAP/TANF rendered as a single one-line strip (not a roster); (Q2) a summary strip = a cash-only$NNN/mo(SNAP/TANF only) + per-kind badges (coverage/package/subsidy counts; CAPS dollars shown but never folded into the cash total) + a status roll-up — no false grand total; (Q3)input_unsatisfiableas an amber "Verification needed" pill + a named-missing-input checklist with Resolve actions. The handoff carries an Askama draft + the--orchard-*token spec + light/dark renders + aDeterminationViewview-model shape — the Slice-1 (CAPS) build target, reused by WIC/Medicaid. (The benefit display is heterogeneous byBenefitKind— cash/coverage/package/subsidy — projected from each program’sprogram_extension, not a new carrier field.) -
canopy-portal (applicant, external) — design determination received (2026-06-16). The applicant spec (high-fidelity, en+es, mobile+kiosk, light+dark): Home keeps program cards (SNAP household card; per-person programs gain an expandable "Who’s covered" roster) + a single "one thing needs you" hero; My Benefits is the canonical per-person view (one card per family member); Letters group by person (a pinned "Needs your action" block on top, household last); the
input_unsatisfiablestate leads with person+program, shows progress ("2 of 3 covered"), names the one missing item, and routes to upload — never "pending." Plain non-alarming language (Covered / Approved / "one thing left"; never denied/ineligible/pending — legal detail stays in the PDF NOA), per-member framing, every new string through Fluent (en+es), WCAG 2.1 AA. Build target: extendhome.rs(ProgramCardView) +notices.rsin the portal’s Dioxus+Fluent primitives. Privacy (#634) — defaults to suppress + neutral row, pending policy sign-off: a confidentiality-protected member’s per-subject outcomes + NOAs are hidden from the head (their My Benefits row degrades to "Details kept private · this person manages their own benefits"; household-scope SNAP stays visible). This is a policy decision, not a layout one — suppress can hide a real benefit from the household manager; surface can leak a protected member’s status on a shared/coerced device — so it requires confirmation before build (see Open questions). -
canopy-notices NOAs (applicant, external, legal). Notices of Action are already per-person (
recipient_person_id,services/canopy-notices/src/domain.rs:34, resolved from the person_id-stamped determination events). Per-subject determination → N per-person NOAs, which is the legally-correct shape (CMS requires individual Medicaid notices; WIC is per-participant) — so this largely falls out of the existing event +person_idplumbing; the slice confirms that per-subject determinations emit per-subject events that drive per-person notices, and the notice content per program/subject is template work.
ADR-035’s data layer (per-subject determinations + person_id + the determination events) feeds all three; the orchestrator/contract changes are surface-agnostic. The ADR does not prescribe any layout and explicitly does not sum heterogeneous benefits into a single hero total (Medicaid coverage / WIC food package / CAPS subsidy are not cash). The applicant-facing surfaces (portal + NOA) are higher-stakes than the worker view — they carry the legal per-person notice obligation — and each gets its design/template treatment before its slice’s UI lands, following (not blocking) the data machinery.
8. Staging (ADR-013) — forward-only slices
The per-subject machinery — the ProgramContextMapper trait + registry; person_id/missing_inputs/input_unsatisfiable on the carrier; the { determinations: […] } list response + the orchestrator’s internal Vec normalization (SNAP/TANF wires unchanged); the forward-only person_id/missing_inputs columns + per-subject rows; the per-subject case-detail UI — is shared by all three per-subject programs, so it is built in the first per-subject slice and reused thereafter.
-
Slice 1 — Per-subject machinery + CAPS (#857), the simplest per-subject program. Build the machinery above; reshape
CapsApplicationContextto amembers[]-based household context;map_caps_contextbuilds it;canopy-capsenumerates children under the age gate + returns N per-child determinations, emittinginput_unsatisfiablefor the #857 worker-facts (activity_weekly_hours/activity_verified/child_has_special_needs) — replacing the CAPS 422. CAPS is chosen first because it is the simplest per-subject program (a family income test + a child age gate; no SOLQ, no EE15/ELE), so it validates the machinery end-to-end (the demo/test path supplies the worker-facts directly) with the least policy complexity. SNAP/TANF/Medicaid keep pass-through mappers + their current wire → non-breaking. -
Slice 2 — Medicaid per-member (#860 core), on now-proven machinery.
canopy-medicaidenumerates members + returns N per-member signed determinations — the headline value: a child’s own C19 / an aged member’s ABD through the orchestrator, which works today for the income/age COAs (they need only threaded age + household income, no worker-facts). The EE15/ELE cross-program propagation reconciliation is deferred within this slice: per-member determination lands, but the existing household-level EE15 group propagation + ELE interaction is kept as a documented interim (CombinedResult.medicaid_assigned_groupstays scalar = the primary member’s group) so per-member determination is not blocked on the propagation redesign (a tracked follow-up). Reuses the Slice 1 machinery. -
Slice 3 — WIC (#769). Reshape
WicApplicationContexttomembers[]-based;map_wic_context;canopy-wicenumerates participants by category + returns N per-participant certifications/determinations. Reuses the machinery; &56-gated for real function like CAPS. -
SNAP/TANF mappers retiring their pass-throughs: post-UAT cleanup (the broadcast is correct for the household programs today).
input_unsatisfiable for real worker flows until the &56 worker-fact corpus (machinery validated via the demo/test path); Medicaid’s income/age COAs work immediately. A Medicaid-first order would reach real value one slice sooner but build the foundational machinery, the highest-blast-radius program (per-member changes working Medicaid + ELE + EE15 + case-detail), and the EE15/ELE reconciliation all at once — rejected for that risk concentration.
Consequences
-
The three dead
compose_*_aumodules become live (called by their own program handlers); the orchestrator gains zero AU logic — ADR-001/027 preserved. -
Silent 422s become structured
input_unsatisfiableresults naming the missing inputs — the ADR-034 contract realized; the worker (or the demo/test path) supplies the named facts and re-runs. -
Honest scope: until the ADR-027 worker-fact corpus (epic &56) lands, CAPS/WIC mappers reach
input_unsatisfiablefor worker-fact inputs (activity_weekly_hours/activity_verified/child_has_special_needs#857;participant_category/nutritional_risk_documented/is_breastfeeding_fully#769). This is a strict improvement over the silent 422 (actionable + observable), and the determine contracts accept the fields directly, so the demo/test path can supply them — but full worker-driven CAPS/WIC determination is gated on &56. The input-coverage gate (ADR-031/ADR-034 Decision 6) already tracks these as gaps. -
Per-subject determination enables what the head-only broadcast cannot: a child’s own Medicaid C19 / an aged member’s ABD, each child in a 2-child CAPS family, and every participant in a multi-participant WIC household — all through the orchestrator.
-
The list response is scoped to the three per-subject programs (Medicaid/CAPS/WIC); SNAP/TANF wires never change. Internally the orchestrator treats every program uniformly (a
Vecof 1 or N). Devstack deploys programs + orchestrator together, so there is no cross-version window. -
CAPS and WIC determine contracts reshape from scalar-subject to
members[]-based (converging on Medicaid’s shape) — a deliberate cost of modeling the per-subject reality, paid once. The alternative (scalar-subject) cannot represent a 2-child CAPS family or a 3-participant WIC household and would force a second reshape later.
Open questions (resolved in the implementing slices, not ADR-blocking)
-
EE15 / ELE cross-program propagation with per-member Medicaid: when Medicaid returns N per-member COAs, which subject’s COA drives the EE15 38-COA cross-program propagation + the ELE child-grant interaction? (Today
CombinedResult.medicaid_assigned_groupis scalar.) Slice 2 lands per-member determination but defers this reconciliation — it keeps the household-level scalar propagation as a documented interim (so per-member determination isn’t blocked on it) and the per-member propagation redesign is a tracked follow-up. -
Per-member idempotency-key semantics: the determination idempotency-key is per-dispatch today; with one POST → N results the key stays per-dispatch (per household-program), and per-subject identity is the
person_idon each returned determination. Confirm the program idempotency cache replays the full list. Slice 3. -
CAPS per-child facts source:
canopy-capsenumerates the children frommembers[](age gate), so subject selection is settled — but the per-childactivity_weekly_hours/activity_verified/child_has_special_needsare worker-facts (#857/&56). Until the corpus exists those children’s determinations areinput_unsatisfiable. Whether "child in care" itself is a worker-fact (which children need care) vs. inferred is the #857 capture question. -
WIC participant enumeration +
participant_categorysource:canopy-wicenumerates participants frommembers[], but the category (pregnant/postpartum/breastfeeding/infant/child) and nutritional-risk are worker-facts/intake-captured (#769/&56) — demographics give infant/child by age, but the women’s categories need capture. Pre-corpus those areinput_unsatisfiable. #769. -
DECIDED (2026-06-16) — Confidentiality + per-member surfacing in the applicant portal (#634). When a household member is confidentiality-protected (DV /
Confidentiality::AddressConfidential), the head does not see that member’s per-subject outcomes or NOAs: the member’s My Benefits row degrades to a neutral "Details kept private · this person manages their own benefits" (no amounts/dates/plan/missing-item), their letters are removed from the head’s list, and household-scope SNAP stays visible. Ratified suppress-default — for a DV/safety context, not-leaking on a shared/coerced device is the safer failure mode; the neutral row signals existence without detail. The same suppression test applies to the EBT recap + address-confidential (ACP) cases (portal HANDOFF §3.8/§3.9). -
DECIDED (2026-06-16) — SNAP deductions breakdown moves to the "Income & Verify" tab (off the Determination tab); it is income detail, not a determination outcome.
Alternatives considered
-
Minimal-incremental (nest per-member Medicaid in
program_extension). Rejected:program_extensionis untyped JSON with no validation; it defers the cardinality decision into the mapper interface (forcing a reshape when #860 lands) and splits provisional mechanics into v1/v2. Its Decision-8 deferral of multi-subject cardinality and its program-owned-AU boundary were grafted in. -
Per-member-first-class with orchestrator fan-out. Rejected for its dispatch model: it has the orchestrator fan out N POSTs and call
compose_magi_budget_groupto learn cardinality — an ADR-001 violation (orchestrator running program AU code). Its first-classperson_idcarrier + per-subject signing + theVecresponse were grafted in — but theVecis program-returned from one dispatch, not orchestrator-fanned (Decision 3). -
Uniform list response (all six programs return
{ determinations: […] }). Rejected: it imposes the N-cardinality + ADR-002 amendment on SNAP/TANF, which are structurally single-valued — a lowest-common-denominator over-generalization. The list is scoped to the three per-subject programs (Decision 2) and normalized internally instead. -
Single-subject CAPS/WIC (follow ADR-034 D8 literally — the orchestrator mapper selects the one child/participant). Rejected: verification against 45 CFR 98.21 / 7 CFR 246.7 + the GA policy book (Context) shows CAPS and WIC are multi-subject — a 2-child CAPS family is two independent authorizations, a multi-participant WIC household is N certifications. A single-subject model either under-models those households or forces the orchestrator to fan out per child/participant (the ADR-001 violation). Superseded by treating CAPS/WIC as per-subject like Medicaid (Decision 3), which also removes the orchestrator subject-selection path entirely.
-
Program-ownership, taken consistently + uniformly across the per-subject programs. Chosen. The panel winner’s self-contradiction ("orchestrator fans out" + "program iterates" + "orchestrator calls compose") is resolved by committing fully to program-owned enumeration: the orchestrator dispatches once per program, the program returns N. Generalized beyond Medicaid to all per-subject programs (CAPS/WIC are multi-subject too), and scoped at the wire so the list + ADR-002 amendment touch only those three while SNAP/TANF are untouched and the orchestrator normalizes to a
Vecinternally. Strongest ADR-001/002/027 alignment, one uniform model (no single-vs-multi asymmetry), no AU duplication, the dead modules become live, the carrier is sound, and the blast radius on the household programs is zero.
References
-
Implements ADR-034 Decisions 1 + 4; supersedes Decision 8 (CAPS/WIC are multi-subject, not single-subject).
-
Slices map to issues: #857 (CAPS per-subject — Slice 1), #860 (Medicaid per-member — Slice 2), #769 (WIC per-subject — Slice 3); worker-fact corpus #858 / epic &56.
-
Recon + design panel provenance: epic &63, 2026-06-16.