Plan: SG/NRSG (UAS 552/550) Guardianship-Subsidy Flow (#1070 program)

On this page

Status

Step Description Status

Step 0

GitLab filing: #1070 weight 3→8 + Plan::SG-FLOW + the decision record (U1–U6/F1–F12); issues #1111 (G0 date-only instants) / #1112 (ended-placement terminal-fact mutability, filed only) / #1113 (auto-activation knob, deferred U5) / #1114 (hard eligibility checks) / #1115 (late-activation backpay, ↔#1071) / #1116 (imported-pending activation, ↔#1071) filed + related; #1070 ↔ #1071/#1072/#1073

Done (2026-07-24) — all filed; decision comment on #1070

G0 (fix MR)

Date-only form values become noon-UTC instants: normalize_datetime_local’s date-only branch emitted midnight UTC, which the Eastern business calendar reads back as the PREVIOUS day — poisoning `started_on/ended_on and every date witness this flow derives from them (transfer_on, signed_at < transfer_on). Fix + caller audit + unit pins; helper extracted to routes/form.rs (B1 route-module budget, structural)

Done (2026-07-24) — MR !1035 (impl 1b4da28c697619827fb0b0dabd52db651a49b2f8, merge 5c0c07e92aeddefc7285b41ea0d0f3b2a67ee073); #1111 closed. Interleaved detour merged first on its own branch: #1117 occupancy-trigger stale-count race (MR !1034, merge 7c30f61a4953d6a31fe71aed8aafa705b30880a0) — it was failing every battery’s invariant sweep; forensics on the issue

G1 (MR1)

Backend: program-tagged create request (7-variant, closed-cohort typed CLOSED_PROGRAM refusal; ERR keeps its LEGACY tag-free canonical hash so persisted F8 hashes survive); proof-typed creation (new ApprovalAction::CreatePending; the generic stamp-based create_agreement DELETED); create_guardianship_agreement (PENDING mint from signed_at; family history locks sorted; F8 pair split out of the ERR-only insert bundle); activate_guardianship_agreement (witnessed pending → active at derived transfer_on; replay-after-success → 200; store-side residence floor from the snapshot; sibling-open refusal; anchors re-derived from the activation approval act per 22.9); guardianship_finalized ERR termination (county floor, S2S-witnessed, business_date == ended_on required); generator BeforePaymentsBegin boundary rule over the stored payments_begin_month = max(month after transfer, month after TANF termination); migration (transfer/boundary/court-evidence/TANF/assessment columns, three-shape snapshot CHECK, flow-coupling + signing + boundary CHECKs, family one-open unique index); shared typed PlacementEndedPayload + subsidy-eligibility ended/goal projection; CRAIG_FINANCIALSUBSIDY_SGENABLED enrollment-consent knob (create+activate only; existing-row lifecycle grandfathered, both directions tested); ruleset v1.4.0 activate row; events generalization; seed family E (+ ACTIVE ERR E for the G2 handoff arc) + SHA re-bless; one-line BFF ripple ("program": "err"); backend docs (ADR-056, CHANGELOG, data-model, config-reference, implementation-guide event payloads, API pages, shared-crates, state-machines, + the ADR-054/ADR-055 sentence amendments C2 falsified — the "generic create stays stamp-based" / "two-step create refuses ERR" lines describe a deleted fn)

Done (2026-07-24) — MR !1036 merged 64c24523836f09e4d44696e51f269a902d88c134; commits 80e135ab (plan) / 52577601 (C1) / 5855452a (C2) / 614bd688 (C3) / 364d7b11 (C4) / 6ce1f342 (C5), each fresh-subagent J-reviewed with all findings remediated pre-commit (highlights: the C2 honest-StaleHead replay fix, the C4 exact-match reason vocabulary killing a padded-token witness bypass, the C4-caught missing F9 witness, the C5 stale fixture comment). Full battery green (21 validate stages, unit/integration, 278 e2e, SDK suites). Discovered work filed: #1119 (pre-existing intake-edge-crypto flake). Deviations recorded in §C5 + Execution notes

G2 (MR2)

Web + e2e + close-out: guardianship enrollment form (program radio 552/550; per-render client_request_id; manual-preconditions attestation text; verbatim-4xx flash; PRG), detail-page Activate (flow-shaped pending rows only; hidden CAS id; court-evidence + optional TANF/legal-reference inputs), BFF activate route, end-placement pre-finalization interstitial (reason=guardianship + no pending family agreement → refuse-first + acknowledgement checkbox) + text-only handoff flash; generic transition form suppresses the guaranteed-fail active target on pending rows; e2e subsidies-guardianship.spec.ts (dana.county 8-step arc on fixture E incl. the ERR guardianship_finalized handoff; county project testMatch update; placement-spec fixture exclusion) + bobsmith write-parity; web docs + runbook; ⁂ comment on #1073; #1070 AC walk + close; epic &70 tick; plan → archive

Done (2026-07-24) — the G2 MR: W1 454fbbb8 (the BFF surface, J-reviewed — SS6 factored into a pure unit-pinned decision, scope-honest wording), W2 the e2e layer (the dana.county 8-step arc on fixture E; 286-green full battery; J-reviewed — the midnight race + two comment nits fixed, pre-existing orphan spec filed as #1120), W3 docs + close-out (runbook, CHANGELOG, ⁂ on #1073, this archive move). Program complete — #1070 closes with this MR

Issues: #1070 (tracking; G2 closes) · #1111 G0 (closed) · #1112 #1113 #1114 #1115 #1116 follow-ups · #1117 (closed, interleaved fix) · discovered mid-program: #1118 (seed ruleset DO-NOTHING fallback), #1119 (intake-edge-crypto e2e flake), #1120 (zero-project orphan spec) Branches: fix/1111-date-only-noon-instants (merged) · feature/1070-sg-backend (this plan = first commit) · feature/1070-sg-web Provenance: 3-reader recon + Plan-agent internal pass + primary verification pass (seed families A/B read in source) + the user’s external review (2026-07-24, ~45 findings — authoritative). The finding→disposition index is in §Review dispositions.

Context

The #1069 ERR program landed the machine: temporal agreement ledger (ADR-052), action-bound proofs (ADR-054), the generator’s single expected_month_row judgment (ADR-053/055), F8 create idempotency, the placement S2S eligibility read, default-off consent knobs, and the BFF/e2e patterns.

SG (Subsidized Guardianship, relative, UAS 552) and NRSG (non-relative, UAS 550) are the post-permanent-guardianship monthly subsidy per GA 22.8: the A&A is signed BEFORE the guardianship transfer; ≥6 months prior residence under DFCS supervision (a 6-vs-12 manual discrepancy is ⁂ on #1073); payments start the 1st of the month AFTER transfer — and when the caregiver received TANF for the child, the month after TANF terminates, whichever is later; eligibility further requires citizenship/residency, child income eligibility, an approved kinship assessment / foster-home evaluation, County Director approval, a non-reunification permanent-guardianship order through age 18, and (SG only) the TANF degree of relationship. Enhanced variants (ERSG/ENRSG) closed 2014-08-01. 12-month renewal + 6-month paper reviews run from the initial or most recent approval (22.9; the paper track is already generic in types_for_program).

Before this program, native SG enrollment was publicly unreachable (the store-level two-step existed but was test-only and stamp-forgeable); the HTTP transition endpoint reserves pending → active for this flow; guardianship finalization was a generic placement end that voids undisbursed payments and silently strands support — financial’s consumer dropped the producer’s end_reason. Two pre-existing defects surfaced by this plan’s review: date-only form values landed a day early on the Eastern calendar (G0, fixed) and ended placements accept silent terminal-fact edits (#1112, filed).

The flow at a glance

  1. Sign — a county director creates a PENDING sg/nrsg agreement from the signed A&A. The anchoring placement is S2S-verified ACTIVE; child/case/worker and the residence-start snapshot derive from it. F8-idempotent. Provisional review anchors run from this approval act.

  2. Finalize — the operator ends the placement with end_reason=guardianship. If NO pending family agreement exists, the BFF refuses first and requires an explicit acknowledgement checkbox (support will stop) — the create-it-first prompt happens BEFORE the irreversible end.

  3. Hand off — the predecessor ERR is terminated with the new guardianship_finalized reason; its business date is REQUIRED to equal the placement’s derived end date (truth-dated).

  4. Activate — activation refuses while any other open agreement exists for the child (ERR first, deterministically). CRAIG derives transfer_on from the placement’s end date, checks the witnesses (signed strictly before transfer; residence floor from the stored snapshot; guardianship ending; goal not reunification; court-order evidence), stores the money boundary payments_begin_month = max(month after transfer, month after TANF termination), and re-derives the review anchors from this approval act (22.9’s "most recent approval").

  5. Pay — the generator pays from payments_begin_month; months before it are skipped with a named reason. ERR’s final month and SG’s first month never overlap; a transfer ON the 1st leaves that month unpaid by both programs (the strict texts' own artifact, ⁂ flagged).

Create + activate sit behind CRAIG_FINANCIALSUBSIDY_SGENABLED (default OFF) — an ENROLLMENT-consent knob: existing agreements' lifecycle (generator, transitions including guardianship_finalized, reviews, sweep) is deliberately NOT gated (grandfathered, tested).

Decisions

# Decision Source

U1

ONE create route: CreateSubsidyAgreementRequest becomes a #[serde(tag = "program")] 7-variant enum — err arm verbatim, sg/nrsg share one body, closed tokens (ersg/enrsg/ rcs/ercs) → typed CLOSED_PROGRAM naming the closure date + the #1071 import path. ERR keeps its LEGACY tag-free canonical hash (persisted F8 hashes survive the upgrade); sg/nrsg hash the tagged body. Mixed-version wire compat = recorded non-goal pre-1.0 (lockstep-deploy CHANGELOG note)

review-hardened

U2

Two-step: create mints PENDING; POST …/{id}/activate performs the witnessed transition at business_date = transfer_on (derived = S2S ended_on, never caller-fed). Activation REFUSES while any other PAYING-CAPABLE (active/suspended) agreement exists for the child — ERR terminates first, deterministic order, no MultiProgram window. As-built narrowing (C2): cross-program PENDING siblings are not checked — family pendings are unrepresentable (F12) and a pending pays nothing; the only reachable shape is an imported cross-program pending, which carries no payment risk

user fork + review

U3

550-vs-552 = caller-chosen program + relationship_evidence_key: REQUIRED for sg (the TANF degree-of-relationship documentation), OPTIONAL for nrsg (documentation supporting the non-relative classification). UAS numerics stay #1072

review (meaning defined)

U4

CRAIG_FINANCIALSUBSIDY_SGENABLED = enrollment consent: gates the sg/nrsg create arms
activate ONLY. Lifecycle of existing rows is grandfathered — generator, ALL transitions (incl. guardianship_finalized), reviews, sweep run ungated (a knob-off flip must never strand owed money or block the corrective ERR termination). Both semantics tested

review (was contradictory)

U5

Auto-activation on placement.ended(guardianship) DEFERRED (#1113, own knob); the handler gains reason-aware WARN arms only

user fork

U6

Full BFF surface; create/activate affordances visible to county_director/admin roles (the real authority floor); backend proofs remain the authority

user fork + review

F1

STRICT signed_at < transfer_on; same-day refused (typed) — ALSO a DB CHECK. ⁂

forced — see Rationale

F2

Stored money boundary: activation writes payments_begin_month = max(first-of-month-after(transfer_on), first-of-month-after(tanf_terminated_on)). Generator: months < payments_begin_month (WHERE set) → None(SkipReason::BeforePaymentsBegin) + counter. Keys on the flow-written FACT — legacy/seeded/native-shaped rows (NULL) untouched

review (absorbs TANF + the 1st-of-month edge + the too-broad-native predicate)

F3

DELETE the generic stamp-based create_agreement — native creation = exactly two proof-typed store fns (ERR’s create_agreement_active; new create_guardianship_agreement deriving EVERY temporal/attribution field from signed_at + the proof; signed_at ≤ today store-checked). Test callsites port; unrepresentable shapes use import_agreement_history (stamps, #1071)

review (partial de-stamping still left forgeable fields)

F4

ApprovalAction::{CreatePending, ActivateGuardianship}, county floor (the County Director approval the policy names). Belt: transition_status refuses native-family pending→active outside the witnessed path. Activate replay-after-success with matching derived transfer_on → 200 (real idempotent replay, not CAS mislabeled); mismatched → 409

review + user principle

F5

Review anchors derive from APPROVAL dates (22.9), never from transfer_on: provisional at create (the initial approval act’s business date +12/+6), re-derived at activation (the activation act = "most recent approval"). The interval rows' approved_at stamps ARE the modeled approval dates. Zero-review-rows asserted at activation; ADR-056 defines the one-time anchor-rewrite semantics (stamped by the activation act; the transitioned event + interval stamp are the audit trail). ⁂

review (transfer-anchoring contradicted 22.9)

F6

Create idempotency = the F8 pair, split out of ErrIdentityInsert into shared identity-insert fields; activation idempotency per F4

house pattern

F7

Activation witnesses: S2S re-read of the snapshotted placement_idended
end_reason=guardianship + child match + permanency_goal ≠ reunification (proxy for the non-reunification order, ⁂); transfer_on = ended_on; transfer_on ≥ current_month − 12 (the reconcile floor — later activation needs the #1115 remediation path, typed refusal names it); REQUIRED court_order_evidence_key; OPTIONAL past-dated tanf_terminated_on attestation (TANF still active ⇒ activate after it ends; runbook + ⁂). Residence floor re-checked IN STORE from the stored placement_started_on snapshot (clamped calendar-month arithmetic; boundary + leap tests) — the S2S check is advisory UX, the store check is the guarantee

review, sharpened

F8

Create witness: placement EXISTS + ACTIVE (the residence-under-supervision evidence); derivations from S2S; no type constraint, no floor at create. Citizenship/residency, child income, funding availability, and caregiver-identity-on-placement linkage are RECORDED UNVERIFIED MANUAL PRECONDITIONS (form + runbook attestation text; ADR-056; #1114 for hard checks). ⁂

review (honest scope)

F9

Handoff: shared typed PlacementEndedPayload; guardianship_finalized ERR-only, county floor, S2S-witnessed (ended + guardianship + child match) and business_date REQUIRED == derived ended_on (typed refusal naming the expected date); NOT knob-gated (U4); reason-aware WARN arms in handle_placement_ended

review-hardened

F10

Migration (§G1 C1): guardianship_transfer_on, payments_begin_month, court_order_evidence_key, tanf_terminated_on, caregiver_assessment_evidence_key (recon-discovered addition — the wire-required assessment reference had no column); three-shape snapshot CHECK; flow-coupling CHECK; signed_at < transfer CHECK; boundary month-start CHECK; family one-open unique index

design + review

F11

G0 fix-MR (#1111, merged): date-only form values → noon-UTC instants (T12:00:00Z, the #1109 seed-instant precedent); caller audit; without it every date witness in this flow sat on a false calendar

review (blocked the flow’s date truth)

F12

SG/NRSG family exclusivity: one-open-per-child unique index over program IN ('sg','nrsg')
both family history locks taken sorted at create + cross-program conc test

review

Rationale for the non-obvious calls

  • F1 is structural: the pending interval opens at signed_at and validate_transition_date is strictly-after — same-day would need an empty interval [d, d). Matches the literal policy text; DFCS wanting same-day = an ADR-052 revision.

  • F2 replaces an earlier TransferMonth-rule design: one stored boundary fact handles the strict "month after transfer", the TANF later-of rule, and the transfer-on-the-1st edge uniformly — and keying the generator on the fact (not "native shape") leaves seed families A/B and any legacy native-shaped rows untouched (verified in tools/craig-seed/src/datagen.rs: A/B carry no F8 pair and will carry a NULL boundary; their pinned 2025 activation months sit below the 12-month generator reach; regression-pinned).

  • F5’s re-grounding: 22.9 runs review clocks from approval, and the activation act IS a county-director approval (it mints the proof) — so "most recent approval" is modeled by the activation itself, dues can never pre-date activation, and no transfer-date anchoring exists to contradict policy. Long-pendency behavior is thereby defined, not accidental.

  • The 1st-of-month gap month is policy’s own artifact: ERR truth-dated to a day-1 transfer is inactive at month start (half-open intervals); SG starts the next month per the strict text. Nobody pays that month. Recorded ⁂ prominently — mid-month transfers keep ERR’s full final month (standing whole-month semantics).

  • F9’s floor: guardianship_finalized at COUNTY (expected permanency outcome); guardianship_dissolved stays REGIONAL (exceptional reversal).

  • Deleting create_agreement (F3): partial de-stamping would leave status/terms dates, both anchors, and created_by_name forgeable by any store caller. Two typed creation fns + the stamp-based import path is the complete story; a "generic create" has no remaining consumer.

Program shape

MR Branch Ships

G0

fix/1111-date-only-noon-instants

The F11 normalization fix + caller audit + tests (merged — see Status)

G1

feature/1070-sg-backend

Contracts, migration, settings knob, store (typed creates, activate, reason token), generator boundary rule + invariants, API + events + ruleset v1.4.0, compose knob, seed family E (+ ERR E), one-line BFF ripple, backend docs (ADR-056, CHANGELOG, data-model, config-reference, implementation-guide event payloads, financial + placement API pages, shared-crates, state-machines, ADR-054/ADR-055 sentence amendments)

G2

feature/1070-sg-web

Guardianship form, detail-page Activate, end-placement interstitial + flash, e2e, web docs
runbook, ⁂ comment, AC walk + close #1070, epic tick, plan → archive, CLAUDE.md pointer

Per-MR: the standing pipeline — fmt/clippy/nextest → fresh-subagent J1–J8 per substantive commit → token-gated commit → battery push (pre-flight ALL cheap gates first: clippy, quality-budgets, plan-lint, route-role-coverage, axis-coverage, check-docs) → MR → merge → J-record → issue notes → Status row updated here.

G1 — backend (commits C1–C5, each J-reviewed)

C1 — contracts + migration + settings

  • craig-placement-contracts/src/subsidy.rs: PlacementSubsidyEligibility gains #[serde(default)] ended_on: Option<NaiveDate>, end_reason: Option<String>, permanency_goal: Option<String> (additive; Eastern projection stays placement-side).

  • craig-placement-contracts/src/events.rs: PLACEMENT_ENDED const + typed PlacementEndedPayload { placement_id, end_reason: Option<String> }; the const replaces string literals in the producer, financial’s subscription binding, AND the dispatch arm.

  • craig-financial-contracts/src/subsidy_agreements.rs — the U1 tagged enum:

    • CreateErrAgreementBody: today’s fields verbatim.

    • CreateGuardianshipAgreementBody: client_request_id, placement_id, form_number, form_version, signed_at, agreement_document_key?, approving_county, legal_county?, relationship_evidence_key? (wire rule: required non-blank for sg, optional for nrsg — U3), caregiver_assessment_evidence_key (required — the approved kinship assessment / foster-home evaluation reference), legal_reference?, predecessor_agreement_id? (validated: same child, program ∈ {sg,nrsg}, terminal, terminated before signed_at — family re-application lineage ONLY; the ERR→SG handoff deliberately has NO predecessor link), parties, monthly_amount (wire string), amount_basis, and the ERR body’s four education fields.

    • CreateClosedProgramBody {} for ersg/enrsg/rcs/ercs.

    • ActivateSubsidyAgreementRequest { expected_head_interval_id, court_order_evidence_key, tanf_terminated_on?, legal_reference? } — no dates beyond the TANF attestation; transfer_on derived. Garde: hand-implement Validate per-variant if the derive fights the tagged enum.

    • Detail contract gains guardianship_transfer_on, payments_begin_month, court_order_evidence_key, tanf_terminated_on (serde-defaulted).

  • craig-financial-contracts/src/subsidy_generation.rs: #[serde(default)] skipped_before_payments_begin: u64 on the report.

  • craig-common: SubsidySgSettings (mirror of SubsidyErrSettings), field subsidy_sg; problem_types::CLOSED_PROGRAM. Every settings literal / bootstrap test gains the field (compile-driven sweep).

  • Migration (all on subsidy_agreements unless noted):

    • ADD guardianship_transfer_on DATE, payments_begin_month DATE, court_order_evidence_key VARCHAR(512), tanf_terminated_on DATE, caregiver_assessment_evidence_key VARCHAR(512).

    • DROP subsidy_agreements_approval_clock_valid; ADD the three-shape snapshot CHECK — all-NULL (legacy/import) | ERR clock pair (both set, due > start) | family residence (program IN ('sg','nrsg')placement_started_on set ∧ clock columns NULL).

    • Flow-coupling CHECK: guardianship_transfer_on IS NULL OR (program IN ('sg','nrsg') AND placement_started_on IS NOT NULL AND create_request_id IS NOT NULL AND court_order_evidence_key IS NOT NULL AND payments_begin_month IS NOT NULL AND guardianship_transfer_on > placement_started_on AND signed_at < guardianship_transfer_on).

    • Boundary CHECK: payments_begin_month IS NULL OR (payments_begin_month = (date_trunc('month', payments_begin_month::timestamp))::date AND payments_begin_month > guardianship_transfer_on).

    • CREATE UNIQUE INDEX subsidy_agreements_one_open_guardianship_family ON subsidy_agreements (child_id) WHERE program IN ('sg','nrsg') AND current_status IN ('pending','active','suspended').

  • Constraint probes: each legal shape, each illegal mix, both boundary CHECKs, the family index (two children pass; one child two programs refused).

C2 — store

  • approvals.rs: CreatePending + ActivateGuardianship (county floor); bindings; ActivateGuardianship admits BOTH its own action AND exactly Transition{Pending→Active} (the leg-binding precedent).

  • store.rs:

    • NEW CreateGuardianshipParams + create_guardianship_agreement(conn, params, proof, today) — derives interval/terms starts from signed_at, provisional anchors from TODAY (the approval act, F5), attribution from the proof; store-checks signed_at ≤ today; takes BOTH family history locks sorted (F12); F8 pair + placement_id + placement_started_on + evidence keys written on identity; cross-family open check + predecessor validation + coverage overlap for both family programs.

    • DELETE create_agreement + CreateAgreementParams; port the test callsites (typed fns or import_agreement_history).

    • Split the F8 pair + placement_started_on + relationship_evidence_key out of ErrIdentityInsert into shared optional identity-insert fields; a guardianship bundle carries the assessment key.

    • NEW activate_guardianship_agreement(conn, params, proof, today) with ActivateGuardianshipParams { agreement_id, expected_head_interval_id, transfer_on, tanf_terminated_on, court_order_evidence_key, legal_reference, residence_floor_months }: FOR-UPDATE lock → replay recognition (already Active with equal guardianship_transfer_on → Ok(replayed), F4) → family/native/pending CAS → signed_at < transfer_on → residence floor from the stored snapshot (clamped month-add; typed short-by-N refusal) → transfer_on ≥ current_month − 12no other open agreement for the child (ActiveSiblingAgreement, names it — U2 ordering) → derive + write payments_begin_month, guardianship_transfer_on, court evidence, TANF attestation, legal_reference (NULL→set ok; equal no-op; different → typed conflict) → re-derive anchors from today (F5; assert zero review rows) → witnessed crate-private transition entry (close head at transfer_on, open Active, projection, maintain_chain_after_transition — slots + reconcile enqueue ride the existing machinery).

    • transition_status belt: refuse native-family pending→active naming the activate path.

    • Vocabulary: guardianship_finalized in the craig-reference termination superset
      ERR_ONLY_TERMINATION_REASONS; store arm requires program == Err ∧ anchored placement.

    • New store-error variants mapped in write_store_error (as-built, the ERR precedent): ClosedProgram → the named CLOSED_PROGRAM type; ActiveSiblingAgreement
      LegalReferenceConflict → typed CONFLICTs; the validation-class refusals (SigningNotBeforeTransfer, ResidenceFloorShort, TransferBeforeReconcileFloor, WitnessedActivationRequired) ride the generic bad-request problem type with their rich messages PINNED by the C4 api tests (per-variant type URLs considered and dropped — ERR’s own witness refusals set the precedent).

C3 — generator + policy + invariants

  • generator.rs: SkipReason::BeforePaymentsBegin; rule after per-diem exclusivity, before active-as-of: agreement has payments_begin_month AND month_start < payments_begin_month → None + counter (reconciling arm voids stray undisbursed rows via the existing reconcile_expected_none machinery, cause subsidy_reconciliation).

  • policy.rs: guardianship_residence_months: u32 (Georgia = 6, ⁂) on SubsidyPolicy.

  • Invariants (test-lib SQL), keyed on the flow fact so legacy rows never fire:

    • sg_transfer_matches_activation.sql: rows WHERE guardianship_transfer_on IS NOT NULL — equals the earliest Active interval’s effective_from; completeness arm — family program ∧ create_request_id IS NOT NULL ∧ an Active interval EXISTS ∧ transfer NULL → fires (keyed on interval existence, not the current projection, so termination can’t hide it).

    • sg_no_payment_before_boundary.sql: rows WHERE payments_begin_month IS NOT NULL — no live payment with payment_month < payments_begin_month.

    • sg_residence_floor.sql (GA devstack): transfer set ⇒ placement_started_on + 6 months ≤ guardianship_transfer_on.

C4 — API + events + handlers + ruleset

  • api/subsidy_create.rs + subsidy_create_wire.rs: tagged dispatch — closed arms → typed CLOSED_PROGRAM; err arm = today’s pipeline with the LEGACY tag-free canonical hash (U1); sg/nrsg arm: SUBSIDY_SG gate FIRST → wire validation (shared helpers; U3 per-program relationship-key rule) → tagged canonical hash → authz (operation:"create", program attr) → require_georgia → F8 idempotency BEFORE volatile deps → policy → S2S active-placement witness → one tx (mint CreatePending proof → create → created event) → 200; race-loser re-runs idempotency.

  • NEW api/subsidy_activate.rs: gate FIRST → garde → preread → authz (operation:"activate") → require_georgia → policy → S2S witnesses (F7) → tx { recheck assignment, mint proof, store activate, publish transitioned } → 200 detail. Registered in api/mod.rs + utoipa.

  • subsidy_mutations.rs: PlacementClient injected; guardianship_finalized → pre-tx S2S witness (ended + guardianship + child match) AND business_date == ended_on required (typed refusal naming the expected date); S2S down = 503. NOT gated (U4).

  • Events: publish_subsidy_agreement_created gains effective_from + initial_status params — an ADDITIVE payload key for ERR too (stated honestly; parser arm unaffected; family tests updated). Financial’s local ended-payload replaced by the shared contract; handle_placement_ended F9c WARN arms (guardianship ± pending sg/nrsg; non-guardianship end
    pending family agreement; absent end_reason degrades to today’s behavior); the placement producer + subsidy-eligibility projection updated. subsidy_generation_completed gains the skipped_before_payments_begin key (manually enumerated payload + telemetry aggregation).

  • Ruleset v1.4.0: operation:'activate' row; ruleset tests extend the enumerated operation coverage (allow/deny × program × jurisdiction × role + unknown-operation).

  • main.rs: Extension(SubsidySgSettings) wired beside subsidy_err; docker-compose.yml knob "true" beside the ERR knob; BFF ripple: the ERR create_body gains "program": "err".

C5 — seed + battery

  • Family E: child + caregiver persons, ACTIVE kinship placement E started as_of − 7 months − a few days, plus an ACTIVE native ERR agreement E on placement E (fixture-C shape) — the e2e’s raw material for the FULL handoff arc. As-built money/clock shape (deviation recorded): the approval clock is future-dated DECOUPLED from start+120 (the placement started ~7 months back, so the policy-derived due lapsed; a fresh seed must not show an overdue badge), and E seeds exactly ONE payment row — the as-of month’s Full unit month — because the deployment-wide generation contract test pins generated == 0 for a scope-All current-month run over a fresh seed; the ~6 back months stay unpaid and unreachable by any automated path (scheduled batches generate only the current month; the reconcile queue holds no pairs for E), keeping the e2e’s handoff-money slate clean. Families A/B UNCHANGED (see Rationale). Re-bless FINANCIAL + PLACEMENT + CASES SHAs (persons live in cases); regenerate tests/e2e/lib/seed.ts; update the pinned-ID exclusion test. verify-seed + full cargo xtask invariants green on a fresh keyed reseed.

G2 — web + e2e + web docs + close-out

  • NEW routes/financial/subsidy_guardianship.rs + subsidy_agreement_new_guardianship.html: program radio ("Relative (552)" / "Non-relative (550)"), per-render client_request_id, signed_at help text, the F8 manual-preconditions attestation text, verbatim-4xx flash, PRG. GET /financial/subsidy-agreements/new-guardianship + POST /financial/subsidy-agreements/guardianship; NEW POST /financial/subsidy-agreements/:id/activate BFF handler (hidden expected_head_interval_id, court_order_evidence_key, optional tanf_terminated_on + legal_reference; PRG; verbatim 4xx). All three routes layered + role-comment-markered; visibility gated county_director/admin (U6). Module registration, main.rs wiring, list button, ftl keys.

  • Detail page: transfer date + payments-begin month + court-order key rendered; Activate form ONLY for flow-shaped pending rows (family program ∧ pending ∧ create_request_id present ∧ no import provenance — BFF-computed from the detail contract); the generic transition form suppresses the guaranteed-to-fail active target for pending rows; provisional review anchors on pending rows marked non-actionable.

  • end_placement: reason=guardianship + NO pending family agreement + no acknowledgement → PRG-refuse with flash + an acknowledgement checkbox ("support will stop") — the pre-finalization check. Post-end flash is TEXT-ONLY (flashes are escaped): names the pending agreement when one exists, no fake links. The pending lookup = two list calls (sg, nrsg); partial failure → WARN
    conservative message.

  • e2e subsidies-guardianship.spec.ts — added to the county project’s testMatch (subsidies-(county|guardianship)), dana.county, ONE serial arc on fixture E (retry posture documented: state-mutating, reseed-first, county-spec precedent): attempt end (guardianship) → interstitial refusal (no pending agreement); create pending SG with signed_at back-dated to yesterday (Eastern — F1 makes a same-day create+transfer arc impossible); F8 replay of the same rendered form → same agreement; premature activate → verbatim "placement is still active" refusal; end placement E (guardianship) → passes the pending check; activate → refused naming the ACTIVE ERR (U2); terminate ERR E via the transition UI, reason guardianship_finalized (witnessed); activate → success; detail shows active
    transfer date + payments-begin month. The existing placement spec’s arbitrary-active-placement selector is pinned AWAY from fixture placements. bobsmith.spec.ts: button absence
    /new-guardianship 403 (write-parity). Closed-cohort / floors / money boundaries stay API/store-level (deterministic dates).

  • Web docs + runbook (developer-guide: enrollment → finalization → handoff → activation walk, TANF-still-active note, generator cadence); ⁂ comment on #1073; AC walk + close #1070; epic tick; plan → archive + nav; CLAUDE.md status pointer.

Test matrix (all @axis-tagged; representative)

Layer Case Axis

store/create

Typed guardianship create atomic; attribution = proof; signed_at ≤ today; F8 pair persisted; sg-vs-nrsg family race — one winner (index + sorted locks); closed programs typed; ERR arm refusal retained; predecessor validation set (wrong child/program/open/chronology)

happy, sad, evil, conc

store/activate

Mid-month: head closed [signed_at, transfer_on), transfer + boundary + evidence written, anchors re-derived from TODAY, slots materialized, reconcile enqueued; same-day signing; future/pre-floor transfer; residence short by one day (+ leap/EOM month-add pins); sibling ACTIVE ERR named; non-pending head; imported row; belt refusal naming activate; legal_reference conflict; double-activate replay-after-success (equal transfer) → Ok replayed; mismatched → StaleHead; concurrent FOR-UPDATE race

happy, sad, evil, conc, replay

store/terminate

guardianship_finalized: ERR-only, county proof, child match, business_date == ended_on required

happy, sad

generator

Boundary pins: transfer 1st / mid / last day; TANF later than transfer shifts the boundary; months < boundary skipped + counted; boundary month pays Full; truth-dated ERR + SG around the boundary (mid-month → ERR full final month, no overlap; 1st-of-month → gap month ⁂ pinned); un-terminated ERR into SG months → MultiProgram fail-closed backstop; NULL-boundary rows (seed A/B shape) untouched — regression pin

happy, sad, replay

api/create

ERR regression (LEGACY hash: pre-upgrade stored hash still replays 200); tagged sg/nrsg 200 pending; closed → CLOSED_PROGRAM; per-arm gate-off names the right knob; F8 replay with placement DOWN still 200; U3 relationship-key rule per program; wire battery

happy, sad, evil, fault, replay

api/activate

Happy; each F7 witness refusal typed (type URLs pinned); S2S down 503; caseworker 403; gate-off 403 naming the knob; stale CAS 409; replay 200

happy, sad, evil, fault, replay

api/transition

guardianship_finalized witness matrix (no-placement / wrong reason / wrong child / wrong date / down)

sad, fault

constraints

Three snapshot shapes, flow-coupling, boundary month-start, family index, signed_at < transfer

evil

settings/gate

Knob OFF: create+activate 403; existing SG still generates, transitions, reviews (U4 grandfather pins)

sad, replay

events + handler

Created payload (pending SG: effective_from = signed_at, initial_status = pending; ERR additive key); typed ended payload round-trip + const sweep; completion-event counter key; ended arms (guardianship ± pending-SG, disruption + pending-SG WARNs; absent reason degrades)

happy, sad, replay

ruleset

Activate operation coverage (allow/deny/program/jurisdiction/unknown-op)

sad, evil

e2e

The dana.county 8-step arc + bobsmith parity

happy, sad, replay

Risks

# Risk Mitigation

1

U1 changes the ERR wire

Legacy-stable ERR hash (durable state safe); lockstep-deploy CHANGELOG note; BFF ripple in G1; ERR api suite re-pins

2

F5 anchor rewrite touches the open head terms row

Two columns, one edge, in-tx, zero-review-rows asserted; ADR-056 defines one-time semantics
audit trail

3

F2 sits inside the ONE month judgment

Boundary battery + sg_no_payment_before_boundary invariant + fact-keyed predicate (legacy rows exempt by construction)

4

Deleting create_agreement breaks tests

Compile-driven port to the typed fns / import path; the diff names every site

5

S2S coupling (create, activate, terminate)

Fail-closed 503; mock-server fault seam

6

Seed churn (3 SHAs + seed.ts + exclusion test)

One C5 commit, dated comments

7

Placement terminal facts remain mutable (#1112, filed)

Activation snapshots at its instant; invariant stays internally consistent

  1. Residence floor = 6 months (manual 6-vs-12 discrepancy), clamped calendar-month arithmetic.

  2. STRICT signed_at < transfer_on; same-day refused.

  3. transfer_on = the anchoring placement’s end date.

  4. Payments begin max(month after transfer, month after TANF termination); TANF termination is an UNVERIFIED operator attestation; TANF-still-active ⇒ activate after it ends.

  5. A 1st-of-month transfer leaves that month unpaid by BOTH programs (strict-text artifact — flagged for explicit confirmation); mid-month transfers keep ERR’s full final month.

  6. Review anchors run from the initial / most-recent APPROVAL act (create, then activation).

  7. guardianship_finalized ERR termination at county floor, truth-dated to the transfer.

  8. One family knob = ENROLLMENT consent; existing-agreement lifecycle grandfathered.

  9. No placement-type constraint at create; placement existence = supervision evidence.

  10. permanency_goal ≠ reunification as the non-reunification-order proxy at activation.

  11. Citizenship/residency, child income, funding availability, caregiver-identity linkage: unverified manual preconditions (attested, recorded; #1114).

  12. sg requires degree-of-relationship evidence; nrsg’s relationship key is optional documentation.

Review dispositions (external findings → where fixed)

Finding Disposition

Eligibility facts missing (citizenship/income/assessment/court order/CD approval/TANF relationship)

F7 (court evidence, goal proxy), F8 (assessment key, manual preconditions + #1114), U3 (relationship meaning per program), F4 (the county proof IS the CD approval)

No caregiver↔placement linkage provable

F8 recorded limit + attestation + #1114

Review clock contradicts 22.9 / no approval date modeled

F5 re-grounded on approval acts; interval approved_at stamps = the model

TANF boundary omitted

F2 + F7 tanf_terminated_on attestation + ⁂4

Date-only → midnight-UTC off-by-one

G0 fix-MR (F11, merged !1035)

ERR handoff date forgeable / 1st-of-month contradiction / generation ordering

F9 (business_date == ended_on required), U2 (activation refuses while ERR open), Rationale
⁂5 (gap month recorded), generator backstop test

Post-end "create one" impossible

The pre-finalization interstitial + acknowledgement (G2); text-only flash

Tagged request breaks durable idempotency

U1 legacy-stable ERR hash; lockstep-deploy note; pre/post-upgrade replay test

Gate semantics unsafe/contradictory

U4 enrollment-consent + grandfathered lifecycle, both tested; guardianship_finalized ungated

Store still forgeable

F3 deletes the generic create; typed params derive everything

SG+NRSG both open

F12 family index + sorted locks + conc test

Delayed activation vs 12-month floor

F7 floor refusal + #1115

source_system IS NULL too broad

F2 keys on payments_begin_month; belt keys native-family only (generic create deleted); A/B regression pin

Store can’t recheck residence / month arithmetic undefined

F7 store-side floor from the snapshot; clamped month-add + leap/EOM pins

Migration under-constrained

F10 flow-coupling + signed<transfer + boundary CHECKs; family index

Completeness invariant hides after termination

C3: keyed on Active-interval existence

Predecessor semantics conflated

C1: family re-application only; ERR handoff has NO link

Re-anchor vs existing review rows

C2: zero-review-rows assertion

Append-only violations un-audited

F5 ADR-056 one-time semantics; identity writes enumerated; legal_reference conflict rule

Placement terminal facts mutable

#1112 filed; Risk 7

guardianship_finalized child check

F9 child match

CAS ≠ idempotency

F4 replay recognition → 200

legal_reference/note semantics

C2 conflict rule; the note field DROPPED

Only CLOSED_PROGRAM typed

C2 store-error variants + pinned type URLs

Transfer date absent from read surfaces

C1 detail contract + G2 views

No BFF activation route

G2 activate handler/route

Flash links impossible

G2 text-only flashes

Single-program list filter

G2 two calls + partial-failure posture

Imported/legacy pending affordances

G2 flow-shaped-only Activate; transition form suppresses active

Supervisor-vs-CD visibility

U6 role-gated visibility

Missing nav/button/ftl/registration

G2 enumerated

Pending review anchors exposed

G2 non-actionable marking

Nonexistent generation-report surface

Dropped from G2

Playwright project mismatch

G2 testMatch update

Serial arc retry safety

Documented reseed posture (county-spec precedent)

Placement spec consumes fixture E

G2 selector exclusion

Fixture E lacks ERR → handoff untested

C5 adds ERR E; the 8-step arc covers the full handoff

CASES sha + seed.ts + exclusion test

C5

Counter missing from event/telemetry

C4 completion event + aggregation

Settings injection + literals

C4 wiring + compile-driven sweep

Ruleset test enumeration

C4 coverage additions

PLACEMENT_ENDED literals / "payload unchanged" claim

C1 const sweep; C4 states the additive key honestly

G1 not doc-complete / wrong event-catalog page

Backend docs moved into G1; implementation-guide targeted

Wrong assertions (unreachable/CAS/identical/untouched)

Corrected throughout

Edit this page · latest