Repository navigation
feat(fabricate): Phase 1 money-plane schema + atomic wallet/authz primitives - #308
Merged
Merged
Conversation
…mitives Lands the durable, revocable money primitives the adversarial review flagged as must-fix — schema only, NOTHING spends yet (place_order / authorize_spend / Stripe land later, test-mode + flag-gated). Migration 027: - wallets (cached mirror) · wallet_ledger (append-only source of truth, per-user idempotency key) · spend_authorizations (DB-backed, full lifecycle + kill switch) · processed_events (durable webhook idempotency). Orders gains authorization_id / idempotency_key / stripe_payment_intent_id + a one-order- per-quote unique index. - debit_wallet(): one atomic, balance-floored txn that is advisory-lock serialized on (user, key) so concurrent same-key retries replay cleanly (no unhandled unique_violation), enforces EVERY authz field (max amount, per-authz daily cap, process/fab allowlist, doc_hash, quote binding, expiry, ownership), and consumes one_time authorizations (standing budgets persist). - credit_wallet() (top-ups/refunds, refund requires order), revoke_user_authorizations() (kill switch), fn_wallet_drift() (reconciliation). - Defense in depth: assert_money_caller() rejects authenticated/anon callers; wallets+ledger grant SELECT-only to service_role so the SECURITY DEFINER RPCs are the sole writers; per-call $1M ceiling. Addresses every consensus finding from the 5-lens review (idempotency race, cross-user key, dead authz fields, missing ownership check, re-runnable DDL, no reconciliation). Validated against ephemeral Postgres 16 (scripts/money-plane/validate.sh): applies twice cleanly + full behavior matrix passes (idempotent replay, all authz-failure modes, insufficient funds, per-authz daily cap, allowlists, doc-hash binding, refund rules, zero drift, service-role guard). NOT applied to prod — money infra holds until the full Phase-1 slice is built, reviewed, and flag-gated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
The first slice of the post-quote money plane — durable, revocable primitives only. Nothing spends yet:
place_order/authorize_spend/ Stripe land in the next slice (test-mode + flag-gated). This migration encodes the three guardrails the adversarial review flagged as non-negotiable.Migration 027
wallets(cached balance mirror) ·wallet_ledger(append-only source of truth; per-user idempotency key) ·spend_authorizations(DB-backed, full lifecyclepending_human → authorized → consumed/revoked/expired+ kill switch) ·processed_events(durable webhook idempotency — replaces the in-memory Map).ordersgainsauthorization_id/idempotency_key/stripe_payment_intent_id+ a one-order-per-quote unique index.debit_wallet()— one atomic, balance-floored txn, advisory-lock serialized on (user, key) so concurrent same-key retries replay cleanly instead of raisingunique_violation. Enforces every authz field: max amount, per-authz daily cap, process/fab allowlist,doc_hashbinding, quote binding, expiry, ownership. Consumesone_timeauthzs; standing budgets persist.credit_wallet()(top-ups/refunds; refund requires an order),revoke_user_authorizations()(kill switch),fn_wallet_drift()(reconciliation: cached vssum(ledger)).assert_money_caller()rejectsauthenticated/anoncallers;wallets+wallet_ledgergrant SELECT-only toservice_roleso the SECURITY DEFINER RPCs are the sole writers; a per-call $1M ceiling.Adversarial review → fixes
A 5-lens review (double-spend, replay, authz-bypass, balance-integrity, Postgres-correctness) found the core sound but flagged: concurrent-same-key
unique_violation, globally-unique key + cross-user replay, dead authz fields (daily_cap/allowlists/doc_hash), missingo.user_idcheck, non-re-runnable DDL, no reconciliation. All addressed.Validation
scripts/money-plane/validate.shspins an ephemeral Postgres 16, applies 027 twice (re-runnability), and runs the behavior matrix:→
ALL LOGIC TESTS PASSED·SERVICE-ROLE GUARD PASSED.Deploy
Not applied to prod. Unlike 024–026, this is money infra — it stays unapplied until the full Phase-1 slice (
authorize_spend,place_order, Stripe test-mode) is built, reviewed, and flag-gated. No changelog entry (no user-facing behavior yet).🤖 Generated with Claude Code