Architecture
On this page
The full system-architecture narrative — service topology, database topology
(default vs --shared-db), the event bus / outbox, Typst document generation,
shared crates, and the ADR rationale — lives in the
Developer Guide. The canonical per-service
ports/databases catalog is the Service Catalog; shared crates
are in Shared Crates Reference. This page is a bounded
architecture overview — it must not re-accrete the full catalog.
Canopy is a multi-service Rust workspace implementing an integrated eligibility
system for Georgia DHS (SNAP, TANF, Medicaid/CHIP, CAPS, WIC). Program services
are legally isolated (each owns its PostgreSQL database per
ADR-001) to satisfy federal
data tenancy; infrastructure services share one PostgreSQL instance. All services
communicate over the RabbitMQ topic exchange canopy.events. ADRs live at
docs/modules/ROOT/pages/adrs/adr-{NNN}-*.adoc.
ADR index (number → topic)
-
ADR-001 — Program service isolation (own PostgreSQL DB per program; no cross-program DB access; Amendment 1 = sanctioned bulk-read contracts — keyset universe reads +
:batchGet+ projection + async report-run job model) -
ADR-002 — Black-box determination contract (program services return signed JWS, never raw data; Amendment 1 = async/bulk determination variant — queued
determination.requested, unchanged JWS in the system-initiated path, stable idempotency keys + driver-owned checkpoints, one-pending-slot deferral, + reuse of the signedprevious_determination_id(ADR-028 §57) as the initial-vs-re-determination signal) -
ADR-003 — Ruleset-as-data (eligibility logic in versioned JDM files, evaluated by canopy-rules)
-
ADR-004 — Legally-scoped data tenancy (FTI / IEVS / SSA SOLQ-BINDEX / FDSH isolated to authorized services; Amendment 1 =
canopy-reportingauthorized as a restricted-data consumer for the person-level T-MSIS PHI extract (CMS-416 held de-identified), minimum-necessary — its own Pub 1075 §4 audit log; Amendment 2 = tamper-evidence mechanism re-homed to ADR-041, retention corrected to 7 years (Pub 1075 AU-11), A6 preserved, A7 chain-v2 attachment withdrawn) -
ADR-005 — Modular deployment profiles (a Docker Compose profile picks the program subset)
-
ADR-006 — Jurisdiction-agnostic rulesets (federal params + per-jurisdiction rules +
jurisdiction.toml) -
ADR-007 — CLI/API/UI parity (every API operation has a
canopyCLI subcommand) -
ADR-008 — Applicant portal architecture (Dioxus, reference-number auth)
-
ADR-009 / ADR-026 — PostgreSQL-backed sessions with Redis LRU cache for BFFs; canopy-portal is Postgres-free with Redis-primary sessions
-
ADR-010 / ADR-029 — Typst document generation (
canopy-typstwrapstypst-as-lib); general signed-document renderer (POST /v1/documents/render) -
ADR-011 — Policy-to-rules traceability (
citations.toml→ PAMMS) -
ADR-012 / ADR-017 — Layered YAML config + SOPS/age-encrypted secrets at rest
-
ADR-014 — FTI audit hash-chain integrity (amends ADR-004). Superseded by ADR-041 (Amendment 12): the FTI hash chain + the chain-v2 line are being retired (gated behind #1304) for a general logging + redaction facility. Historical chain-v2 lineage: Amendment 5 = chain-v2 (hash-bound sequence + durable head, sharded, externally notarized-anchored); Am 6 = the substrate byte-level contract; Am 7 = the append-transport bindings; Am 8 = the verifier bindings; Am 9 = the verifier revisions (unified
/v1/security/chain/*namespace, family lease, durable verify jobs, JSON number fence); Am 10 = the external anchor authority (enumerable transparency frontier); Am 11 = the anchor reframed on a WORM capability tier (supersedes Am 10). All of the above are superseded by ADR-041; the surviving non-chain C7/C8 obligations are re-ratified there. -
ADR-016 — Forward-only schema migrations
-
ADR-018 — Persistent outbox (
event_outboxtable +OutboxDrainer) -
ADR-019 — Service identity and on-behalf-of (OIDC at service boundaries)
-
ADR-021 / ADR-022 / ADR-024 — Worker-portal composability (plugin model, override storage, user-delta schema)
-
ADR-025 — Cross-service referential integrity (
cargo xtask seed-verifyauditor pattern) -
ADR-027 / ADR-028 — Worker fact-authoring + determination input snapshot (epic &56)
-
ADR-030 — Code-quality gating (strict lint posture +
quality-budgetsratchet) -
ADR-031 / ADR-032 — Policy coverage assurance + two-tier scenario corpus
-
ADR-033 / ADR-034 / ADR-035 — Generative seed harness; per-program / per-subject determination context-mapping
-
ADR-037 — Signing-key-aware service-token acquisition (sender self-heals on a rotated/deleted IdP token-signing key; amends ADR-019)
-
ADR-038 — Concurrency-safe, recoverable applicant finalization (persons transactional receipt + draft-row-locking saga + crypto-shred compensation + held events; amends ADR-026 §5/§6)
-
ADR-039 — Single-source outbox schema (canonical in canopy-mq + generator + parity gate) + first-class event-hold (amends ADR-018)
-
ADR-040 — Build-once, gate-complete artifact promotion (immutable SHA staging refs + digest retag behind a full test-stage barrier; guarded
latest; per-image SBOMs;ci-config-lintstatic gate) -
ADR-041 — Configurable structured logging + jurisdiction-owned field redaction (supersedes ADR-014’s hash chain; amends ADR-004). Per-field redaction in every service, fully jurisdiction-overridable (canopy = mechanism, deployment = policy); an unfilterable versioned audit-export channel carrying a complete-row digest + policy version; external tamper-evidence + retention delegated to the deployment logging facility
-
ADR-042 — Upload scan quarantine (clamd backend, async promotion). Applicant uploads land durable at
scan_status='pending'; a fenced promotion worker settles verdicts bound to content identity (a verdict follows the bytes, not the row); serving and review gate on viewability — clean, or scan-skipped with an audited supervisor override -
ADR-043 — OIDC program amendments to the immutable ADR-023 (citizen-path credential, exchange semantics, frozen rejection contract). The citizen path runs on a dedicated narrow IdP service account (no citizen token exists to exchange); GA exchange semantics fixed (
subpreserved,azpvisible,actunused); 401/403 responses frozen; a denied user-context exchange never downgrades to the broad service token -
ADR-044 — Worker program scope is a required IdP claim (Pub 1075 AC-6). A token with no usable
primary_programsis refused at admission — role-agnostic, no canopy-side override (the override is the claim mapper); the claim parses once into a structurally non-emptyWorkerProgramScope, so the fail-open see-all branches have nothing left to branch on. Explicitly a BFF control: it does not supersede the upstream actor-claim work
The complete, current ADR list is in the site navigation under Architecture & Design.
Service topology at a glance
-
Infrastructure services share the
postgresinstance (port 5432): rules, persons, applications, eligibility, verification, enrollment, renewals, notices, exchange, appeals, reporting, security, plus thecanopy_webBFF database. canopy-portal is Postgres-free per ADR-026. -
Program services have isolated PostgreSQL databases (ADR-001): snap (5433), tanf (5434), medicaid (5435), caps (5436), wic (5437).
-
BFFs: canopy-web (worker portal) and canopy-portal (applicant portal — a Dioxus app per ADR-008, built via
dx). canopy-web’s container-internal listen port is fixed; its host port is OS-ephemeral — discover it withcargo xtask dev status.