Skip to main content

ADR 0012: Use one live adapter and asynchronous provider writes

Context

The pinned Perflo contracts expose enough operations to implement a constrained neobank integration through customer device sessions. They do not expose an executable fiat quote, a fiat-account ledger, upstream idempotency for every write, or signed webhooks. Earlier decisions treated those gaps as production startup gates and split the provider path into development, pilot, and future production adapters. That staging taxonomy no longer matches the integration. The wrapper can adapt the reviewed endpoints without claiming capabilities they do not provide. The remaining Perflo capability requests describe stronger upstream contracts, but their acceptance does not control process startup.

Decision

Use only fake and live adapter modes. live uses each customer’s Vault-encrypted Perflo device session and pins the exact app, Perfolio, fiat, cards, and agent origins reviewed with this release. Production requires live, PostgreSQL, Redis, Vault over Transport Layer Security, Auth0, authenticated metrics, HTTPS application origins, and disabled debug authentication. Represent every provider mutation as an operation processed from the transactional outbox. Persist the operation and idempotency record before provider submission, and commit submitting before any money or authority write. A transport-uncertain write becomes indeterminate and is never submitted again. Scheduled reconciliation can use only provider reads with exact evidence. Use requires_action operations for one-off transfers and mandate creation. The browser signs a recipient grant on the trusted Perflo application origin. One-off grants bind one exact United States dollar debit, one payment, and one day. Mandate grants bind the beneficiary destination, United States dollar caps, count, and floored whole-day expiry. Local atomic reservations enforce callers, rules, and rolling limits. Treat /payout/quote as an estimate. Convert local source currency through the public fiat rate, show the exact United States dollar Perflo cash debit, derive the destination currency from the beneficiary, and limit wrapper freshness to five minutes. Do not describe the quote as locked or executable. Project customer activity from agent transactions and Perfolio transaction history. Filter it to classifiable cash and payment rows, normalize cash to United States dollars, deduplicate by transaction identifier and hash, and omit token, address, chain, network, and raw provider fields. Continue to request a cursor-addressable fiat ledger through PFR-005. Expose only rows evidenced as fiat deposit accounts — an ISO 4217 currency code and a non-empty bank account identifier — refusing rows that declare a crypto type. Project upstream account IDs to opaque hashes and omit raw routing codes. Store beneficiary destinations and approval context only as Vault ciphertext bound to the exact customer and resource row; bind session credentials to the provider-connection generation. Public cards expose lifecycle state, last four digits, aggregate United States dollar balance, transactions, and a hosted reveal URL. The wrapper never calls the raw card credentials endpoint. Claim every accepted grant, transaction, beneficiary, and card identifier once per provider-binding generation using a non-reversible fingerprint. A collision cannot complete the later operation; it remains indeterminate with its exact correlation reference preserved for operator investigation. Replace the legacy migration chain with one initial schema. Deployment against this release requires an empty database; startup does not erase or translate existing data. This decision supersedes the production-gate portions of ADRs 0004, 0010, and 0011. Their identity, credential encryption, exact-origin, single-origin edge, and persistent Vault decisions remain in force.

Consequences

Live behavior depends on strict normalization of undocumented provider response bodies. Missing amounts, identifiers, timestamps, or successful states fail closed. A controlled canary must capture sanitized fixtures before production traffic. Customers receive asynchronous accepted responses for beneficiary, transfer, mandate, card, and suspension writes. The frontend restores active operations after reload, opens only trusted approval URLs without an opener, polls customer-owned operations, and refreshes resources after success. Perflo capability requests remain the path to customer-bound one-time Know Your Customer sessions, provider-driven fiat provisioning, a true fiat ledger, executable quotes, upstream idempotency, signed webhooks, upstream caller and rolling-window enforcement, and fully idempotent card lifecycle operations.

Rejected alternatives

  • Keep separate development, pilot, and production adapter classes: they duplicate one provider contract and allow configuration labels to drift from behavior
  • Treat every Perflo capability request as a startup gate: the wrapper can adapt current endpoints while describing the residual limitations accurately
  • Execute the payout quote directly: the pinned contract has no execute-by-quote operation
  • Attribute customer-wide history to a fiat account: neither transaction feed proves that account-level relationship
  • Retry uncertain provider writes: a retry can duplicate money movement or authority
  • Store recipient destinations or approval session identifiers in request JSON: those values belong in Vault ciphertext
  • Preserve the old migration chain: this release explicitly requires database reinitialization