Skip to content

0016 - Postgres-backed wallet escrows

  • Status: Accepted
  • Date: 2026-05-13
  • Deciders: nexus core team
  • Tags: billing, ptb, postgres, gateway

PTB (Pass-Through Billing) requires nexus to reserve spend against an organization wallet before making upstream provider calls with nexus-held credentials. The reservation must settle reliably: successful requests commit the escrow, failed requests cancel it, and stale reserved rows can be reconciled without leaking reserved balance.

  • Wallet balances must be durable across gateway restarts.
  • Reservation, commit, and cancel operations must preserve financial consistency under concurrent requests.
  • Gateway hot-path code needs a small service boundary so tests can use an in-memory ledger.
  • Reconciliation must be able to find aged reserved rows efficiently.
  • Postgres tables with row-level locking — Store balances and escrows in the control-plane database and use transactional updates for consistency.
  • In-memory gateway ledger — Simple for tests, but loses state on restart and cannot support real billing.
  • External durable object / queue-first reservation — Adds infrastructure before the repository needs distributed wallet ownership.

Chosen option: Postgres tables with row-level locking.

Running balances are stored in workspace_wallets with balance_usd and reserved_usd. Each reservation lifecycle is stored in workspace_wallet_escrows with status values reserved, committed, and cancelled. Wallet transactions record reserve, commit, cancel, and grant entries so operators can inspect balance changes by request and actor.

The gateway depends on the EscrowService trait. StoreEscrow adapts that trait to the control-plane store; tests exercise the same trait boundary through MemoryStore.

  • The gateway can reserve before PTB upstream calls and commit or cancel when the attempt resolves.
  • The ingest service can observe committed escrows by id from gateway audit / response metadata.
  • Tests can exercise financial consistency without requiring Postgres.