Settlement Recovery
settlement_runs (migration 020) makes settlement a resumable state machine — no orphan order claims are possible.
State machine
CREATED → CLAIMING → CLAIMED → VALIDATING → COMMITTED
↘ RELEASING → RELEASED (claim release is itself resumable)
↘ FAILED / NEEDS_REVIEW (recovery_action='release' → sweeper retries)1
2
3
2
3
- Claimed-but-uncommitted orders carry
orders.settlement_id='run:<run_id>'— always resolvable to a run row.run:*tags with no run row are zombie claims the sweeper releases. lease_owner/lease_expires_at/fencefence all transitions and order updates — a stale writer cannot release or commit.- Fee snapshot (
pg_fee_bp/platform_fee_bp) is fixed at run creation; commit re-checks0 ≤ fee ≤ gross,net ≥ 0— never commits abnormal settlement.
Operator actions
http
POST /api/v1/settlements # seller-scoped; resumes stale run inline or 409 SETTLEMENT_RUN_IN_PROGRESS
POST /api/v1/admin/settlements/sweep # admin — finish pending releases + zombie 'run:*' claims
POST /api/v1/admin/settlements/:id/mark-paid {payout_ref} # payout confirmation, CAS + ref conflict 4091
2
3
2
3
Recovery op RELEASE_STALE_SETTLEMENT_CLAIM (approval required) targets settlement_run or a single order when the sweeper is insufficient.
Evidence & drift
settlement.commitevidence is chain-sealed at record-batch commit (audit-evidence).- Post-commit re-check compares
SUM(amount-refunded)and order count against the run snapshot — drift →NEEDS_REVIEW(money is committed; investigate, don't re-run blindly). fee_rate.changeevidence records every fee write, so aNEEDS_REVIEWrun's snapshot can be audited against the rates in effect atrun.created_at.