Skip to main content

D84 — Typed finance-provider identity: the Foundation FinanceProvider enum + per-table provider CHECK constraints (RESOLVED, extends D40/D55/D60)

Architecture decision record, relocated verbatim from the retired single-file docs/tenancy/ decision log. Status, thematic clusters, and how to record a new ADR: the decision log index.

The provider column on the mirror tables was a loose string whose only runtime value is 'conduit'. It is now typed end-to-end: a string-backed Modules\Foundation\ValueObjects\ FinanceProvider enum (Conduit = 'conduit') is the single source of truth — ConduitRailsProvider::PROVIDER is wired to FinanceProvider::Conduit->value, the shared IsProviderMirror::mirrorCasts() enum-casts provider on every concrete mirror model (accounts/payments/funding/onboarding), and DB CHECK (provider IN (...)) constraints (built from FinanceProvider::values() in the migration) backstop raw writes.

Why Foundation (not rails). Both sides of the seam must reference the type without a boundary inversion: rails may depend only on Foundation, and provider-mirror (which owns the casting concern) may depend only on Foundation + Tenancy — so neither may host it for the other. Foundation already owns the provider-adjacent vocabulary (ExternalRef's (provider, id) tuple, Rail, Chain), and the enum joins those as ValueObjects\FinanceProvider.

CHECK scope (deliberate). The enum CAST covers all eleven IsProviderMirror models app-wide (accounts ×4, payments ×4, funding ×1, onboarding ×2; order_transactions and the central provider_customers map carry the provider vocabulary but not the trait — they get the CHECK in the security-hardening §4 follow-up, not the cast); the CHECK constraints land on the four accounts tables (virtual_accounts/wallets/wallet_signers/ signing_quorums, <table>_provider_check) — the module this change owns. The other mirror modules' tables get per-module follow-up migrations (each module owns its schema; tracked in docs/tracking/security-hardening.md). Deliberately NOT constrained: command_dedup_records and vendor_request_logs (their provider is the open IdempotencyKey/vendor-log namespace, not the closed mirror vocabulary) and mirror_drift (framework-owned; records whatever the mirror carried). Adding a provider = add the enum case + register the connector + a companion migration that drops/recreates the CHECKs (documented on the enum).


← Decision log index