Skip to content

Latest commit

 

History

History
80 lines (60 loc) · 3.4 KB

CUSTODY_ACCOUNTING.md

File metadata and controls

80 lines (60 loc) · 3.4 KB

Custody accounting

This document describes the custody accounting behavior that exists in the application. Code, migrations, and tests are authoritative when this summary differs from the implementation.

Current boundary

The repository contains an append-only PostgreSQL journal with balanced postings and rebuildable account projections. The journal is not a general ledger for every payment flow: the only connected business path is the optional shadow entry for a new internal_balance_only merchant credit after the one-way Phase 2A cutover marker has been activated.

Custody invoice routing and payout request/execution flows are not implemented. Their gates must remain disabled.

Data model and invariants

  • custody_accounts identifies asset/network accounts and their normal side.
  • custody_journal_entries and custody_journal_postings form immutable balanced events.
  • custody_account_balances is a rebuildable projection.
  • custody_journal_source_links binds a shadow journal entry to its exact merchant_settlement_entries source.
  • custody_phase2a_cutovers contains the immutable one-way cutover marker.

PostgreSQL constraints and deferred triggers enforce balanced positive postings, matching asset/network/scale, source coverage, exact shadow posting shape, restrictive deletion, and append-only history. Corrections are new journal entries; posted financial history is not edited.

Gate behavior

All custody and payout gates default to false.

Before a cutover marker exists, positive internal credits follow the existing legacy balance and settlement-ledger path when the shadow gate is off. Enabling the shadow gate before the marker fails closed. Once the marker exists, positive internal credits require exactly this configuration:

CUSTODY_ACCOUNTING_ENABLED=true
CUSTODY_JOURNAL_WRITES_ENABLED=true
CUSTODY_PHASE2A_SHADOW_INTERNAL_CREDITS_ENABLED=true
CUSTODY_INVOICE_ROUTING_ENABLED=false
PAYOUT_REQUESTS_ENABLED=false
PAYOUT_AUTOMATIC_REQUESTS_ENABLED=false
PAYOUT_EXECUTION_ENABLED=false

Any later mismatch fails closed. A successful post-cutover internal credit writes the settlement source, two-posting shadow journal entry, source link, projections, merchant balance, invoice state, and webhook outbox in one database transaction.

Read-only checks

./vendor/bin/sail artisan custody:verify-projections
./vendor/bin/sail artisan custody:verify-phase2a
./vendor/bin/sail artisan custody:verify-phase2a --json
./vendor/bin/sail artisan custody:reconcile-legacy-balances

Projection rebuild is a dry run unless --write is explicitly supplied:

./vendor/bin/sail artisan custody:rebuild-projections

The backfill command is a pre-cutover compatibility tool. Preview it with ./vendor/bin/sail artisan settlements:backfill-ledger --dry-run; write mode is prohibited after cutover.

Cutover safety

./vendor/bin/sail artisan custody:activate-phase2a --reference=<stable-reference> is one-way and succeeds only for the exact gate configuration and a completely empty, clean baseline. It does not reset a database. Database resets, gate changes, and activation require explicit operator intent and verified local/testing database identity. Never run them against production, staging, an external database, or data with financial obligations.

The implementation lives in app/Services/Custody, app/Services/Settlement, the custody migrations, and tests/Feature/Custody.