The adjudication layer
Arus records money, Alur moves captures, Banding adjudicates disputed captures, and Selaras reconciles the final settlement trail.
Source: disputes/docs/product-requirements/2026-06-12-v01-disputes-v1-prd.mdChapter 08 - Dispute lifecycle engine
v1 shipped - publication gated
A Go/PostgreSQL chargeback lifecycle engine that owns every dispute deadline in a durable due_at row and books terminal money impact into Arus exactly once.
Source: disputes/backend origin/main b6e50ac; PR #1 fixup 3259d59; F1-F3 verified in Phase-0 evidence and fresh local race runsArus records money, Alur moves captures, Banding adjudicates disputed captures, and Selaras reconciles the final settlement trail.
Source: disputes/docs/product-requirements/2026-06-12-v01-disputes-v1-prd.mdIn card disputes, the two expensive failures are missing a deadline, which can become an automatic loss, and booking the financial impact wrong or twice.
Source: disputes/docs/product-requirements/2026-06-12-v01-disputes-v1-prd.mdDurable deadline scheduler
Every awaiting state stores state_deadline and deadline_kind on the dispute row in the same transaction as the transition that created the deadline.
Source: disputes/backend/internal/domain/transition.goThe scheduler selects due rows with FOR UPDATE SKIP LOCKED, then leases the row by moving state_deadline forward before applying timeout work.
Source: disputes/backend/internal/worker/scheduler.goAlur polls PSP truth for stuck payment attempts; Banding owns the deadline truth itself, so a restart cannot lose an in-memory timer.
Source: disputes/docs/adr/2026-06-12-v01-adr-001-durable-deadline-scheduler.mdClosed state machine
Every state change calls transition(), locks the dispute row, checks legal_transition, writes the new state, and appends transition_log in the same transaction.
Source: disputes/backend/internal/domain/transition.goThe acceptance tests iterate every state x every state pair except the legal set, proving illegal transitions are rejected instead of becoming hidden shortcuts.
Source: disputes/backend/internal/httpapi/p1_acceptance_test.goDatabase trigger and privilege checks keep transition_log append-only; timeline output is a read of that audit spine.
Source: disputes/backend/db/migrations/000001_init.up.sqlSimNetwork can deliver a ruling before representment; the state machine rejects the illegal-from-state move and the later valid event converges.
Source: disputes/backend/internal/httpapi/p3_acceptance_test.goExactly-once balanced money
API, scheduler, and SimNetwork paths enqueue booking_outbox rows inside the transition transaction, then the drainer posts to Arus out of band.
Source: disputes/docs/adr/2026-06-12-v01-adr-003-outbox-arus-http-only.mdMoney movement external refs use dispute:<id>:open|release|loss|arb_fee|withdraw:v1 and are unique in the outbox before Arus sees them.
Source: disputes/backend/internal/domain/transition.goThe drainer treats Arus ErrEntryAlreadyExists as posted, so a retry cannot convert a safe replay into an operational failure.
Source: disputes/backend/internal/worker/outbox.goA guard test scans production files for Arus database datasource tokens; Banding reaches Arus through the HTTP API only.
Source: disputes/backend/internal/httpapi/p3_acceptance_test.goADR callouts
The deadline lives in Postgres, not a timer wheel, and tests advance an injected clock to prove restart-safe convergence.
Source: disputes/docs/adr/2026-06-12-v01-adr-001-durable-deadline-scheduler.mdThe lifecycle is small enough to keep visible, so legal transitions and reason codes stay closed and audited.
Source: disputes/docs/adr/2026-06-12-v01-adr-002-state-machine-and-taxonomy.mdState and intent-to-book commit together; Arus availability affects the drainer, not the dispute lifecycle.
Source: disputes/docs/adr/2026-06-12-v01-adr-003-outbox-arus-http-only.mdScenario tokens pick every verdict, duplicate event, out-of-order event, timeout, and arbitration branch with no RNG.
Source: disputes/docs/adr/2026-06-12-v01-adr-004-deterministic-simnetwork.mdDeadline-storm gate
The CI gate seeds 500 disputes across four reason codes and ten scenario tokens, each against a distinct alur:<intent>:capture:v1 reference.
Source: disputes/backend/internal/httpapi/p3_acceptance_test.goThe loop advances the fake clock, runs a partial scheduler batch, recreates the scheduler, then continues until no non-terminal disputes remain.
Source: disputes/backend/internal/httpapi/p3_acceptance_test.goThe gate fails unless there are zero past-deadline awaiting disputes, zero pending/failed outbox rows, and exactly one balanced Arus post per external ref.
Source: disputes/backend/internal/httpapi/p3_acceptance_test.goReview and fixup story
The local SimNetwork default secret was removed; bandingd api now requires BANDING_NETWORK_SECRET and empty secrets reject callbacks.
Source: disputes/backend/cmd/bandingd/main.goThe storm now recreates the scheduler after a partial convergence batch, not during seeding, so the restart proof exercises live due rows.
Source: disputes/backend/internal/httpapi/p3_acceptance_test.goTerminal state chooses the expected release, loss, or withdraw outbox ref, with exactly-once arb_fee rows only for arbitration scenarios.
Source: disputes/backend/internal/httpapi/p3_acceptance_test.goLoop closing
Each dispute references the original Alur capture ref, so the chargeback engine attaches to the payment orchestration artifact rather than floating alone.
Source: disputes/backend/internal/httpapi/p3_acceptance_test.goOpen, release, loss, arbitration fee, and withdraw effects are balanced Arus entry payloads behind same-transaction outbox rows.
Source: disputes/backend/internal/domain/transition.goBanding closes the platform arc by producing stable external refs that can be compared against settlement and ledger truth.
Source: disputes/docs/product-requirements/2026-06-12-v01-disputes-v1-prd.mdHonest limits
Roadmap
The compose demo starts Postgres, Banding, and SimNetwork, but a seed CLI is still needed before it can open a dispute end-to-end.
Source: family-finance/docs/docs/system-analysis/2026-06-13-v01-banding-case-study-evidence-sa.mdReview follow-up tracks operational cleanup for rows that exhaust attempts, separate from the proven exactly-once posting path.
Source: family-finance/docs/docs/system-analysis/2026-06-13-v01-banding-case-study-evidence-sa.mdSimNetwork proves lifecycle behavior; ISO 8583, VROL, Mastercom, and processor-specific network auth remain outside v1.
Source: disputes/docs/adr/2026-06-12-v01-adr-004-deterministic-simnetwork.mdThe script prints no cloud command executed and requires explicit human DEPLOY approval plus real project values outside the script.
Source: disputes/backend/scripts/prepare-cloud-run.shPlatform links
Banding adjudicates the captures Alur moves through payment orchestration.
Source: disputes/docs/product-requirements/2026-06-12-v01-disputes-v1-prd.mdBanding books dispute money effects into Arus through balanced HTTP entries.
Source: disputes/docs/adr/2026-06-12-v01-adr-003-outbox-arus-http-only.mdThe same external-ref discipline makes dispute outcomes reconciliable against settlement.
Source: disputes/docs/product-requirements/2026-06-12-v01-disputes-v1-prd.mdEvidence trail