ADR-0015: An air gap between finality and payout

The challenge window (ADR-0003, 7 days) answers exactly one question: was this root challenged? It cannot answer was the dispute machinery itself sound? Two failures live in that gap:

Status: accepted (accepted, implementing). Dated 2026-09-04.

2026-09-04. Status: accepted, implementing.

Context

The challenge window (ADR-0003, 7 days) answers exactly one question: was this root challenged? It cannot answer was the dispute machinery itself sound? Two failures live in that gap:

  1. a bug in the one-step verifier, the state-tree check, or the dispute program — a false root wins its game, or is never challenged because the tooling that would have challenged it was wrong;
  2. a challenger who is right and censored for the whole seven days.

In both cases the window elapses, the root is FINAL, and today the vault pays out one hour later with nothing left that can intervene. Solieum is pre-audit and runs a single sequencer; assuming (1) away is not a posture, it is a hope.

Every other production rollup that settles real money has something in this position. OP Mainnet splits its seven days into a 3 d 12 h dispute window and a 3 d 12 h finality delay, in which the Guardian may blacklist the game a withdrawal rests on. Solieum had the delay — the portal's withdraw_delay_secs — but nothing could act inside it. It was a pause with no hand on it.

The user's decision that frames this ADR: add the air gap, keep the seven days. The window is not the thing being fixed.

Decision

The portal's withdraw delay becomes an air gap: the interval in which a guardian may deny a settlement root.

  • deny_root(root_number). Guardian-signed. Creates [b"deny", n]. Its existence is the denial; there is no flag to unset.
  • Both finalize paths refuse while that account exists, lamports and tokens alike. prove_withdrawal refuses too, before reading the proof, so a denial is not a race an attacker re-enters.
  • reopen_withdrawal(id). Permissionless. Closes the [b"wd", id] record of an unpaid claim whose root was denied, returning the id to unproven. The L2 leaf never expires (ADR-0010), so the holder proves again against the next honest Final root and is paid.
  • The guardian is installed by the admin and may not be the admin (initialize_guard, checked). An air gap held by the upgrade key guards against nothing that key could not already do.
  • Nothing else. deny_root is the only instruction the guardian key can send.

The parameters

ValueWhy
Challenge windowSUPERSEDED — 172 800 s (48 h) per ADR-0016. Was 604 800 s (7 d) and left unchanged by this ADRADR-0003; the step-9 arithmetic budgets 53 moves × 1 h response ≈ 2.2 d worst case against it, at ~3.2× margin. Splitting it in half the way OP Stack does would cut that to ~1.6×, which the step-9 note already calls thin once L1 congestion is priced in. The air gap is added, never carved out of the window.
Air gap (devnet)3 600 s (1 h), unchangedKeeps the drill observable.
Air gap (mainnet)86 400 s (24 h), recommendedThe watchtower detects a divergent root in minutes, so this is generous for detection; it is sized for the human half — reaching a 2-of-3 quorum on hardware keys at 3 a.m.
Total exit, mainnetSUPERSEDED — about 3 days per ADR-0016: 48 h + 24 h. Was 8 days (7 d + 24 h) on the window this ADR was written againstArbitrum One is longer today (6 d 8 h + 2 d), and was not the reason for either number.

Why this is not custody

The shape of the power, not a promise about how it will be used:

  • It names a root, never a withdrawal id. There is no instruction that takes a person's claim. The guardian cannot be pointed at someone.
  • It moves nothing. Its whole effect is the creation of a marker account. No instruction lets the guardian withdraw, redirect, or change a recipient.
  • It cannot confiscate, because the remedy is permissionless. The guardian denies; anyone reopens. A denial costs an honest holder the time to re-prove against a later root, not their money.
  • A guardian that denies is loud. RootDenied is emitted, the marker is permanent and public, and Guard::denials counts them.

Deviation from prompt invariant 9

The architecture prompt states: "The pause guardian cannot prevent finalization of an already-proven withdrawal." That invariant is kept for the pause — finalize_withdrawal and finalize_token_withdrawal still do not read paused, structurally, and no configuration of that flag reaches the withdrawal path.

The air gap is a different mechanism and it does reach a proven withdrawal, so the invariant is restated rather than quietly preserved:

No key can take, redirect, or cancel a withdrawal. A guardian can void the root a proof rests on — never one claim — and the holder re-proves against a later root.

Anything published that says "nothing can stop a proven withdrawal" must be corrected to that sentence. The version that reads better is not the version that is true.

Consequences

  • Withdrawal gains root_number. New records only; no live account is resized. Portal's layout is deliberately untouched — the guardian lives in its own [b"guard"] PDA — so the initialized devnet portal and vault carry over across the upgrade.
  • The account lists of prove_withdrawal, finalize_withdrawal and their token twins each gain the denial marker. This is a breaking change to those instructions: any client that builds them by hand must add the account.
  • The pure core carries the rule, as always: solieum-bridge gains deny_root / is_root_denied / reopen_withdrawal, with the supply-restoration arithmetic that keeps supply ≤ locked true across a reopen. Six tests.
  • The node gains the three instruction builders, the two PDAs, the embedded helpers and portal --init-guard / --deny-root / --reopen.
  • Mainnet initialization now has a third key to place: guardian, separate from the upgrade authority and from the settlement payer (DEPLOYMENT.md D6). The bridge timelock stays ≥ 10 days, which still clears 7 d + 24 h + margin.

Reversal triggers

A validity proof (ADR-0002 multi-prover) removes the failure this exists for, and the air gap should shrink toward the proof latency when one is live — the same trigger that governs the window itself. A guardian that has never been needed after a real audit plus a year of mainnet is a candidate for removal, not for quiet retention.