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).