Lock alpha scope, tracker auth, TOPLOC fraud verification, and deferred multi-tracker money-path work; supersede legacy fraud issues with ADR-0018. Co-authored-by: Cursor <cursoragent@cursor.com>
3.7 KiB
ADR-0019: Money-path consistency (multi-tracker)
Status: Accepted (design); implementation deferred post-alpha
Context
ADR-0015 assumes a single settlement tracker with Raft-replicated ledger events. The hive already gossips billing, accounts, and stats between trackers. When multiple trackers participate, several money-path races appear:
- Settlement retries may double-pay if idempotency keys diverge from on-chain reality.
- Any tracker follower can apply
forfeit/chargeevents — fine under one trusted operator, dangerous with adversarial peers. - Raft term/vote state is in-memory — restart can duplicate leadership epochs.
forfeitevents are not commutative withcharge/payoutordering under concurrent replication.
Alpha ships with one settlement-capable tracker (ADR-0016). This ADR records the accepted multi-tracker design so Bucket 2 issues do not block alpha while preventing silent architectural drift.
Decisions
1. Settlement idempotency (C2)
- Every on-chain payout batch carries a stable
settlement_idalready debiting pending before broadcast (server.py~3353–3356,_send_settlement~3358). - Required hardening:
solana_adapter.send_payouts(~186–213) and the settlement loop must treat the Solana transaction signature as the idempotency anchor — retries reuse the samesettlement_id; confirmed signatures must never spawn a second debit. - Deposit watcher already uses
deposit-<sig>event ids — extend the same pattern to payouts.
2. Consensus-gated money mutations (C3/C4)
Money-affecting events (charge, payout, forfeit, credit, settlement, bind) must commit through Raft (or successor consensus), not best-effort gossip alone:
- Only the Raft leader appends money commands to the replicated log.
- Followers apply committed entries in order; gossip becomes a transport, not the source of truth.
- Settlement signing remains leader-only and treasury-key-bearing (ADR-0015).
Shard-assignment commands already use Raft (raft.py); extend the log command set.
3. Durable Raft metadata (A3)
Persist currentTerm, votedFor, and log index to disk (raft.py ~26 — today term lives only in memory). Without this, restart can elect split leaders and duplicate settlement epochs.
4. Commutative forfeit semantics (H1)
Define explicit ordering rules when replicating forfeit vs charge/payout:
- Forfeit amount is snapshotted at event creation time (
billing.py~287). - Apply forfeit after pending balance reflects all prior committed charges in the same epoch, or use signed pending snapshots in the event payload.
setdefaulton_pending_since(~324) and wallet bindings (~351) must not resurrect stale state on out-of-order apply — use last-writer-wins with(term, index)tie-break.
5. Alpha posture
| Item | Alpha | Post-alpha |
|---|---|---|
| Single treasury holder | Required | Optional multi-sig |
| Gossip-authenticated billing | Required (ADR-0017) | Superseded by Raft commit for money |
| On-chain idempotency | Best-effort resend | Hard guarantee (C2) |
| Consensus-gated settlement | Leader check only (~3331) | Full Raft gate (C3/C4) |
Consequences
- Bucket 2 issues in
.scratch/alpha-hardening/are tracked but not alpha-blocking. - Implementing C3/C4 before alpha would delay release without security benefit while only one operator runs settlement.
- When enabling third-party settlement trackers, all four hardening items must ship together — partial deployment is worse than single-tracker alpha.
Related
- ADR-0015 (settlement loop, pending collateral)
- ADR-0016 (single settlement tracker for alpha)
- ADR-0017 (gossip auth prerequisite)
- Issues:
12-c2-on-chain-idempotency.mdthrough15-h1-commutative-forfeit.md