Plan: Exchange partner architecture + Gateway-derived interface mocks (epic &79)
On this page
Context
canopy’s partner surfaces are stubs and fabricated-data adapters. Epic &79’s investigation produced two fused evidence bases — the source-derived interface catalog and the 7.4 INT design-document review — establishing that wire-faithful mocking is achievable for most of the estate. Reproducible from the pinned review artifact: 44 of 51 documented packages carry at least one operation with a full field-level layout, and 107 of 168 operations enumerate their reason/disposition code values.
ADR-045 records the architecture this plan implements: three layers (canonical contracts / grant-checked aggregation + routing / per-partner wire connectors) with canopy-exchange as a blind broker — recipient public keys only, every durable or returned evidence object a signed, recipient-sealed HPKE envelope it cannot decrypt.
Outcome: partner integrations that are real code against real formats, tested
end-to-end against a devstack simulator, so a live deployment is credentials
endpoints + the transport-hardening gate — never a re-modeling.
Evidence provenance (pinned)
Connector children derive their wire facts from these artifacts. The
vendor-derived inputs are not in this repository; they are archived
locally and hash-verified by the generator at run time (fail-closed;
--allow-unpinned to regenerate from different inputs).
| Artifact | sha256 |
|---|---|
Source sweep round 1 ( |
|
Source sweep round 2 ( |
|
7.4 INT design-doc review ( |
|
Catalog generator ( |
regenerate: see the header comment; census self-check must exit 0 |
Gateway |
commit |
Gateway |
commit |
Divergence adjudication. Design docs win on semantics and code values; Gateway source wins on wire bytes and names; where both are internally inconsistent, the connector implements the source XSD/BeanIO literal and the simulator asserts it. Record the adjudication in the connector’s module docs.
Sanitization rule for fixtures and code tables. Interface FACTS only — field names, layouts, code values, cadences, ack semantics. Never: credentials, concrete hostnames/IPs, MFT or filesystem paths, personal names/emails/phone numbers, or the identity of secret-bearing vendor files. Fixtures use injected synthetic namespaces and endpoints; each checked-in code table records its provenance (package + document) in a header comment. Auth is modelled by shape (OFX signon block, WS-Security token, hub token) with fake devstack-only credentials.
Design
See ADR-045 for
the normative architecture: the three layers, the envelope protocol
(HPKE seal + Ed25519 sign, authenticated header), the two independent
registries (grants — legal; keys — crypto, never authorizing), the three
exchange functions (live query facade, batch relay, origin-durable command
routing), program-local matching via the persons identity projection
match tokens, the opt-in per-program response cache for metered sources, the
operational-only bus events, and the transit-capability compliance gate.
Non-goals recorded here so children do not re-litigate them: signed/versioned deployment-config manifests (compiled legal maxima carry the invariant); SFTP/SSH transfer-auth fidelity in the simulator (the shared volume tests parser and protocol fidelity only — a documented limit); performance and backpressure suites before real connectivity exists.
Scope by phase
-
Phase 0 (done) — land the interface catalog + generator + publishability gate.
-
Phase 1 (this plan’s MR) — ADR-045, this plan, GitLab restructure.
-
Phase 2 — foundations — envelope protocol, compliance transit gate, L1 contracts, L2 registry + live-query facade, persons identity projection, batch-relay core, recipient-ingest kit, scheduler, simulator scaffold, command-routing core.
-
Phase 3 — SNAP-first dialects and cutovers — GDOL, SDX, BENDEX, SVES/SOLQ, SAVE, then the SNAP cutover and the verification rip-out.
-
Phase 4 — remaining estate — ingress layer, eDRS, NDNH/New Hire, Work Number, NAC/PARIS, EBT commands + EBTAS dialect, incarceration, dedup policy, catalog enrichment, observability baseline.
Children
Each child is one MR. Acceptance criteria below are the issue bodies' source;
every child additionally satisfies the standing bar: SPDX headers, typed
errors, no unwrap/expect/panic, quality budgets, cargo xtask validate,
Antora doc updates in the implementing MR, and a CHANGELOG == Unreleased
entry.
Universal criteria for connector/dialect children
Every dialect child (11–22, 24, 25) satisfies all of:
-
Simulator personality implementing the partner’s documented wire dialect, selectable by
source_id, serving deterministic fixtures keyed to seed personas. -
Exchange L3 connector: parser + builder, mapped to L1 kinds, registered with compiled legal maxima (classification, authority, maximum program set) and capabilities.
-
Proptests are mandatory on every parser/builder (round-trip, malformed input, boundary widths, code-value coverage).
-
Golden fixtures are source-derived bytes committed independently of the connector/simulator code; the connector must not be the fixture’s author.
-
Cross-process integration test: connector ↔ simulator over the real transport (HTTP or file volume), asserting field-level fidelity and the documented code values.
-
Negative paths: malformed payload, auth failure, timeout, partial file, unknown code value — each asserted, none silently swallowed.
-
Hostile-input controls active: XXE disabled, size/record caps, filename path-traversal guards, checksum/control-total validation.
-
Divergence adjudication recorded in module docs; sanitization rule honoured for all fixtures and tables.
Child acceptance criteria
| # | Child and acceptance criteria |
|---|---|
1 |
Envelope protocol + key registry. New module in |
2 |
Compliance transit-capability gate. Extend
|
3 |
L1 contracts crate ( |
4 |
L2 grant registry + live-query facade. Grant registry keyed by source ×
jurisdiction × recipient service × benefit program × purpose × authority;
compiled legal maxima per connector with boot-time validation (config may
narrow, never broaden). Live-query facade: ADR-019 caller identity mapped to
program/purpose server-side, grant check, capability dispatch, in-memory
merge, one signed sealed envelope response. Blind |
5 |
persons identity projection + match tokens. Audited, service-scoped internal endpoint exposing full SSN/name/DOB for authorized evidence purposes (closes #1496); match-token facility (keyed HMAC over normalized identity) so program services can match batch records locally. Exact normalization rules specified and tested. Every access audited with actor, purpose, elements. Tests: unauthorized caller denied; audit row per access; token stability across normalization-equivalent inputs; no token collision on the seeded corpus. |
6 |
Batch-relay core. Parse → seal-per-entitled-recipient → enqueue →
delete-file pipeline; delivery-scope enforcement ( |
7 |
Recipient-ingest kit. Shared crate: verify signature → decrypt → match via tokens → persist → audit → acknowledge, all in one transaction; wrong-recipient envelope rejected; unknown key id rejected. Adopted by canopy-snap first. Match outcomes defined and tested: zero / one / multiple / ambiguous; uncertain matches never drive adverse action; nonmatch leaves no residue in rows, logs, or events (but IS audited as an access). |
8 |
Scheduler. UTC cron per source; DB lease with fencing; overlap policy skip + alert; audited manual trigger (created → pending/deletable → processed) mirroring the documented Gateway capability; event-driven triggers from canopy domain events. Tests: two schedulers, one runner; DST boundary correctness (UTC); missed-window behavior explicit; manual trigger authorization + reason recorded. |
9 |
Partner-simulator scaffold. |
10 |
Command-routing core. Origin-durable command contract (origin service
owns the record and resubmits; exchange transforms per attempt in memory);
UUIDv7 command id, idempotency key, actor; state machine submitted →
accepted | rejected | unknown → confirmed; ack correlation; reconciliation
surface for |
11 |
GDOL wage/UBI dialect + connector — SOAP (the documented |
12 |
SSA SDX dialect + connector — 338-field fixed-width, three cadences (daily incremental / monthly recon / annual COLA), trailer reconciliation counts computed, the documented code tables checked in; snap + medicaid ingest adoption; universal criteria. |
13 |
SSA BENDEX dialect + connector + ingest adoption; universal criteria. |
14 |
SVES/SOLQ dialect + connector. Compiled program set
{tanf, snap, medicaid, chip} per the tenancy matrix. Consumption relocates
program-side: canopy-medicaid queries exchange during its own determination
and snapshots the result per ADR-028; canopy-eligibility’s pre-dispatch
fetch, |
15 |
SAVE workflow contract + dialect + connector, including the named consumer migration that makes verification’s SAVE route deletable in child 17; universal criteria. |
16 |
SNAP cutover. canopy-snap consumes exchange for live {GDOL,
SOLQ-under-SNAP-purpose} plus locally stored batch-fed {SDX, BENDEX} — the
batch sources have no live per-person index by construction, so the cutover
waits for children 12 and 13. |
17 |
Verification rip-out. Delete internal ievs/save/ssa routes, the shared
IEVS DTOs and |
18 |
Partner ingress layer. Authenticated inbound endpoints for Gateway-as-server surfaces and vendor callbacks; replay protection; correlation to originating runs/commands; durable processing. Simulator drives the callbacks. Universal criteria where a dialect is involved. |
19 |
FNS eDRS workflow + dialect + connector — stateful multi-operation workflow (query, add, modify, delete disqualification); universal criteria. |
20 |
NDNH + GA New Hire dialects + connectors; universal criteria. |
21 |
Work Number dual personality — the OFX-era flavor and the REST/JSON successor as two personalities under one partner; universal criteria. |
22 |
NAC + PARIS dialects + connectors; universal criteria. |
23 |
EBT commands. Typed create-account / issue / expunge on the
command-routing core, balance as a query; canopy-enrollment adopts them and
keeps the issuance ledger (settlement, expungement policy). Replay-stable
idempotency preserved from the current |
24 |
EBTAS file dialect — issuance file out, drawdown file in as a source-derived reconciliation signal (per the design docs, not an invented vendor ack), address sync; needs the ingress layer; universal criteria. |
25 |
Incarceration kind — GA DOC + SVES prisoner-match dialects and connectors; universal criteria. |
26 |
Dedup/conflict policy across same-kind sources. Blocks enabling any additional same-kind source beyond the status-quo set. Defines precedence, conflict surfacing to workers, and what a determination may consume. |
27 |
Catalog enrichment + coverage-gap completion. Fuse the 7.4 INT design-review findings into the catalog (documented layouts, code tables, divergence notes); close the coverage gaps the page’s own section enumerates (JAX-RS inbound registry, SMS and SMTP channels, the worker-portal QAS ProWeb second deployment, IQ/CV outbound stubs) or record each as explicit, owned debt. |
28 |
Observability + runbook baseline. Per-source outcome and latency
metrics carrying no person or delivery identifiers; oldest-undelivered and
receipt-age gauges; scheduler lease health; config/key-change events;
simulator-activation signal; per-source kill switch (registry |
29 |
Real-connectivity hardening (T4, partner-blocked). mTLS, endpoint allowlists + SSRF controls, PGP-at-rest, SSH host-key and credential rotation, vendor quota/cost handling, HA scheduler. Real restricted connectors are compile-gated on this feature set — enabling one without it is a boot error. |
Dependency spine (encoded as GitLab blocked-by links at filing): children 1, 2, 3 → 4 → {5, 6, 7, 8, 9, 10} → dialect children → cutovers 16, 17. Child 28 blocks 16. Child 26 blocks enabling any second same-kind source. Children 23 and 24 need 10 and 18. Child 27 follows the catalog MR. Per-cutover deploy order: exchange + keys first, recipients enroll, then the caller flips (ADR-016 expand/contract for wire-shape changes).
Status
| Step | Description | Status |
|---|---|---|
0 |
Catalog page + generator + publishability gate landed (MR !1187, merge |
Done (2026-08-22) — merged |
1 |
ADR-045 + this plan + nav + CHANGELOG (MR !1188, merge |
Done (2026-08-22) — see epic &79 |
2 |
Foundations — children 1–10 |
Not started |
3 |
SNAP-first dialects + cutovers — children 11–17 |
Not started |
4 |
Remaining estate — children 18–28 |
Not started |
5 |
Real-connectivity hardening — child 29 |
Blocked (partner connectivity does not exist; T4) |
Verification
-
Phase 1 (this MR):
cargo xtask docs plan-lint;cargo xtask check-docs; Antora build in the CI-pinned container (antora/antora:3.1.14with the.gitlab-ci.ymlextension pins) with no new diagnostics for the added pages; full pre-push battery. The tenancy matrix is deliberately untouched here — it changes in child 2, with the enforcement. -
Per child: the criteria above plus the universal bar;
cargo xtask validate; for API-bearing children,cargo xtask api-docsdrift-clean against a live service. -
GitLab state: children exist with the acceptance criteria above as their issue bodies, weights, milestone, labels, and real blocked-by links; epic &79’s task list carries actual IIDs; #1527 closed with both SHAs and a per-criterion checklist.