# 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` / `charge` events — fine under one trusted operator, dangerous with adversarial peers.
- Raft term/vote state is in-memory — restart can duplicate leadership epochs.
- `forfeit` events are not commutative with `charge`/`payout` ordering 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_id`** already 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 same `settlement_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.
- `setdefault` on `_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.md` through `15-h1-commutative-forfeit.md`
