Plan: SSA Benefit Verification (SOLQ / SDX / BENDEX) for IV-E Eligibility

On this page

Status

Program of record for issue #162, restructured as a six-epic tracking program on 2026-08-14 (plan v2). The v1 draft (11 steps, all Not started, authored pre-adapter-pivot) was rejected by an external stop-ship review (13 stop-ship, 25 high-severity, 9 construction findings); every finding is folded — see Findings traceability.

Unit Description Status

U0

This plan rewrite + GitLab program restructure (epic split, product-decision issue, contract chores)

Done (2026-08-14) — plan v2 on main (eab9a332); epic &81, #1427–#1434

A1

craig-partner-ssa-solq crate, UNREGISTERED (categorical errors; no in-crate mock; #[non_exhaustive] wire placeholders)

Done (2026-08-14) — #1427; discard-at-adapter (the transport byte-cap rides B4 S13 per traceability)

A2

Dispatch-class guardrail: DispatchClass::TypedOnly + hard rejection in generic send / retry / worker (3 checkpoints)

Done (2026-08-14) — #1428; erased-seam refusal added as a 4th innermost layer

A3

Registration LAST: bundle entry, PINNED_KINDS 11→12, lockstep + rejection tests, partner-audit codec, seed inventory, active=true refusal

Done (2026-08-14) — #1429; seed = deliberate ABSENCE (born-active rows would violate the refusal)

A4

SsnDigest versioned newtype in craig-cases-contracts

Done (2026-08-14) — #1430; wire-only (B1 decides the column shape)

A5

ADR-065 (benefit-data classification/custody/retention) + ADR-066 (typed dispatch + pipeline + counterparty model)

Done (2026-08-14) — #1431; UD1–UD3 drafted as options, ratified later the same day (#1434)

B1

Screening schema: runs/members/typed jobs, DDL invariants, cohort binding, provenance-grade rows

Done (2026-08-14) — #1462; digest column = tagged single TEXT; cohort_hash/UD11 horizon shared via craig_cases_contracts::screening_cohort

B2

Cases custody extension: value-bound plaintext release (expected_digest), exchange-only allowlist, fail-closed custody gate

Done (2026-08-14) — #1463; POST /v1/cases/persons/{id}/ssn-release, version-before-bytes ct compare, refusal audit event, body-borne stage-time attribution (§D2 as-built)

B3

Request path: cases-owned BOLA-checked intent → cases-only exchange endpoint, atomic claim-first, new authz ResourceTypes + GA/TX fixtures

Done (2026-08-14) — #1464; the WEB mint itself lands with E1’s form (no screening UI exists until E1) — B3 delivered the client-held-id protocol it rides

B4

Typed SSA worker: last-responsible-moment re-checks, value-bound JIT, wire/persisted separation, egress controls, lease budget

Done (2026-08-14) — #1465; EgressControlledTransport (the A1 byte-cap lands), SsaWorker sequential-batch lease math, endpoint-shaped activation lift

B5

Results + benefit facts per the ratified #1479 design: SolqSendAuthority witness gate + contract-pending pause; run lifecycle (complete_run_if_final from every member-terminal writer, three-token invalidation + one-tx sweep, fork-1 cancel + #1469 resume); cohort-revision invalidation delivery (watermark-hardened staging, auto-supersede); the cases-side outcome tx (ssa_screening_outcomes projection, guarded ssn_verified CAS per UD2, fact chain); mutation-quiescent retention posture

Done (2026-08-21) — #1466, four MRs each behind its own contextless J-review + full battery: U1 !1445 (witness + run lifecycle; trybuild unconstructibility probe under nextest; the paused gauges zero-filled for the cumulative bridge), U2 !1446 (outcome tx + fact chain + projection; the review-forced DOMAIN replay guard — post-prune redeliveries are pure replays, never a re-CAS on a revoked person or divergent facts; lock-first CAS staging person_updated), U3 !1447 (revision seam + P1–P5 bumps with P5’s attempt-tx threading; D15 watermark three-way + fork-3 supersede; the pre-producer inbox consumer; fork-1 cancel — UD3-knob-exempt, audit event rides B6; the battery itself caught + fixed a cases→exchange test-residue isolation defect), U4 close-out (ADR-058 § Amendment #1466 mutation-quiescent admission + the spec.rs rewrite; the exchange ≥365 d load() floor; as-built notes on ADR-065/066). #1469 (resume) and the B7 race-matrix rows stay their own units.

B6

PII-safe domain events + audit codec, split per #1479 D17: B6a catalog (ADR-003: case.screening_cohort_changed, exchange.screening_run_invalidated) + security parser arms; B6b producers — the inbox consumer/binding + revision bumps shipped in B5 (consumers-before-producers); B6b also owes the fork-1 cancel’s actor-naming audit event (recorded B5 deviation)

Done (2026-08-21) — #1467, two MRs: B6a !1449 (catalog + parser arms + the exchange.screening_ resource-id family, parsers-before-producers), B6b (the producers: publish_screening_cohort_changed staged INSIDE the screening_revision seam with per-case post-bump revisions; exchange.screening_run_invalidated staged in all four invalidation writers' own txs — inbox, fork-3 supersede with superseded_by_run_id, worker freshness, and the fork-1 cancel carrying user_id = the relayed requested_by, discharging the recorded B5 deviation)

B7

Phase-B verification battery (V1–V5, V7, V9) — phase exit gate

Done (2026-08-21) — #1468, one MR closing every implementable matrix gap the three-lens coverage audit found over B1–B6’s existing legs: V9 transport (http-non-loopback via TEST-NET literal, the 301/302/307 class, the non-2xx byte-cap), the V1 canary two-leg sweep (real Err(BadStatus) shape + the 2xx echo path, DB/event/TRACE-log sinks with a capture-liveness sentinel) + the shared leak_scan SSN-shape detector sweeping BOTH services' OpenAPI documents, V2.3 twin digests, V4 genuine concurrency (tokio::join staging races + the ssa_jobs_lease scratch fence suite — all four stale stamp shapes + the outcome-path member fence), V5 dispatch-time partner drift + outcome-outage retryable, V7 facts DDL probes (+ the exchange DDL suite’s base-only-schema VACUITY fix with positive-vocabulary companions), concurrent supersession via the 23505-retry loop, the outbound-unmappable wiremock pin, ssn_stale run-completion, in-flight convergence at reclaim, V3 exchange-cancel allowlist/binding + generic send/retry 422 against a real ssa_solq row + the LIVE broker→audit_log actor-row e2e (proving the B6a arms), and the retention PruneCountMismatch injection + heal-retry. Recorded residue, each with an owner: V3.4 cross-jurisdiction live-HTTP = #1369; V4.6 WEB leg = E1; resume/attempts-reset = #1469; the V9 rebind-pin + real-TLS-handshake legs are not constructible without DNS control (mapping-level covered); V1 panic-diagnostics rides the panic-text bound doctrine; the worker’s post-send fence / finalize_after_zero_stamp direct interleavings are covered at the store-fence layer only

C0

SDX/BENDEX contract acquisition

Blocked (external — Gateway/DHS record contracts unobtained; interim typed refusal)

C1

Import schema + state machine (ADR-057-exact finalize semantics, resource limits, attested checksum)

Not started

C2

Matching + reconciliation (ambiguity fail-closed, correction supersession)

Not started

C3

Test-only fixture generation (no runtime proxy)

Not started

D1

zen-engine semantics spike: executable absent-key fixtures BEFORE any ruleset edit

Done (2026-08-21) — #1548: the S7 truth table PROVEN by committed executable fixtures (absent/null keys skip equality+comparison cells and fall to the "" catch-all; first-hit order pinned; the transparency theorem probed on the D2-shaped two-input table; the SHIPPED deprivation table answers an absent key through its production catch-all), the 12-fixture byte-identical ive_eligibility baseline corpus (full-document equality; regeneration = a ruleset-change act), and the deprivation-table design recorded as ADR-066 § Amendment #1548 — mechanics ACCEPTED (they realize the ratified UD13), the fact→basis POLICY mapping PROPOSED with ratification gating D2/D3 (no production ruleset touched)

D2

Ruleset rollout mechanics (promotion, canary, pinning, rollback) + the deprivation-table SSA rows per the ratified mapping

Done (2026-08-22) — #1556/!1458 (merge bd77de59): same-tx snapshots keyed by the CAS token (baseline-backfilled), hash-bound two-step promote with the isolated shadow canary + thin-corpus consent (traffic-split REJECTED as policy), restore-as-new-revision rollback, supervisor-gated NON-OPERATIVE pinning, revision stamping on every evaluation, the two audited lifecycle events + parser arms, xtask rules-promote (two-invocation), seed content-drift convergence, the revision-gap invariant; DELETE requires ?reason= (pre-1.0 breaking). As-built: ADR-066 § Amendment #1556

D3

"Screened before determination" orchestration per UD1 + the UD5 worklist

Done (2026-08-22) — #1557 / !1459: the UD1 authoritative assembly (six-token witness ladder, F8 replay precheck, deterministic in-tx TOCTOU legs, server-only evidence derivation with the ssa_death dormancy pin, rules revision join) + the UD5 standing-alert worklist (three same-tx writers, scoped list, self-claim, audited dismiss event). Recursive attested-inputs validation; #1054 money fence held (no eligibility.evaluated producer). As-built: ADR-066 § Amendment #1557

D4

Read APIs + DTOs (never store models; cases-authorized web proxy)

Done (2026-08-21) — #1549: cases serves the household-scoped outcome list (PageRequest pagination, H12 person-XOR-run filters, typed depth 400) + current benefit facts (chain heads only; the household join is the structural scope gate — a case URL can never become a cross-case person probe) + the run-status relay from exchange’s new cases-only GET /ssa-screening-runs/latest (fork-1 relay posture; H22 pinned — no URL is representable in any view). ADR-065 §D3 realized: the shared screening_read DTOs are the ONLY exits, SsaScreeningRun no longer derives Serialize (the row maps only consumed columns), and per UD3/§D6 no read consults a feature knob. Existing indexes cover the read shapes (household join + person idx; uuidv7 id tiebreak)

E1

Web UI via cases proxy

Done (2026-08-21) — #1551: the case-detail SSA tab (run status with zero-filled member tallies + invalidation reason, outcome history, CURRENT facts — all from the D4 reads; the BFF never talks to exchange), the request-screening PRG action with a BFF-minted per-render client id + the fork-1 cancel (empty-bodied — the upstream cancel is a deterministic idempotent replay), both with VERBATIM 4xx-detail flashes (the office-refusal precedent), never-screened as an empty state (the relayed 404), and the caseworker e2e legs (panel render + the refusal-path action chain; the happy 202 path is E2’s, which owns partner lifecycle)

E2

e2e under caseworker-per-case identity (BOLA + actor relay)

Done (2026-08-22) — #1553: the 5-leg caseworker matrix (cases-ssa.spec.ts — person-edit SSN fixture, partner-inactive 5xx-redact + reads-open, happy 202 with pending tallies, cancel→invalidated→slot-released, teardown to the #1555 park posture) + the unassigned-caseworker BOLA leg in rbac.spec.ts (verbatim 403 flash). The ssa_solq slot fixture drives craig-exchange’s API as admin (ROPC; lib/ssa-fixture.ts — no craig-web partner-UPDATE route exists); admin surfaces are never navigated in the caseworker project. Gate-flip leg stays OUT per the 2026-08-21 decision (exchange unit test + B7 legs carry it; residue recorded on #1553)

E3

Ops: metrics/watchdogs/retention wiring

Done (2026-08-22) — #1566 / one MR: the UD4 handoff relation (ssa_transport_handoffs, written INSIDE the transport prune tx — the archive-success precondition discharged transactionally; FK direction handoff→member RESTRICT forcing deletion order), the hourly ssa-transport-sweep (window SSA_SCREENING__TRANSPORT_RETENTION_DAYS, load()-validated 0..=90 naming UD4, 0 refused while the archiver is consented; the jobs delete re-asserts the FULL eligibility predicate + a snapshot-exists guard, the transactions delete the hold + type legs — a mid-batch hold OR a set→clear flap = count-mismatch rollback, pinned deterministically; a mismatch-aborted pass withholds the heartbeat so a wedge alarms as staleness), UD10 holds at run granularity (legal_hold as an ELIGIBILITY-PREDICATE column — precondition-4-covered, allowlist+probe extended; human-admin set/clear endpoints, reason-required, transition-only audited events exchange.screening_hold_set/_cleared with same-MR parser arms), the exchange ArchiveEngine wiring (3 FK-ordered specs with the probe-asserted 12-month quiescence + archive_ledger + archive-fetch arm; ledger-only bookkeep — no fleet event, recorded; no purge tooling per D14/#1480), the ops metrics (ssa_screening_oldest_pending_seconds, ssa_screening_job_errors_total{class}, ssa_retry_exhaustion_total, ssa_partner_request_duration_seconds, ssa_retention_lag_rows{class}, ssa_screening_held_runs, ssa_screening_ready via the minute-cadence ssa-readiness-probe reusing live_preconditions), 2 report-only invariants, the operations/ssa-screening-ops.adoc runbook + heartbeat rows. TWO recorded deviations: (a) the C1-slice split — see the Phase-E E3 unit text; (b) "readiness degrades" lands on the METRICS plane (gauge + transition warn), not /readyz — the ssa-worker is Observed by recorded decision and a partner-side condition must never unroute the pod. As-built: ADR-065 § Amendment #1566 + ADR-058 § Amendment #1566

E4

Georgia case-section export via the existing plugin runtime

Done (2026-08-22) — #1552: plugins/ssa-screening (the FIRST case_section export) renders the SSA section at parity over the new ONE-fetch composite (/ssa-screening/summary — the W5 host fetches exactly one endpoint per render); placed by the GA case_detail.toml (span 12, row 0); craig-web gained the case_detail composed-surface consumption (tab per section, htmx mount, degrade-to-zero) and DELETED the native E1 tab per the ratified fork, keeping the request/cancel ACTION chrome host-owned (a cached fragment cannot mint the ADR-062 per-render id). As-built: ADR-033 § Amendment #1552

P

Production enablement epic (outbound auth, rotation, wire schemas, code mappings, DSA, cutover evidence)

Blocked (external — no SSA/Gateway auth or wire contracts exist)

Anchor: #162 (tracking)
Branches: one per unit, feature/162-<unit>-<slug>
Scale: ≈18–26 engineer-weeks (5 S / 7 M / 8 L / 3 XL internal units + two external blocks)

Context

IV-E eligibility determination (45 CFR 1355.52) requires SSA benefit verification for household members before eligibility is calculated. Georgia performs this through Gateway’s interface with SSA: SOLQ (real-time SSN lookup), SDX (SSI batch), BENDEX (RSDI batch). Per Melody Debussey on #162: every household member is screened before determination, and the SSA benefit-type code (survivors / retirement / disability) enables automated deprivation determination. Counterparty model (recorded in ADR-066): SSA is the data authority; Gateway is the network recipient/proxy — both identities belong in partner/DSA/compliance evidence.

Why v1 was rejected (the review’s structural corrections)

  1. Generic-send bypass (S1). Once an ssa_solq partner row existed, any authorized caller could push a raw-SSN payload through POST /v1/exchange/send//retry; it persists verbatim in exchange_transactions + exchange_send_jobs. Fix: a DispatchClass::TypedOnly concept with hard rejection at three independent checkpoints (generic intent assembly, retry restage, worker dispatch), landing before registration makes a partner row possible.

  2. Illusory type-level stripping (S2). A typed response protects only decoded success; the WIC-template error path preserves upstream bodies into last_error, exchange.failed, and logs. Fix: SSA errors are closed categorical enums — no upstream bytes in Display, persistence, events, or logs; bodies bounded-read and discarded.

  3. Unbound custody reads (S3). Fetching the current SSN at dispatch can transmit a different value than the one staged. Fix: the custody request carries expected_digest; cases releases plaintext only on exact current match; mismatch terminally marks the member ssn_stale.

  4. Production is not a knob (S4). Outbound partner auth is unimplemented (store/models.rs:10). Production enablement is the externally-blocked Phase P epic; until it closes, activating an ssa_solq partner with a non-loopback endpoint is impossible in production builds (code-level block).

  5. The IV-E claim needs an owner (S5). No authoritative orchestration loads screening witness + benefit facts today; generic /v1/rules/evaluate callers could omit or forge SSA fields. UD1 (ratified 2026-08-14): cases-side authoritative assembly.

  6. No fabricated authority (S9/S10/S11). attested_sha256 (administrator-attested, audited; signed manifest per UD7 before any recurring feed); test-only fixtures with production hard-refusal (no runtime CSV proxy); batch persistence via an explicit minimal allowlist that can only be finalized when the real record contracts arrive (C0).

  7. A real benefit-fact model (S12). Row-per-benefit with attribution, beneficiary role, amount/frequency, as-of, effective/termination, supersession chain, source precedence, provenance FKs, and replay-unique keys — not a scalar benefit_type.

  8. Egress controls (S13). The SSA outbound path: HTTPS-only, strict host allowlist, redirects disabled, bounded response bodies, resolver pinning (DNS-rebind safe).

Decisions

Settled by the review (SD1–SD14 — hard constraints)

SD1 TypedOnly dispatch for SSA kinds (three rejection checkpoints; dedicated typed worker).
SD2 Categorical, value-free SSA errors across Display/persistence/events/logs.
SD3 Value-bound custody release (expected_digest); mismatch = terminal stale member.
SD4 Production enablement = externally-blocked epic with a code-level activation block.
SD5 Exchange screening endpoint is cases-only; exact service allowlists, actor propagation, per-case BOLA, admin-only imports, new ResourceType::{ScreeningRun, BenefitFact} + GA/TX fixtures.
SD6 No ruleset edit before the executable absent-key semantics spike; explicit row precedence.
SD7 attested_sha256 — administrator-attested checksum, audited as such.
SD8 No runtime CSV proxy; fixtures compile/test-only with non-authoritative provenance
production hard-refusal.
SD9 Batch persistence via explicit minimal allowlist, finalized only with the real contract.
SD10 Row-per-benefit fact model with supersession/precedence/provenance.
SD11 Egress controls per S13 on every SSA outbound call.
SD12 SsnDigest = versioned newtype in craig-cases-contracts (scheme/key-version tag, constant-time compare, redacted Debug; no unversioned CHAR(44)).
SD13 e2e runs under the caseworker-per-case identity (admin only for import surfaces).
SD14 Wire payloads are constructed separately from persisted payloads; correlation rides an explicitly modeled field (unknown-field drop hazard).

User decisions (ALL 13 RATIFIED via #1434 — UD1–UD3/UD6/UD11/UD13 on 2026-08-14; UD4/UD5/UD7/UD8/UD10/UD12 + the UD9 split on 2026-08-16)

# Question Decision Needed by

UD1

Who enforces "screened before determination"?

RATIFIED 2026-08-14: cases-side authoritative assembly endpoint

D3

UD2

Does a digest-bound SOLQ match set persons.ssn_verified?

RATIFIED 2026-08-14: yes — exchange-only audited CAS bound to expected digest; ADR-051 amendment applied

B5

UD3

Read availability when gates are off

RATIFIED 2026-08-14: ADR-057 twin (writes gated, reads open); the custody plaintext-release gate is separate and always fail-closed

B2/C1

UD4

Retention horizons for SSA rows

RATIFIED 2026-08-16: runs/imports/facts = provenance class (archive-then-prune, ADR-058); transport rows ≤ 90 d once a provenance handoff relation exists. Delimitation: classification + bound CLOSED; row/history mechanics RESOLVED 2026-08-17 by #1479 — mutation-quiescent-at-eligibility (ADR-065 § Amendment #1479; the scoped ADR-058 amendment ships with B5). Handoff-relation engineering → E3; external DFCS schedule → #1480

E3

UD5

New SSA data arriving post-determination

RATIFIED 2026-08-16: notify (worklist item); never silent auto-re-evaluation. Worklist design (ownership, person→case fan-out, dedupe, assignment, dismissal, authz) lands with D3

D3

UD6

v1 benefit taxonomy scope

RATIFIED 2026-08-14: SSI + Title II only (additive schema)

B5

UD7

Batch file authenticity level

RATIFIED 2026-08-16: attested checksum for the v1 conversion window; signed manifest required before any recurring automated feed (transition ownership → #1433)

C1

UD8

Late-matching of unmatched import records

RATIFIED 2026-08-16: dropped from v1 (append-only design stays forward-compatible); C2 defines unmatched-record terminal behavior

C2

UD9

Cutover sign-off owner (DSA naming SSA + Gateway)

RECORDED 2026-08-16 (split): the business/legal SIGNATORY remains an unnamed, REAL Phase-P blocker (#1433); the project maintainer is technical coordinator only

P

UD10

Legal-hold semantics

RATIFIED 2026-08-16: hold flag at run/batch granularity; transitive-hold scope, set/clear authority + audit, sweeper-race semantics → E3

E3

UD11

Screening freshness horizon

RATIFIED 2026-08-14: 12 months (redetermination cadence)

B1

UD12

case_id for batch-imported facts

RATIFIED 2026-08-16: person-scoped only; case association at UD1’s assembly time (assembly authz/ambiguity/correction/multi-case semantics → C2)

C2

UD13

Precedence: documented deprivation basis vs SSA-derived

RATIFIED 2026-08-14: documented basis first; SSA-derived rows before the "" catch-all as fallback

D1

Phased unit graph

Weights: S ≤ 1 day · M 2–3 days · L ≈ 1 week · XL > 1 week. Blockers are hard edges; units within a phase without mutual edges may parallelize. Every unit: own branch/MR, full battery, GA-behavior-preserving, this Status table updated on completion.

Phase A — inert substrate (nothing here can move an SSN)

Ordering justification: the crate lands unregistered (partner-create validates adapter_kind against the live registry, so no ssa_solq row can exist); the generic-path rejection lands next (testable against a synthetic registered TypedOnly kind); registration lands last, gated on the rejection tests being green. Strictly safer than either measure alone.

  • A1 (M) crates/craig-partner-ssa-solq from the WIC template with two departures: closed categorical error enum (Display emits no upstream bytes — S2; at most the bounded numeric HTTP status), and no in-crate mock/mock feature (S10). As built, "bounded-read and discarded" is split per the plan’s own traceability: the adapter DISCARDS every upstream-controlled byte at its mapping boundary (transport bodies, reqwest text, serde decode context — canary-tested through the full send path), while the transport-level response byte-CAP lands with B4’s S13 egress controls (V9 "response byte-cap") — the transport hands adapters an already-fully-read Vec<u8>, so A1 alone cannot bound the read. Wire types are FIELD-FREE #[non_exhaustive] placeholders pending the real Gateway contract (inventing fields would fabricate a contract, the S9-class hazard; unknown fields tolerated on decode so the first real response cannot fail on unmodeled fields). The typed_only erased registration (A2’s innermost layer) is adopted in-crate with refusal + probe-forward tests. Workspace member + deps; the #492 dto-length walk gains the crate (the 3 pre-existing missing partners = #1461). Blockers: none.

  • A2 (M) DispatchClass::{Generic, TypedOnly} as a REQUIRED ErasedAdapter::dispatch_class() method (craig-exchange-contracts/src/erased.rs) — as built, the metadata rides the erased trait via the impl_erased_adapter! arms (existing two arms Generic, new typed_only arm), NOT the factory-entry tuples: registries materialize adapters at boot, so ExchangeRegistries::dispatch_class_of(kind) derives from the materialized map with zero bundle-contract changes. The typed_only arm’s erased send_value/audit_value also refuse unconditionally (ErasedAdapterError::TypedOnlyDispatch, categorical) — a 4th, innermost layer under the three checkpoints. Hard rejection (typed 422 / worker terminal-categorical typed-only-dispatch) in api/transactions.rs send pre-claim, /retry in-tx pre-restage, and send_worker dispatch pre-resolve — all keyed on the PARTNER-ROW kind via dispatch_class_of (never resolve(), whose noop:// substitution would mask the kind). Synthetic-kind unit tests pin the macro arms (incl. the categorical-refusal canary), the registry lookup (incl. the noop-masking contrast), and the shared ingress guard (422 shape/field); the WORKER checkpoint’s wiring is deliberately untestable pre-A3 (no TypedOnly kind is registrable in a real boot), so its proof — a #1195-style direct-SQL worker terminal test — rides A3 together with the live ingress-rejection lockstep extension. Blockers: none.

  • A3 (S) Registration: GeorgiaBundle factory + audit codec entries (craig-state-ga/src/lib.rs:104–129), PINNED_KINDS 11→12 + lockstep test extended to assert generic-path rejection for every TypedOnly kind (adapters/mod.rs:38–68) + the deferred A2 worker-checkpoint proof (a #1195-style direct-SQL flip to the registered TypedOnly kind must terminal-fail typed-only-dispatch, never dispatch — as built, ordering INVERTED vs t1195: stage under a generic kind first, then CAS-flip the partner row inside the worker’s poll window, because the registered kind 422s at ingress), craig-partner-audit closed enum + decode_jsonb arm, seed inventory — as built, a DELIBERATE ABSENCE with the reason committed at the inventory site: seeded partners are born active (no active flag in the seed model), so any seeded ssa_solq row would violate this unit’s own refusal; the B-phase seeds the SSA partner when the typed pipeline exists — mock-route token test (flipped to adapters-minus-ssa_solq; the first adapters↔mocks count desync, by S10 design), GA feature wiring (dep only — no mock forward; the crate has no mock feature), and the partner active=true refusal for every TypedOnly kind (keyed on dispatch_class_of, NOT the literal token, so future TypedOnly kinds inherit it; 403 FEATURE_DISABLED naming the lift; covers create (rows born active), kind-flip-while-active, and PATCH {active: true} resurrection; {adapter_kind, active: false} stage-ahead stays legal; lifted for non-prod in B4, for prod in Phase P). Blockers: A1, A2.

  • A4 (S) SsnDigest newtype per SD12 in crates/craig-cases-contracts. As built: canonical wire form is ONE string v{version}:{44-char base64} (serde try_from/into String — a stable byte form the B1 cohort hash binds to); PartialEq IS the constant-time compare over the full canonical bytes (version + digest together; .version() exists so B2 classifies a rotated-away version as its own refusal per V2); redacted Debug, no Display, categorical errors. Wire-only — NO sqlx::Type yet (B1 decides tagged-single-TEXT vs digest+version columns when its DDL lands; the one-string canonical form keeps both trivial); no ToSchema (consuming DTO fields annotate value_type = String, the Sha256Hex precedent). The three PRE-EXISTING bare-String digest wire fields in persons.rs (SsnVerificationStatus.digest, AttestSsnRequest.digest, FederalExportRow.ssn_digest) retype in #1435 (after B2, so the emit-side version-stamping seam exists once), not this unit. Blockers: none.

  • A5 (M) ADR-065 + ADR-066 (numbers free after adr-064) with UD1–UD3 drafted as options; ratified 2026-08-14 (#1434) and the ADR-051 amendment applied in the ratification commit. Blockers: none.

Phase B — SOLQ screening pipeline (B7 is the exit gate)

  • B1 (L) Screening schema in craig-exchange: ssa_screening_runs / ssa_screening_members / typed job table. DDL invariants (H11): status CHECKs, timestamp ordering, status-dependent nullability, FKs, one-active-run-per-case partial unique index, unique txn↔member. Cohort binding (H1): cohort hash over (member set, versioned digests, as-of), freshness horizon (UD11), invalidation marker. Rows are provenance-grade (H3/H14), not prunable transport. As built (#1462): the A4-deferred digest column shape is the tagged single TEXT (ssn_digest stores SsnDigest::as_str() verbatim under a mirroring grammar CHECK); the invalidation marker is ORTHOGONAL to run status (a one-way invalidated_at
    invalidation_reason pair — it can stamp a completed run, and either completion or invalidation releases the one-active slot); member.transaction_id is a logical fk (ADR-065 §D4 makes SSA transaction rows prunable transport, so provenance must not enforce-FK them), unique fleet-wide; the typed job table ships the exchange_send_jobs lease shape (claimed_at/claim_generation) day-one plus the H16 paused status, and NO payload column (H18/SD14). cohort_hash + SCREENING_FRESHNESS_MONTHS (12, a const not a knob)
    the stale-on-anniversary screening_is_fresh live in craig_cases_contracts::screening_cohort (the ADR-057 shared-crate precedent — D3 recomputes the same bytes); status enums in craig-reference. Store modules deliberately deferred to their consumers (B3 staging, B4 worker) — B1 is schema + DDL-invariant probes against the real migration file. Blocker: A4.

  • B2 (L) Cases custody extension: S2S plaintext release taking person_id
    expected_digest + purpose + run correlation; release only on exact current-digest match (S3); mismatch → typed 409, member terminally ssn_stale. Exchange-only allowlist (S6); dedicated custody-purpose gate, fail-closed for plaintext regardless of UD3 (H21); full audit; As built (#1463): POST /v1/cases/persons/{id}/ssn-release — allowlist is a handler const (NOT a ruleset row, per H21); version checked BEFORE bytes so a rotated-away blind-index version refuses as its own 409 (ssn-digest-version-rotated) distinct from ssn-digest-mismatch; the compare is SsnDigest constant-time equality against the stored digest stamped with the effective registry version (the #1435 emit-side seam); one row snapshot feeds compare AND decrypt; unverified emission per §D5’s carve-out; releases ride case.person_pii_exported (purpose = ssa_solq_screening + correlation trio), digest-class refusals stage the categorical case.person_ssn_release_refused in their own tx; the caseworker attribution rides the BODY (requested_by = the run’s stage-time actor — a B4 background release has no live actor to relay; ADR-065 §D2 as-built). Member ssn_stale terminalization is the CALLER’s move (B4) — cases returns the typed 409. actor propagation (caseworker actor for audit, service identity for authn). Blockers: A4, A5.

  • B3 (L) Request path: cases-owned per-case, BOLA-checked screening intent (caseworker actor); cases forwards to a cases-only exchange endpoint; WEB mints and retains client_request_id, cases forwards unchanged, exchange claim_first creates run + members
    transactions + typed jobs atomically (H4, reusing request_claims). New ResourceType::{ScreeningRun, BenefitFact} (craig-authz/src/types.rs:45) + GA/TX authz fixtures + boot coverage. Partner resolution zero/one/many fail-closed + active/DSA check at stage time (H15). Blockers: B1, B2, A3. As built (#1464): POST /v1/cases/cases/{id}/ssa-screening (human-only intent — deny_pure_service + per-case Action::Update BOLA; cohort assembled SERVER-side from active household members, every member’s digest version-stamped through the B2 seam; members without an SSN refuse typed naming the persons — skipping them would fabricate a complete UD1 witness) → POST /v1/exchange/ssa-screening-runs (SD5 allowlist = handler const craig-cases; the UD3 knob CRAIG_EXCHANGESSA_SCREENINGENABLED default OFF gates this one write). The staging wire DTOs live in craig_cases_contracts::screening_wire (the cohort in motion — craig-exchange-contracts stays the pristine Plan-T3.1 leaf); the caseworker attribution rides the body (requested_by/requested_by_name, the §D2 as-built posture — cases authenticates as ITSELF via its OIDC principal, the first cases→exchange client). Exchange re-derives everything derivable: ordinals from the canonical cohort sort (never wire order), the cohort hash recomputed via the ONE shared fn and refused on mismatch; the one-ACTIVE-run-per-case check runs POST-claim so replays short-circuit at the claim; H15’s DSA validity consult is the first in the codebase (status active + unexpired on the business date). Exchange refusals relay VERBATIM through cases (status + problem body, no translation). The WEB mint lands with E1’s form — B3 proved the protocol end-to-end (full-chain replay test: same client-held id → the same run through both services). Runtime staging stays operationally dark until B4 lifts the A3 activation lock; tests reach it via the documented direct-SQL partner flip, serialized under the ssa-partner-slot nextest group (the slot is deployment-global state). NO events yet (B6). Rulesets for the two new ResourceTypes are deliberately deny-all in BOTH jurisdictions until D4 defines reads (recorded in the files + pinned in L3).

  • B4 (XL) Typed SSA worker (never the generic dispatcher): claim typed job → last-responsible-moment re-check of gate + partner-active + DSA + endpoint/adapter drift (H15/H16, pause-vs-terminal table) → value-bound JIT custody call (S3) → minimal wire payload built separately from the persisted job (H18; modeled correlation field) → send under egress controls (S13). Dedicated lease budget with compile-time floor lease ≥ 3 × (custody_timeout + send_timeout) or heartbeat extension (H17); failure-mode classification (custody timeout ≠ partner timeout ≠ stale digest ≠ auth failure). Non-prod ssa_solq activation unblocks here only for the dev loopback/mock allowlist; the production block stays code-level (S4). Blockers: B3, A2. As built (1465): services/craig-exchange/src/ssa_worker.rs + the NEW craig_exchange_transport::EgressControlledTransport (S13: https-only with a literal-loopback http dev carve-out; exact-host allowlist where EMPTY denies all; DNS resolve-then-vet-then-PIN via the client resolver override — https-class hosts must not resolve into internal space; redirects surface as BadStatus, never followed; the A1-deferred 1 MiB response byte-cap on success AND error bodies, chunked-read). The worker processes claims SEQUENTIALLY (CLAIM_BATCH = 2) so the lease math is compile-provable: CLAIM_LEASE_SECS = 120 ≥ 3 × (5s custody + 30s send) AND batch × worst-case < lease, both const asserts (the H17 floor branch; heartbeat not needed at this volume). Pause classes (H16, job paused + categorical last_error, re-admitted by direct operator UPDATE until resume tooling lands): gate-disabled / partner-unavailable / dsa-not-active / egress-refused — the egress pre-check runs BEFORE the custody call so plaintext is never released for a refused send. Custody classification: digest-class 409s → member terminally ssn_stale + job ssn-stale; person 404 → person-missing; cases outage → retryable custody-unavailable within the lease (V5). Partner errors classify per closed adapter variant (partner-timeout/-connect/-transport/-status/ -malformed) and retry to the cap. The wire payload is the extended SolqScreeningRequest (ssn wire-transient + modeled correlation_id + schema_version; Debug redacts; [non_exhaustive] so C0 extends additively); on sent the member STAYS pending — the response hand-off is B5’s. The activation lift is ENDPOINT-SHAPED (noop:// or literal-loopback http(s) hosts pass refuse_typed_only_activation; anything else remains code-blocked naming Phase P) — a host-literal check, deliberately not DNS. The exchange→cases custody client is the second S2S client (CRAIG_EXCHANGE__CASES_BASE_URL); the worker registers Observed + knob-gated (WORKER_POLL_SECONDS, 0 = Disabled — the devstack posture, so in-process test workers are the only claimers; the ssa-partner-slot group also covers the worker + activation suites). The exchange lib seam grew to carry the worker/store modules for the in-process tests (the #1185 doctrine).

  • B5 (L) Results + benefit facts, per the RATIFIED #1479 design (2026-08-17 — all six forks; working record on #1479, carried AC on #1466; decision record: ADR-065 § Amendment #1479 + ADR-066 § Amendment #1479): categorical per-member outcomes with per-member ambiguous fail-closed (digest-lookup ordinals/chunking moved to C2 per D21); the SD10 fact model in casesssa_benefit_facts born insert-only, pointer-forward supersession chain, structural exactly-one-current, widened replay key with beneficiary_role in the chain identity — plus the ssa_screening_outcomes projection row (insert-only, no digests); the ONE cases-side outcome tx (POST /v1/cases/ssa-screening-outcomes: identity-hash claim → classification-only digest verify → authority refusal → fact chain with split-23505 → the guarded persons.ssn_verified CAS per UD2 with defined 0-row dispositions → projection insert); fenced aggregate run-state updates under worker claim generation (H2) — completion fires from EVERY member-terminal writer (complete_run_if_final); the pre-contract send-authority witness (SolqSendAuthority, no pre-P constructor) with the contract-pending pause as precondition #0; run invalidation writers (cohort_changed via producer-carried cohort_revision
    watermark-hardened staging with fork-3 auto-supersede, freshness_expired, and operator_cancelled via the fork-1 audited human-only cancel endpoint), one-tx sweep of the paused/pending graph, freshness-bounded for completed runs (fork 2); model-level V7 tests via internal outcome fixtures; retention: runs/members admitted mutation-quiescent (the scoped ADR-058 amendment ships with this unit), specs/floor land in E3. Blockers: B4 (done; UD2/UD6 ratified 2026-08-14; UD4 ratified 2026-08-16; #1479 design ratified 2026-08-17).

  • B6 (M) Domain events (screening requested/completed/failed, benefit recorded/superseded) — PII-safe categorical payloads only; exchange.failed for SSA carries category codes only; catalog + parser deploy ordering per #1479 D17 (parsers before producers): B6a = event catalog (ADR-003 additions: case.screening_cohort_changed AND exchange.screening_run_invalidated) + security parser arms; B6b = the producers (publish_screening_cohort_changed at the cases seam; exchange emits exchange.screening_run_invalidated). The cohort-revision bumps and the exchange inbox consumer/binding ship in B5 (consumers-before-producers); the case.screening_cohort_changed payload is MINIMAL per fork 5 (case UUID
    cohort_revision + categorical change token — no person_id, no digest). Blockers: B4, B5.

  • B7 (L) Phase-B verification battery: matrix rows V1–V5 + V7 + V9 (see Verification matrix (by invariant)). The battery adds the #1479 B5 lifecycle rows: witness unconstructibility, pause/resume/attempts-reset, invalidation races (event-before-staging 409, supersede, in_flight convergence), completion-from-every-terminal-path, the watermark invariant probe, V7 fact-chain assertions incl. successor-replay idempotency, and the retention fixpoint/PruneCountMismatch injections (the #1479 §6 matrix). Nothing downstream consumes screening results before B7 is green. Blockers: B4, B5, B6.

Phase C — SDX/BENDEX import

  • C0 (external, BLOCKED) Real fixed-width record contracts from Gateway/DHS. Interim: import endpoints refuse with a typed error naming this blocker; the persistence allowlist (SD9) finalizes only here.

  • C1 (XL) Import schema + state machine, ADR-057-exact: staging ledger (ssa_import_batches/ssa_import_records), finalize under advisory lease — 409 only for a concurrent lease holder, deterministic 200 for completed replay (H8); aborted/failed/retry/ crash-resume arms (H7); parse_error rows carry line number + bounded categorical error, never the raw line; attested_sha256 verified in memory, no blob persistence (raw files carry SSNs); cross-batch replay blocked by (source identity, record key) uniqueness + unique record→benefit provenance (H9); resource limits (bytes/rows/line/field/wall-time) + zeroize on SSN-bearing parse buffers (H10); terminal timestamps for retention (H14) — PLUS the E3 slices recorded 2026-08-22 (#1566): the ssa_import_batches/ssa_import_records archive specs on the exchange engine, BATCH-granularity UD10 holds (the run-granularity mechanism
    events generalize), and the ssa_import_stale_finalizing gauge (C2 adds ssa_unmatched_rate/ssa_ambiguous_rate with the matching semantics). Admin-only, writes gated. Structure may land pre-C0; parsing refuses until C0 closes. Blocker: B1.

  • C2 (L) Matching + reconciliation: chunked digest lookup with per-item ordinals and invalid-SSN results; ambiguity fail-closed (H5); SSN-correction supersession — re-affirm digest at fact-read time or consume correction events (H13); late-matching REMOVED per UD8 (ratified 2026-08-16: dropped from v1) — C2 instead defines unmatched-record terminal behavior (finalize vs abort vs remediable, plus retention/metrics treatment) and the UD12 assembly-time association semantics (authz, ambiguity, correction, multi-case). Blockers: C1.

  • C3 (S) Test-only fixture generation (mock-server/xtask/cfg(test)); non-authoritative provenance; production hard-refusal. Blocker: C1.

Phase D — rules + orchestration + reads

  • D1 (M) zen-engine semantics spike: committed executable fixtures proving absent-key behavior on first-hit tables with "" catch-alls (S7) — tested, never assumed. Then the deprivation-table design: SSA-derived rows with explicit order/precedence before the catch-all (S8, UD13); byte-identical baselines for all existing non-SSA inputs. May start immediately.

  • D2 (L) Ruleset rollout mechanics: DB rule_sets rows are runtime truth (H23) — promotion/import, revision handling, cache convergence, canary, evaluation version pinning, rollback, audit. Blocker: D1.

  • D3 (L) Orchestration per UD1 (ratified 2026-08-14: cases-side authoritative assembly — screening witness freshness-checked per H1 + benefit facts loaded server-side; generic evaluate callers cannot claim the witness). Carries the UD5 worklist design (ratified 2026-08-16: notify, never silent re-eval — ownership, person→case fan-out, dedupe, assignment, dismissal, authz). Blockers: B7, D1.

  • D4 (L) Read APIs + DTOs: case-scoped list/latest, indexes, stable pagination, exactly-one-of filters (H12); DTO layer per ADR-065 (never serialize store models — H24); web reads proxied through cases authorization; the 202 relay never exposes internal exchange URLs (H22). Blockers: B5, A5.

Phase E — surfaces + ops

  • E1 (M) Web UI via the cases proxy. Blocker: D4.

  • E2 (L) e2e under the caseworker-per-case identity (P7): actor relay, assignment, BOLA refusal; admin project only for import surfaces; gate-flip + partner-inactive scenarios. Blockers: E1, B7.

  • E3 (M) Ops: Operational acceptance acceptance + retention wiring (H14) for the LIVE Phase-B surfaces; horizons per UD4 (ratified 2026-08-16 — classification + the ≤90d bound are closed; the provenance handoff relation shape, FK direction, horizon clock, archive-success precondition, and hold interaction are designed HERE; the external DFCS schedule is #1480), FK deletion order, claims coordination, legal holds per UD10 (ratified 2026-08-16 — transitive-hold scope, set/clear authority + audit, sweeper-race semantics designed here). Blocker: B5. Scope split, recorded 2026-08-22 (#1566): the original C1 blocker edge is DISSOLVED — E3 ships the live-surface wiring now, and the import-side slices ride C1/C2 where the plan already places their mechanics (C1 owns "terminal timestamps for retention (H14)"): ssa_import_batches/ssa_import_records retention specs, BATCH-granularity UD10 holds, the ssa_import_stale_finalizing gauge, and ssa_unmatched_rate/ssa_ambiguous_rate (C2 matching). Deferring the ratified UD4/UD10 wiring behind a Not-started XL unit would have stranded the live tables' retention posture.

  • E4 (M) Georgia case-section export via the existing plugin runtime (P4 corrected: case_section contracts/runtime exist; only a shipped Georgia export is missing). federal-export is explicitly not touched (H19: no production caller exists; any future federal projection is a narrow per-purpose endpoint with its own contract). Blocker: D4.

Phase P — production enablement (externally blocked epic)

Exit criteria, independently verified: outbound auth implemented (consume auth_config for this path only, credential zeroization), secret-rotation procedure, real Gateway wire schemas + SOLQ code mappings, executed DSA recording both SSA and Gateway identities (P5), populated egress allowlist, cutover acceptance evidence, custody-path pen-test. Until closed, ssa_solq activation with a non-loopback endpoint is impossible in production builds. Weight: XL.

Findings traceability

Every finding of the 2026-08-14 external review maps to a disposition:

Finding Disposition

S1 generic-send bypass

A2 (3-checkpoint rejection) + A3 (lockstep asserts rejection per TypedOnly kind) + B4 (typed worker); ordering A2 strictly before A3

S2 error-path SSN leaks

A1 (categorical enum by construction) + B4/B6 (persistence/events) + V1

S3 unbound JIT

B2 (expected_digest release) + B4 (stale → terminal) + V2

S4 no production auth

Phase P epic; A3/B4 code-level activation block

S5 IV-E claim unenforced

UD1 → D3 (authoritative assembly or honest removal)

S6 confused deputy

B2/B3 (allowlists, actor propagation, BOLA, admin-only imports, new ResourceTypes + GA/TX fixtures) + V3

S7 ruleset missing-key contradiction

D1 spike precedes any edit

S8 unreachable rows

D1 row order/precedence + UD13

S9 checksum ≠ authenticity

C1 attested_sha256 relabel + audit; UD7 upgrade path

S10 proxy fabricates authority

C3 test-only fixtures + production refusal; A1 no in-crate mock

S11 "SSN-stripped" insufficient

C0-blocked allowlist; interim fail-closed refusal in C1

S12 no fact model

B5 (row-per-benefit, supersession, precedence, provenance)

S13 egress

B4 controls + V9

H1 cohort binding

B1 (hash, as-of, horizon UD11, invalidation)

H2 state machine

B1 DDL + B5 fenced aggregates + V4

H3 provenance

B1/B5/C1 FKs + replay-unique keys; E3 no-pruning-of-provenance

H4 idempotency chain

B3 (WEB mints, cases forwards, exchange atomic claim)

H5 digest-lookup semantics

C2 (ordinals/chunking); B5 (per-member ambiguous fail-closed) — split per #1479 D21

H6 late-matching

UD8 (RATIFIED 2026-08-16: dropped from v1; C2 owns unmatched-terminal behavior)

H7 import recovery

C1 state machine; parse_error categorical only

H8 finalize replay

C1 ADR-057-exact semantics

H9 cross-batch replay

C1 source identity + provenance uniqueness

H10 resource limits

C1 limits + zeroize

H11 schema invariants

B1/C1 DDL

H12 read semantics

D4 + UD12

H13 correction staleness

C2 re-affirmation/supersession

H14 retention

E3 + C1 terminal timestamps + UD4/UD10

H15 partner resolution/drift

B3 stage-time + B4 dispatch-time re-checks

H16 kill switch

B4 last-responsible-moment re-check, pause-vs-terminal table

H17 lease budget

B4 floor or heartbeat + failure classification

H18 wire/persisted separation

B4 + SD14

H19 federal-export over-reach

E4 (endpoint untouched; no production caller exists)

H20 verification lifecycle

UD2 → B5 (ratified yes: audited CAS; ADR-051 amendment applied)

H21 gate semantics

UD3 → B2 custody gate always fail-closed; C1/D4 read posture

H22 web proxy

D4/E1

H23 ruleset rollout

D2

H24 data classification

A5 (ADR-065) + D4 DTO layer

H25 domain audit events

B6

P1 registration scope

A3 full file scope

P2 graph inconsistencies

Rebuilt: A2→A3; endpoints before forwarders inside B3; B7 gates consumers; UD1 gates D3; UD6 feeds B5 schema; no confirm-nothing decisions

P3 newtype home

A4 (craig-cases-contracts, versioned)

P4 composition overstatement

E4 restated (runtime exists; ship the GA export)

P5 counterparty identity

A5/ADR-066 + Phase P DSA evidence

P6 verification breadth

Verification matrix (by invariant)

P7 e2e identity

E2 caseworker project

P8 ops acceptance

Operational acceptance

P9 honest scale

Status weights + the six-epic split

Verification matrix (by invariant)

  • V1 — no SSN value persists or leaks outside custody + wire. Canary SSN corpus (raw, hyphenated, spaced, embedded-in-JSON) through every path; assert absent from DB rows (all new tables + last_error/error_message), logs, audit sink, DLQ payloads, crash diagnostics, exchange.failed, OpenAPI examples. Non-2xx echo test (mock returns 400/500 with canary body → categorical error only). Malformed + oversized bodies → bounded read. Evil-input corpus for the batch parser (fixed corpus plus proptests).

  • V2 — value binding. Correction between staging and retry → custody 409 → ssn_stale, no send; household change → cohort invalidation; duplicate digests → deterministic per-ordinal handling; digest key-version rotation → old version refused, never silently rehashed.

  • V3 — access control. 403/BOLA matrix: non-cases caller on the screening endpoint; non-exchange caller on custody release; caseworker on unassigned case; cross-jurisdiction (GA actor / TX resource); admin-only imports; actor identity asserted in audit rows; generic send/retry with ssa_solq → typed rejection, including a pre-staged malicious job row (worker refuses).

  • V4 — state machines under concurrency. Concurrent screening requests → one run + one 202 replay; member-finalizer races; stale claim generations; lease reclaim mid-JIT and mid-send; crash-after-partner-success (no double-POST); full WEB→cases→exchange chain idempotent replay.

  • V5 — gates + partner lifecycle. Gate flip between stage and dispatch (pause-vs-terminal per H16); partner deactivation / DSA expiry mid-flight; zero and two ssa_solq rows fail closed; endpoint/adapter drift detected; cases outage classified retryable within lease.

  • V6 — imports. Partial-stage crash-resume; abort; finalize replay (200) vs concurrent lease (409); cross-batch duplicates rejected by provenance; resource-limit enforcement; ambiguity statuses; retry exhaustion; source precedence (SOLQ vs SDX vs BENDEX same person/benefit); multi-benefit persons.

  • V7 — fact model. Exactly one current fact per (person, benefit, source, beneficiary role) — structural, via the chain-root + successor unique partial indexes (#1479 D13: beneficiary_role joins the chain identity); supersession chains; termination; post-correction supersession; successor-insert replay idempotency under redelivery (the widened replay key); concurrent supersession resolving via 23505-retry; CAS/fact refusal for NonAuthoritativeStub authority and for invalidated runs. Model-level tests ship in B5 via internal outcome fixtures; the integrated worker→fact leg is deferred to contract activation (Phase P — the pre-P field-free response ⇒ outcome-unmappable is itself battery-pinned).

  • V8 — ruleset. Committed absent-key fixtures precede any edit; byte-identical non-SSA baselines; SSA-row reachability regression; promotion → canary → rollback round-trip.

  • V9 — egress. http:// refused; 301/302/307 not followed; host outside allowlist refused (incl. DNS re-resolution to a non-allowlisted IP); response byte-cap; TLS failure categorical.

Operational acceptance

All metrics PII-free (ids, counts, categories): ssa_screening_oldest_pending_seconds (watchdog > lease × N) · ssa_import_stale_finalizing gauge · ssa_unmatched_rate / ssa_ambiguous_rate · per-categorical-error counters · ssa_retry_exhaustion_total · partner latency histogram · retention lag per SSA table class · readiness degrades on enabled-but-unready (gate on with no authenticated active partner or empty egress allowlist). Parsers deploy before producers. Runbook entry per alert.

Out of scope

Outbound reporting to SSA; direct SSA/ATIN connectivity; any new IV-E evaluation trigger beyond UD1’s assembly endpoint (#1054 posture stands); composition-engine plugin framework changes (E4 uses the existing runtime); federal-export changes (H19).

Bookkeeping

Program epics: #162-A substrate · #162-B SOLQ pipeline (B7 exit gate) · #162-C import (C0-blocked enablement) · #162-D rules+reads · #162-E surfaces+ops · #162-P production (externally blocked). Children: the C0 contract chore (#1432), the Phase-P prerequisites chore (#1433), the UD1–UD13 product-decision issue (#1434), the SSA retention externals (#1480 — supersedes the earlier #1073 pointer; #1073 covers rate schedules/SG-residence/ACF-199, not records retention). ADR-065 + ADR-066 in A5; ADR-051 amendment applied per UD2. ALL 13 UDs ratified via #1434 (UD1–UD3/UD6/UD11/UD13 on 2026-08-14; UD4/UD5/UD7/UD8/UD10/UD12 + the UD9 split on 2026-08-16). Each UD gates exactly the units in the table’s Needed-by column (table-normative; the earlier "due before B5/D1 finalize" sentence here was drift). The B5 design unit #1479 is ratified, closed by this record (2026-08-17 — all six forks ratified; decision record in ADR-065/ADR-066 § Amendment #1479; carried AC on #1466); the freeze-lift record commit is tracked by #1478; resume tooling is #1469; the fork-4 digest-rotation mass-invalidation xtask is #1489 (blocked on #1466).

Edit this page · latest