# Run Jovian Upgrade

> Execute the Jovian upgrade pipeline (v4.1.0 + v5.0.0 + Succinct v2 + optional basefee) on a local Anvil fork, as an orchestrator that dispatches one subagent per TODO. Use when: upgrading Celo OP Stack, testing Jovian, running v4/v5/succ-v2 upgrades, local fork testing, op-deployer bootstrap, SuperchainOps simulation, contract state comparison, or when the user mentions Jovian, v4.1.0, v5.0.0, Succinct, OPSuccinct, upgrade pipeline.

- Skill: `celo-org/run-jovian-upgrade` (Agent Skill)
- Install (CLI): `npx skillmds@latest add celo-org/run-jovian-upgrade`
- Raw SKILL.md: https://api.skillmd.com/api/skills/celo-org/run-jovian-upgrade/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: celo-org (https://skillmd.com/u/celo-org)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/celo-org/run-jovian-upgrade

---


# Jovian Upgrade Pipeline — Orchestrator Protocol

## §1 ORCHESTRATOR RULE (read first, read every run)

You are the **orchestrator**. You do NOT run `anvil`, `cast`, `forge`, `bootstrap.sh`,
`exec-*.sh`, `just sign`, or any heavy bash yourself. Your only direct actions are:

- `TodoWrite` — create and update the fixed checklist.
- `Task(subagent_type="oh-my-claudecode:executor", model="sonnet", ...)` — dispatch ONE
  subagent per TODO, passing the full §CONTEXT_BLOCK and the CARD text for that TODO.
- `mcp_question` — only at checkpoint gates (interactive mode) and final cleanup gate.
- A short final report summarising what happened.

Everything else is delegated. This keeps your context clean and prevents drift.

### §1.1 Hard rules (non-negotiable)

1. **Localhost only.** Every subagent runs against `http://127.0.0.1:$RPC_PORT`.
   If the user requests real mainnet/sepolia execution → REFUSE, print the commands,
   explain why.
2. **The TODO list is fixed.** You MUST create the full list in §THE PROTOCOL at the
   start of every run, verbatim, in order, no merges, no renames, no omissions.
3. **No skipping.** A "skippable" step still appears as a TODO; the subagent either
   executes it or returns `STATUS: SKIP` with a reason. Silently dropping a TODO is a
   binding violation.
4. **Subagent per TODO.** You never batch multiple TODOs into one subagent. You never
   run two TODOs in parallel (the pipeline is sequential by construction).
5a. **Evidence-gated completion (subagent TODOs).** You only mark a TODO executed by a
   subagent `completed` when the subagent's report contains the `REQUIRED EVIDENCE`
   block named by that TODO's card. If not, re-dispatch with a corrective prompt.
5b. **Orchestrator-executed TODOs.** The orchestrator-only TODOs — T14/T23/T29/T34
   checkpoint gates (CP0..CP3), T40 CP4 gate, T42 cleanup gate — have NO subagent.
   Their evidence is whatever the orchestrator itself emits: the `mcp_question`
   response value (interactive) or a one-line autonomous decision string. Rule 5a
   does not apply to these — you mark them completed the moment you record the
   decision. Note: T00 is NOT in this list — T00 does dispatch a minimal `haiku`
   subagent to register the cleanup plan, then stays `pending` throughout the
   pipeline per rule 6 below (it's completed at the end of T41, not T00's own
   subagent return).
6. **Cleanup TODO exists BEFORE anvil starts.** TODO `T00` is created in the first
   `TodoWrite` call as status `pending` and stays `pending` (NOT `in_progress`,
   NOT `completed`) throughout the entire pipeline. It is the visible sentinel that
   cleanup is owed. Mark it `completed` only at the end of T41 (after cleanup
   succeeded). Multiple simultaneous `pending` items is allowed; TodoWrite only
   requires at most one `in_progress` at a time.
7. **No double-running.** If a script fails mid-pipeline, the only recovery is
   cleanup + re-fork from scratch. Never re-run a partial `bootstrap.sh` or
   `exec-*.sh`.
8. **Checkpoints are four TODOs, not one.** Every checkpoint (CP0..CP4) is represented
   by four TODOs: `.1` queries, `.2` PASS/FAIL table, `.3` x-ray offer, `.4` gate.
   You may not fold them together.

### §1.2 Recovery protocol on FAIL

When a subagent returns `STATUS: FAIL`, the orchestrator's response depends on which
TODO failed:

| Failed TODO range | Recovery                                                                                                 |
| ----------------- | -------------------------------------------------------------------------------------------------------- |
| T01..T07          | Fix the root cause (wrong path, missing tool, busy port), re-dispatch the same TODO. No re-fork needed.  |
| T08..T10          | Bootstrap/plant partial state. Jump to T41 cleanup, re-fork from scratch at T04.                         |
| T11..T14 (CP0)    | If validation fails (not a query error), the fork is bad. Jump to T41 cleanup, re-fork from scratch.     |
| T15..T17 (sign)   | Fix signing env (LOCAL_RPC_URL, mise, TEST_PK), re-dispatch. No re-fork needed.                          |
| T18 / T24 (sim)   | Only FAIL on actual simulate failure. On SKIP (pre-flight), record reason, continue.                     |
| T19 / T25 / T30   | Exec failure (`revert`). Jump to T41 cleanup, re-fork from scratch. Do NOT retry in place.               |
| T20..T23 (CP1)    | Post-v4 invariant broken. Jump to T41 cleanup, re-fork from scratch.                                     |
| T26..T29 (CP2)    | Post-v5 invariant broken. Jump to T41 cleanup, re-fork from scratch.                                     |
| T31..T34 (CP3)    | Post-succ-v2 invariant broken (esp. `SG_ASR == OLD_ASR`). Jump to T41 cleanup, re-fork from scratch.     |
| T35 (Phase 7)     | Comparison table failure — pipeline already done, report and proceed to T36.                             |
| T36..T40 (CP4)    | Basefee failure — cleanup, investigate, re-fork if the operator wants to retry.                          |
| T41 (cleanup)     | Manual cleanup required. Log a warning in final summary so the operator knows.                          |

"Re-fork from scratch" means: reset the TodoWrite list, re-create T00..T42 from the
fixed template, and start over at T01. Do NOT patch the TodoWrite list in place.

### §1.3 CORE INVARIANTS (memory anchor — read after §1.1)



A one-glance summary of things that MUST be true of every run, regardless of
NETWORK or MODE. If any of these is not true at any point, the pipeline is FAILED.

| # | Invariant                                                                                             |
| - | ----------------------------------------------------------------------------------------------------- |
| 1 | Every bash command targets `http://127.0.0.1:$RPC_PORT` — never a real network RPC.                   |
| 2 | The TodoWrite list has exactly 43 items (`T00..T42`), created in one call after §2 mode detection.    |
| 3 | `T00` is `pending` the entire run, not `in_progress`, and completes only after T41 cleanup succeeds.  |
| 4 | Exactly ONE subagent is `in_progress` at a time. Sequential dispatch. No parallel TODOs.              |
| 5 | Checkpoint gates (T14, T23, T29, T34, T40, T42) are orchestrator-only — NO subagent dispatch.         |
| 6 | X-ray offers (T13, T22, T28, T33, T39) are their own TODOs, each a subagent that ONLY builds text.    |
| 7 | Simulations (T18, T24) are mandatory unless a documented pre-flight reason forces SKIP.               |
| 8 | SKIP TODOs still dispatch a subagent (or are orchestrator-only); silent dropping = binding violation. |
| 9 | Dynamic slots (`OPCM_V4`, `OPCM_V5`, `NEW_CSC`, `NEW_ASR`) are filled by specific TODOs per §3.2.     |
| 10| Real `DEPLOYER_PK` flows forward in prompts; evidence reports redact it as `0x***`.                   |
| 11| Every exit path — success, failure, user stop, crash — lands at T41 cleanup.                          |
| 12| On CP3 FAIL (SG_ASR == OLD_ASR) or any other irrecoverable failure: cleanup, re-fork from scratch.    |

### §1.4 What goes to a subagent vs. to main agent

| Action                              | Main agent | Subagent |
| ------------------------------------ | ---------- | -------- |
| `TodoWrite`                          | ✅         | ❌       |
| Dispatching Task                     | ✅         | ❌       |
| `mcp_question` (checkpoint gate)     | ✅         | ❌       |
| `mcp_question` (prerequisites)       | ✅         | ❌       |
| `anvil`, `cast`, `forge`, `jq`, `lsof` | ❌       | ✅       |
| `bootstrap.sh`, `mock-*.sh`, `exec-*.sh` | ❌   | ✅       |
| `just sign`, `just simulate`         | ❌         | ✅       |
| Reading logs / verifying state       | ❌         | ✅       |
| Rendering PASS/FAIL table            | ❌         | ✅       |

---

## §2 MODE DETECTION (Step 0 — first action of every run)

**Detect mode** by scanning your invoking prompt for any of:
`non-interactive`, `background task`, `autonomous`, `headless`, `no questions`,
`do not ask`, `just run it`, `make decisions and proceed`.

- Match → **Autonomous mode**.
- No match AND you can call `mcp_question` → **Interactive mode**.

**Announce the mode** in your first message: `Detected mode: Interactive|Autonomous`.

### §2.1 Autonomous mode — required details

In autonomous mode you MUST validate every required detail BEFORE any TodoWrite,
BEFORE any subagent dispatch. Missing detail → emit the missing-detail quit block
from §2.2 and stop.

Required for both modes:

| # | Detail             | Example                                         |
| - | ------------------ | ----------------------------------------------- |
| 1 | Execution mode     | `A` (mocked) or `B` (real sigs)                 |
| 2 | `CELO_ROOT`        | e.g. `/path/to/Celo` (your local checkout)      |
| 3 | Network            | `mainnet`, `sepolia`, `chaos`                   |
| 4 | Fork block         | numeric or alias (see §APPENDIX B)              |
| 5 | Upstream RPC URL   | Tenderly / archive Alchemy / archive Infura     |
| 6 | Anvil port         | default `8545`                                  |
| 7 | Deployer PK        | derives to network-specific SENDER (§APPENDIX B) |

Required in Mode A only:

| # | Detail                         | Notes                                         |
| - | ------------------------------ | --------------------------------------------- |
| 8a| Succinct config filename       | `opsuccinctfdgconfig.<network>.json` under `$SUCCINCT_REPO/contracts/` — OR generate via `just fetch-fdg-config`. See §3.4 for repo resolution and §F.1 for per-network config references. |
| 9a| `include_basefee`              | MUST be `no` — Mode A rejects `yes`           |

Required in Mode B only:

| # | Detail                         | Notes                                         |
| - | ------------------------------ | --------------------------------------------- |
| 8b| `include_basefee`              | `yes` or `no`                                 |

Optional with defaults:

- Sibling repo paths → default: sibling directories of `$CELO_ROOT` (e.g. `$CELO_ROOT/../Optimism`, `$CELO_ROOT/../Optimism2`, `$CELO_ROOT/../SuperchainOps`, `$CELO_ROOT/../CeloSuperchainOps`, `$CELO_ROOT/../Succinct`, `$CELO_ROOT/../Succinct2`, `$CELO_ROOT/../x-ray`)
- `SUCCINCT_ROOT_MAINNET` → default `$CELO_ROOT/../Succinct2` (see §3.4)
- Mocked signer addresses/PKs → defaults in §APPENDIX B

### §2.2 Missing-detail quit block (autonomous mode)

```
AUTONOMOUS MODE FAILURE: missing required detail(s).

Missing:
- <detail name>: not provided or ambiguous
- ...

This skill runs in autonomous mode and cannot ask the user. Re-invoke with all
required details explicitly passed in the prompt.

Aborting before any bash commands. No cleanup needed (nothing was started).
```

### §2.3 Interactive mode — prerequisites collection

Dispatch **one** subagent (`T01`, see §THE PROTOCOL) that asks the user for the missing
details via printed questions (the subagent does NOT use `mcp_question` — only the
main orchestrator does). The subagent returns a filled §CONTEXT_BLOCK as evidence.

---

## §3 THE CONTEXT BLOCK (build once, then grow across TODOs)

Every subagent dispatch starts with the same CONTEXT_BLOCK. You assemble it once after
T01 completes and **mutate a single slot only after it is explicitly discovered or
written** by a prior TODO. Never let a subagent "guess" a value.

```
CELO_ROOT=<path>
OP_ROOT_V4=<path>                 # default $CELO_ROOT/../Optimism (overridable)
OP_ROOT_V5=<path>                 # default $CELO_ROOT/../Optimism2
SUPERCHAIN_OPS=<path>             # default $CELO_ROOT/../SuperchainOps
CELO_SUPERCHAIN_OPS=<path>        # default $CELO_ROOT/../CeloSuperchainOps
SUCCINCT_ROOT=<path>              # default $CELO_ROOT/../Succinct
XRAY_ROOT=<path>                  # default $CELO_ROOT/../x-ray (clone of https://github.com/celo-org/x-ray)
NETWORK=<mainnet|sepolia|chaos>
MODE=<A|B>
INCLUDE_BASEFEE=<yes|no>          # Mode B only; Mode A forces 'no'
RPC_PORT=<port>                   # default 8545
RPC_URL=http://127.0.0.1:<port>
LOCAL_RPC_URL=http://127.0.0.1:<port>   # SuperchainOps reads this
XRAY_PORT=<port>                  # default 8080 (collision check at T07)
UPSTREAM_RPC_URL=<archive RPC>    # see §3.1 redaction policy
FORK_BLOCK=<number>
FORK_CHAIN_ID=<1 | 11155111>
DEPLOYER_PK=<0x...>               # see §3.1 handling policy
SENDER=<network-specific; §APPENDIX B.6>
PARENT_SAFE=<§APPENDIX B.3>
CLABS_SAFE=<§APPENDIX B.3>
COUNCIL_SAFE=<§APPENDIX B.3>
SYSTEM_CONFIG=<§APPENDIX B.4 or B.4b>
DGF=<§APPENDIX B.4 or B.4b>
OLD_ASR=<§APPENDIX B.4 or B.4b>
OLD_CSC=<§APPENDIX B.4 or B.4b>
SUCCINCT_IMPL_ADDR=<§APPENDIX B.4 or B.4b>
EXPECTED_NEW_ASR=<§APPENDIX B.4 or B.4b>   # deterministic post-v4 ASR
SUCCINCT_CONFIG_FILE=<Mode A only; filename under $SUCCINCT_REPO/contracts/>
SUCCINCT_REPO=<resolved Succinct repo; see §3.4>
SUCCINCT_PROPOSAL_ADDR=<expected impl addr from CeloSuperchainOps proposal; §F.2>
SUCCINCT_DEPLOY_STRATEGY=<pending|deploy|setcode; see §3.2 & §5.10>
OPCM_V4=<see §3.2 dynamic slots>
OPCM_V5=<see §3.2 dynamic slots>
NEW_CSC=<see §3.2 dynamic slots>
NEW_ASR=<see §3.2 dynamic slots>
```

### §3.1 Secret & RPC handling

- **`DEPLOYER_PK`**: the orchestrator holds the real value in memory and passes it
  verbatim into each subagent dispatch prompt that needs it (T04 anvil fork,
  T08 bootstrap v4, T09 bootstrap v5, T19 exec v4, T25 exec v5, T30 exec succ-v2,
  T36 exec basefee). In **evidence returned by subagents**, the value MUST be redacted
  as `0x***`. In **TodoWrite task descriptions**, never include the raw PK. Rule:
  "real PK flows forward in prompts, redacted PK flows back in evidence."
- **`UPSTREAM_RPC_URL`**: treated the same way. Real URL flows forward, redacted as
  `<upstream-archive>` in evidence.
- **CONTEXT_BLOCK shown in this skill file uses placeholders** — when the
  orchestrator passes it to a subagent, it substitutes the real values.

### §3.2 Dynamic slots (mutated by specific TODOs)

Dynamic slots are populated between dispatches (not all at T01). The orchestrator
MUST update the CONTEXT_BLOCK between TODOs when each slot is filled:

| Slot         | Filled by            | Source                                                                             |
| ------------ | -------------------- | ---------------------------------------------------------------------------------- |
| `OPCM_V4`    | **Mode A**: T08 output. **Mode B**: T01 static from §B.5 (not actually dynamic in Mode B) | Mode A: `jq -r .opcm .../op-deployer/v4/config-upgrade.json` / Mode B: §B.5 literal |
| `OPCM_V5`    | **Mode A**: T09 output. **Mode B**: T01 static from §B.5                                    | Mode A: same for v5 / Mode B: §B.5 literal                                          |
| `NEW_CSC`    | T20                  | `cast call $SYSTEM_CONFIG "superchainConfig()(address)"`                           |
| `NEW_ASR`    | T20                  | `cast call <PERM_GAME_V4> "anchorStateRegistry()(address)"`                        |
| `OLD_CSC`    | **Mode A mainnet**: §B.4 literal at T01. **Other networks**: T11 dynamic capture from `SystemConfig.superchainConfig()` on the pre-v4 fork state | see §B.4b sepolia note |
| `SUCCINCT_IMPL_ADDR` | **Mainnet**: §B.4 literal at T01 (initial). **Sepolia**: T01 reads it from the matching `"sepolia-succ-vX")` case's CALLDATA in `exec-upgrade.sh` (impl embedded in the calldata blob, and in the `setImplementation(42, 0x...)` comment above the case where present) — succ-v2 → `0x67cd626e1c2534cd5a129ba9208de69b305ffbd3`, succ-v210 → `0x36ad817e6a273a05d6f7638bc3a68a0233b0c33e`. **Chaos**: operator-provided via the `SUCCINCT_IMPL` env to `./exec-upgrade.sh chaos succ-v2` (the planted/deployed impl captured at T01/T10) — NOT grep-ed from any script. T10 may override if Strategy A (deploy) succeeds — see §5.10. | §B.4 / §B.4b / §F.2 |
| `SUCCINCT_PROPOSAL_ADDR` | T01 from §F.2 per NETWORK. | §F.2 literal |
| `SUCCINCT_DEPLOY_STRATEGY` | T10 — set to `deploy` if Strategy A succeeds, `setcode` if fallback used. | §5.10 |
| `SUCCINCT_REPO` | T01 — resolved per §3.4: mainnet → `$SUCCINCT_ROOT_MAINNET`, sepolia/chaos → `$SUCCINCT_ROOT`. | §3.4 |

Rule: after a TODO whose evidence contains one of these slots, the orchestrator's
very next action is to append the new value to its in-memory CONTEXT_BLOCK. All
subsequent dispatches use the grown block. Subagents downstream of T20 (T21, T26,
T27, T31, T32) therefore see `NEW_CSC` and `NEW_ASR` as concrete addresses, not
`<placeholder>`.

### §3.3 Sibling repo path portability

The `OP_ROOT_V4`/`OP_ROOT_V5`/`SUPERCHAIN_OPS`/`CELO_SUPERCHAIN_OPS`/`SUCCINCT_ROOT`/
`SUCCINCT_ROOT_MAINNET` defaults assume a single operator's laptop layout. On a
different machine, the orchestrator MUST override these in T01 (interactive: ask;
autonomous: orchestrator prompt must pass them).

### §3.4 Succinct repo resolution (NETWORK-dependent)

Succinct has a **per-network repo problem**: different networks were deployed with
different repo versions, branches, and commits — each producing different vkeys and
therefore different contract addresses. This is a temporary state; eventually all
versions should converge into tagged releases in the public Succinct repo.

**Current state (Jovian era)**:

| Network  | Repo variable          | Default path                       | Why                                                                                           |
| -------- | ---------------------- | ---------------------------------- | --------------------------------------------------------------------------------------------- |
| mainnet  | `SUCCINCT_ROOT_MAINNET`| `$CELO_ROOT/../Succinct2`          | Requires a specific repo, branch, or commit to produce vkeys matching the mainnet deployment. Operator determines the correct source.                    |
| sepolia  | `SUCCINCT_ROOT`        | `$CELO_ROOT/../Succinct`           | Public repo at specific commit produces vkeys matching sepolia deployment.                     |
| chaos    | `SUCCINCT_ROOT`        | `$CELO_ROOT/../Succinct`           | Public repo at specific commit produces vkeys matching chaos deployment.                      |

At T01, set `SUCCINCT_REPO` to the resolved path:
- `NETWORK=mainnet` → `SUCCINCT_REPO=$SUCCINCT_ROOT_MAINNET`
- `NETWORK=sepolia|chaos` → `SUCCINCT_REPO=$SUCCINCT_ROOT`

The FDG config file at `$SUCCINCT_REPO/contracts/opsuccinctfdgconfig.$NETWORK.json`
(or the generic `opsuccinctfdgconfig.json` generated by `just fetch-fdg-config`)
contains `aggregationVkey` and `rangeVkeyCommitment` — these are constructor
parameters to `OPSuccinctFaultDisputeGame` and directly affect the deployed
contract address via CREATE (deployer + nonce + initcode w/ constructor args).

**Vkey → address chain**: `repo commit` → `SP1 ELF binaries` → `aggregationVkey +
rangeVkeyCommitment` → `constructor args` → `initcode hash` → with deployer nonce →
`CREATE address`. Wrong commit = wrong vkeys = wrong address = proposal mismatch.

See §APPENDIX F for per-network commit references, config URLs, and expected
proposal addresses.

---

## §4 THE PROTOCOL — FIXED TODO CHECKLIST

Create the list below in ONE `TodoWrite` call, immediately after §2 mode detection.
Items are numbered `T00`..`T42` in BOTH Mode A and Mode B (43 items total — the list
has the same length in both modes; only the subjects differ between the tables below,
with skipped phases marked as explicit SKIP TODOs). Use these exact subjects verbatim
(you may add the `activeForm` field as shown).

### §4.1 Mode A (mocked, full canonical flow)

| ID  | Subject                                                              | Card §  |
| --- | -------------------------------------------------------------------- | ------- |
| T00 | CLEANUP PRECOMMIT: kill anvil on $RPC_PORT at end of run             | §5.0    |
| T01 | Collect prerequisites + build CONTEXT_BLOCK                          | §5.1    |
| T02 | Validate repo branches & build artifacts (5 sibling repos)           | §5.2    |
| T03 | Validate CLI tools (anvil, cast, forge, jq, just, mise)              | §5.3    |
| T04 | Start anvil fork at $FORK_BLOCK and verify block number              | §5.4    |
| T05 | Mock Safe storage via mock-$NETWORK.sh                               | §5.5    |
| T06 | Validate mock state (thresholds=2, owners, nonces)                   | §5.6    |
| T07 | Start x-ray dashboard on :$XRAY_PORT                                 | §5.7    |
| T08 | Bootstrap v4 OPCM via op-deployer/v4/bootstrap.sh                    | §5.8    |
| T09 | Bootstrap v5 OPCM via op-deployer/v5/bootstrap.sh                    | §5.9    |
| T10 | Plant Succinct v2 impl (Strategy A: deploy from repo; Strategy B: setCodeAtAddress fallback) | §5.10 |
| T11 | CP0.1 — run post-deploy baseline validation queries                  | §5.11   |
| T12 | CP0.2 — render PASS/FAIL table                                       | §5.12   |
| T13 | CP0.3 — x-ray offer (interactive mcp_question OR headless ack)       | §5.13   |
| T14 | CP0.4 — checkpoint gate (mcp_question / autonomous proceed)          | §5.14   |
| T15 | Sign v4 (clabs + council) via CeloSuperchainOps                      | §5.15   |
| T16 | Sign v5 (clabs + council) via CeloSuperchainOps                      | §5.16   |
| T17 | Sign succ-v2 (clabs + council) via CeloSuperchainOps                 | §5.17   |
| T18 | Simulate v4 via SuperchainOps (REQUIRED; skip only with documented reason) | §5.18 |
| T19 | Execute v4 (exec-mocked.sh mainnet / exec-upgrade.sh sepolia v4 / exec-upgrade.sh chaos v4) | §5.19   |
| T20 | CP1.1 — run post-v4 validation queries (discover $NEW_ASR, $NEW_CSC) | §5.20   |
| T21 | CP1.2 — render PASS/FAIL table                                       | §5.21   |
| T22 | CP1.3 — x-ray offer                                                  | §5.22   |
| T23 | CP1.4 — checkpoint gate                                              | §5.23   |
| T24 | Simulate v5 via SuperchainOps (REQUIRED; skip only with documented reason) | §5.24 |
| T25 | Execute v5 (exec-mocked.sh mainnet / exec-upgrade.sh sepolia v5 / exec-upgrade.sh chaos v5) | §5.25   |
| T26 | CP2.1 — run post-v5 validation queries                               | §5.26   |
| T27 | CP2.2 — render PASS/FAIL table                                       | §5.27   |
| T28 | CP2.3 — x-ray offer                                                  | §5.28   |
| T29 | CP2.4 — checkpoint gate                                              | §5.29   |
| T30 | Execute succ-v2 (exec-mocked.sh mainnet / exec-upgrade.sh sepolia succ-v2 / exec-upgrade.sh chaos succ-v2) | §5.30 |
| T31 | CP3.1 — run post-succ-v2 validation queries                          | §5.31   |
| T32 | CP3.2 — render PASS/FAIL table                                       | §5.32   |
| T33 | CP3.3 — x-ray offer                                                  | §5.33   |
| T34 | CP3.4 — checkpoint gate                                              | §5.34   |
| T35 | Phase 7 — final comparison table (CP0..CP3 progression)              | §5.35   |
| T36 | Phase 8 — SKIP (Mode A incompatible)                                 | §5.36   |
| T37 | CP4.1 — SKIP (Phase 8 not executed)                                  | §5.37   |
| T38 | CP4.2 — SKIP                                                         | §5.38   |
| T39 | CP4.3 — SKIP                                                         | §5.39   |
| T40 | CP4.4 — SKIP                                                         | §5.40   |
| T41 | CLEANUP — kill anvil, kill x-ray, verify port free, print summary    | §5.41   |
| T42 | CLEANUP GATE — mcp_question (interactive) / final print (autonomous) | §5.42   |

### §4.2 Mode B (real signatures, post-impls block)

| ID  | Subject                                                              | Card §  |
| --- | -------------------------------------------------------------------- | ------- |
| T00 | CLEANUP PRECOMMIT: kill anvil on $RPC_PORT at end of run             | §5.0    |
| T01 | Collect prerequisites + build CONTEXT_BLOCK                          | §5.1    |
| T02 | Validate repo state (Celo only; sibling builds not required)         | §5.2    |
| T03 | Validate CLI tools                                                   | §5.3    |
| T04 | Start anvil fork at $FORK_BLOCK (post-impls) and verify block        | §5.4    |
| T05 | SKIP mock — Mode B keeps real Safes (emit explicit SKIP report)      | §5.5    |
| T06 | SKIP mock validation — n/a in Mode B                                 | §5.6    |
| T07 | Start x-ray dashboard on :$XRAY_PORT                                 | §5.7    |
| T08 | SKIP bootstrap v4 — real OPCM v4 already on-chain                    | §5.8    |
| T09 | SKIP bootstrap v5 — real OPCM v5 already on-chain                    | §5.9    |
| T10 | SKIP Succinct plant — real impl already on-chain at fork block       | §5.10   |
| T11 | CP0.1 — post-deploy baseline queries                                 | §5.11   |
| T12 | CP0.2 — render PASS/FAIL table                                       | §5.12   |
| T13 | CP0.3 — x-ray offer                                                  | §5.13   |
| T14 | CP0.4 — checkpoint gate                                              | §5.14   |
| T15 | SKIP sign v4 — real sigs in secrets/.env.signers.v4                  | §5.15   |
| T16 | SKIP sign v5 — real sigs in secrets/.env.signers.v5                  | §5.16   |
| T17 | SKIP sign succ-v2 — real sigs in secrets/.env.signers.succinct200    | §5.17   |
| T18 | Simulate v4 via SuperchainOps (REQUIRED; skip only with documented reason) | §5.18 |
| T19 | Execute v4 via exec-upgrade.sh mainnet v4                            | §5.19   |
| T20 | CP1.1 — run post-v4 validation queries                               | §5.20   |
| T21 | CP1.2 — render PASS/FAIL table                                       | §5.21   |
| T22 | CP1.3 — x-ray offer                                                  | §5.22   |
| T23 | CP1.4 — checkpoint gate                                              | §5.23   |
| T24 | Simulate v5 via SuperchainOps (REQUIRED)                             | §5.24   |
| T25 | Execute v5 via exec-upgrade.sh mainnet v5                            | §5.25   |
| T26 | CP2.1 — run post-v5 validation queries                               | §5.26   |
| T27 | CP2.2 — render PASS/FAIL table                                       | §5.27   |
| T28 | CP2.3 — x-ray offer                                                  | §5.28   |
| T29 | CP2.4 — checkpoint gate                                              | §5.29   |
| T30 | Execute succ-v2 via exec-upgrade.sh mainnet succ-v2                  | §5.30   |
| T31 | CP3.1 — post-succ-v2 queries                                         | §5.31   |
| T32 | CP3.2 — render PASS/FAIL table                                       | §5.32   |
| T33 | CP3.3 — x-ray offer                                                  | §5.33   |
| T34 | CP3.4 — checkpoint gate                                              | §5.34   |
| T35 | Phase 7 — final comparison table                                     | §5.35   |
| T36 | Phase 8 — execute exec-basefee.sh (if INCLUDE_BASEFEE=yes) OR SKIP   | §5.36   |
| T37 | CP4.1 — post-basefee queries (only if T36 executed)                  | §5.37   |
| T38 | CP4.2 — render PASS/FAIL table                                       | §5.38   |
| T39 | CP4.3 — x-ray offer                                                  | §5.39   |
| T40 | CP4.4 — checkpoint gate                                              | §5.40   |
| T41 | CLEANUP — kill anvil, kill x-ray, verify port free, print summary    | §5.41   |
| T42 | CLEANUP GATE                                                         | §5.42   |

> In Mode A: `T36..T40` stay in the list as explicit SKIP TODOs (Mode A incompatible).
> In Mode B with `INCLUDE_BASEFEE=no`: `T36..T40` stay in the list as SKIPs.

---

## §5 EXECUTION CARDS — per-TODO subagent prompt templates

Each card tells you (a) what the subagent should do, (b) the exact `REQUIRED EVIDENCE`
block the subagent must return, (c) the PASS condition, (d) the model tier.

All subagents are dispatched with this wrapper:

```
Task(
  subagent_type="oh-my-claudecode:executor",
  model="<tier>",
  description="Jovian T<NN>",
  prompt="""
<§1.1 safety reminder: localhost only; no retries; fail-fast>
<§CONTEXT_BLOCK verbatim>

TODO: T<NN>
CARD: §5.<NN>

TASK:
<card body>

REQUIRED EVIDENCE (return ALL of these, labelled, in your final report):
<card evidence list>

OUTPUT FORMAT:
---
TODO: T<NN>
STATUS: PASS | FAIL | SKIP
EVIDENCE:
  <labelled evidence block>
NEXT: <one sentence>
---

If the task fails: report FAIL with the exact error and DO NOT attempt recovery.
"""
)
```

Orchestrator verifies evidence → marks TODO complete. Missing evidence → re-dispatch.

### §5.0 — T00 Cleanup precommit (SENTINEL — stays `pending`)

**Task**: Lightweight sentinel subagent that records the cleanup plan so it is
visible from the very first `TodoWrite`. It lists the files/processes that T41
will kill/preserve but does NOT kill anything itself. After the subagent returns
PASS, the orchestrator keeps T00 on the TodoWrite list as **`pending`** (per §1.1
rule 6 — NOT `in_progress`, NOT `completed`) until T41 cleanup succeeds, at which
point the orchestrator marks T00 `completed`.

Files the subagent registers for cleanup: `/tmp/anvil-$RPC_PORT.pid`,
`/tmp/anvil-$RPC_PORT.log`, `/tmp/xray-$RPC_PORT.pid`, `/tmp/xray-$RPC_PORT.log`.
Ports to verify free at T41: `$RPC_PORT`, `$XRAY_PORT`.

Tier: `haiku`.

**Evidence**: `ARMED: rpc_port=$RPC_PORT; xray_port=$XRAY_PORT; files=/tmp/anvil-$RPC_PORT.pid,/tmp/anvil-$RPC_PORT.log,/tmp/xray-$RPC_PORT.pid,/tmp/xray-$RPC_PORT.log; cleanup_todo=T41`

### §5.1 — T01 Collect prerequisites

**Task**: Build the §CONTEXT_BLOCK. Steps (applied in order):

1. **Alias normalization**: if `FORK_BLOCK` is a string alias (`oldest block`,
   `pre-impls`, `mode-a block`, `post-impls`, `mode-b block`, `impls-deployed`),
   resolve via §B.1 for the chosen NETWORK. After resolution, verify the resolved
   numeric block matches the declared `MODE` (Mode A = pre-impls row; Mode B =
   post-impls row). Mismatch → FAIL with `mode-block mismatch: <mode> expects <n>, got <actual>`.
2. **Address resolution**: populate all static slots (`SENDER`, `PARENT_SAFE`,
   `CLABS_SAFE`, `COUNCIL_SAFE`, `SYSTEM_CONFIG`, `DGF`, `OLD_ASR`) from §B.3/§B.4
   (mainnet) or §B.4b (sepolia/chaos). On mainnet also populate `OLD_CSC` and
   `SUCCINCT_IMPL_ADDR` from §B.4 literals; on **sepolia/chaos**, `OLD_CSC` is
   deferred (set to empty placeholder — captured later at T11 from
   `SystemConfig.superchainConfig()` on the pre-v4 fork and copied into
   CONTEXT_BLOCK before T12 dispatches). On **sepolia**, `SUCCINCT_IMPL_ADDR` is
   read at T01 from the matching `"sepolia-succ-vX")` case's CALLDATA in
   `$CELO_ROOT/packages/op-tooling/exec/exec-upgrade.sh` (the impl is embedded in
   the calldata blob, and in the `setImplementation(42, 0x...)` comment above the
   case where present): succ-v2 → `0x67cd626e1c2534cd5a129ba9208de69b305ffbd3`,
    succ-v210 → `0x36ad817e6a273a05d6f7638bc3a68a0233b0c33e`. On **chaos**,
    `SUCCINCT_IMPL_ADDR` is operator-provided via the `SUCCINCT_IMPL` env to
    `./exec-upgrade.sh chaos succ-v2` (the planted/deployed impl captured at
    T01/T10) — NOT grep-ed from any script.
   `EXPECTED_NEW_ASR` is populated from §B.4 on mainnet and left empty on
   non-mainnet runs (compared dynamically at T20).
3. **OPCM resolution**: Mode B sets `OPCM_V4` and `OPCM_V5` from §B.5. Mode A leaves
   them empty (filled by T08/T09).
4. **Secret handling**: `DEPLOYER_PK` and `UPSTREAM_RPC_URL` are passed through to
   downstream cards verbatim; in the **evidence report** from T01, redact
   `DEPLOYER_PK` as `0x***` and `UPSTREAM_RPC_URL` as `<upstream-archive>`.
5. **Interactive round-trip**: in Interactive mode, if any required detail is
   missing, the subagent prints numbered questions to stdout and returns
   `STATUS: FAIL` with evidence `missing_details=<list>`. The orchestrator then
   calls `mcp_question` itself (only the orchestrator is allowed to, per §1.4)
   to collect answers, then re-dispatches T01 with the merged details.
6. **Autonomous mode**: on any missing or ambiguous detail → `STATUS: FAIL` with
   the §2.2 missing-detail quit block. Do NOT guess.

Tier: `haiku`.

**Evidence**: full CONTEXT_BLOCK filled in; `DEPLOYER_PK` redacted as `0x***`;
`UPSTREAM_RPC_URL` redacted as `<upstream-archive>`; plus, in interactive mode,
a numbered `missing_details=<list>` block if applicable.

### §5.2 — T02 Validate repos

**Task (Mode A)**: for each of $OP_ROOT_V4, $OP_ROOT_V5, $SUPERCHAIN_OPS,
$CELO_SUPERCHAIN_OPS, $SUCCINCT_ROOT: check branch (`git branch --show-current`),
binary presence (`op-deployer/bin/op-deployer` for v4/v5), forge artifact count,
task directories. SCOps task directories:
- `NETWORK=mainnet`: `$SUPERCHAIN_OPS/src/tasks/eth/048-opcm-upgrade-v410-celo/`
  and `.../eth/049-opcm-upgrade-v500-celo/`
- `NETWORK=sepolia`: `$SUPERCHAIN_OPS/src/tasks/sep/048-opcm-upgrade-v410-celo/`
  and `.../sep/049-opcm-upgrade-v500-celo/` (path is `sep/` at the TOP level of
  `src/tasks/`, NOT under `eth/sep/`)
- `NETWORK=chaos`: no SCOps tasks exist; T02 MUST return a chaos-specific evidence
  line `scops_chaos=N/A`; T18/T24 will SKIP for the same reason.
If artifacts missing → run `forge build` (may take 5-10 min). This is the ONE
exception to "no extra steps" (§7 dispatch rules): artifact bootstrap is explicitly
allowed in T02.

**Succinct build & FDG config validation (Mode A)**:
1. Resolve `$SUCCINCT_REPO` per §3.4 (mainnet → `$SUCCINCT_ROOT_MAINNET`, else →
   `$SUCCINCT_ROOT`). Verify the directory exists and is a git repo.
2. Report `git -C $SUCCINCT_REPO log --oneline -1` and `git -C $SUCCINCT_REPO
   branch --show-current` as evidence (`succinct_commit=<hash>; succinct_branch=<br>`).
3. Check for pre-existing per-network config:
   `$SUCCINCT_REPO/contracts/opsuccinctfdgconfig.$NETWORK.json`. If present, use it
   as `SUCCINCT_CONFIG_FILE`. If absent, check for the generic
   `$SUCCINCT_REPO/contracts/opsuccinctfdgconfig.json`.
4. If neither config exists AND the operator has a `.env.$NETWORK` in the Succinct
   repo: attempt `cd $SUCCINCT_REPO && just fetch-fdg-config .env.$NETWORK` to
   generate it. This may fail if RPC access is not configured — on failure, record
   `fdg_config_generated=false` as evidence and continue (T10 Strategy B
   `setCodeAtAddress` fallback will be used).
5. If a config file is found/generated: extract and report `aggregationVkey` and
   `rangeVkeyCommitment` from it as evidence. Store the config path as
   `SUCCINCT_CONFIG_FILE` in the CONTEXT_BLOCK.
6. Build Succinct contracts: `cd $SUCCINCT_REPO/contracts && forge build`. If build
   fails, record `succinct_build=failed` — T10 will use Strategy B fallback.
7. Report `succinct_build=ok|failed; fdg_config=$SUCCINCT_CONFIG_FILE|missing`.

**Task (Mode B)**: only check `$CELO_ROOT/packages/op-tooling/exec/exec-upgrade.sh`
exists (unified runner for mainnet, sepolia, and chaos) AND
`$CELO_ROOT/secrets/.env.signers.{v4,v5,succinct200}` are decrypted. If
`INCLUDE_BASEFEE=yes`, ALSO check `$CELO_ROOT/secrets/.env.signers.basefee` — this
is the pre-check so Phase 8 does not late-fail at T36.

Tier: `sonnet`.

**Evidence**: per-repo `branch=<x>, binary=<ok|missing|built>, artifacts=<N>` lines;
`scops_tasks=<eth|sep|N/A>`; `exec_script=<file>`; for Mode B: `signers.v4=ok`,
`signers.v5=ok`, `signers.succinct200=ok`, `signers.basefee=ok|N/A`.

### §5.3 — T03 Validate CLI tools

**Task**: `which anvil cast forge jq just mise`. Report missing.
Tier: `haiku`.

**Evidence**: `anvil=<path>; cast=<path>; forge=<path>; jq=<path>; just=<path>; mise=<path>` one per line.

### §5.4 — T04 Start anvil fork

**Task**:
1. `lsof -ti:$RPC_PORT` — if non-empty, FAIL ("port busy; orchestrator must resolve").
2. `anvil --port $RPC_PORT --fork-url "$UPSTREAM_RPC_URL" --fork-chain-id $FORK_CHAIN_ID --fork-block-number $FORK_BLOCK &>/tmp/anvil-$RPC_PORT.log & echo $! > /tmp/anvil-$RPC_PORT.pid`
3. `sleep 3 && cast block-number -r $RPC_URL`
Tier: `haiku`.

**Evidence**: `anvil_pid=<N>; current_block=<matches $FORK_BLOCK ±1>`

### §5.5 — T05 Mock Safe storage (Mode A only; SKIP in Mode B)

**Script selection by network** (the skill does NOT have `mock-chaos.sh` or
`mock-mainnet.sh` symlinks — these are the real script names):
- `NETWORK=mainnet`: `./mock-mainnet.sh`
- `NETWORK=sepolia`: `NETWORK=sepolia ./mock-sepolia.sh`
- `NETWORK=chaos`:   `NETWORK=chaos ./mock-sepolia.sh`   (same script, different env)

**Task (Mode A)**:
```
cd $CELO_ROOT/packages/op-tooling/fork
RPC_URL=$RPC_URL \
NETWORK=$NETWORK \
MOCKED_SIGNER_1=0x865d05C8bB46E7AF16D6Dc99ddfb2e64BBec1345 \
MOCKED_SIGNER_2=0x899a864C6bE2c573a98d8493961F4D4c0F7Dd0CC \
MOCKED_SIGNER_3=0x480C5f2340f9E7A46ee25BAa815105B415a7c2e2 \
MOCKED_SIGNER_4=0x8Af6f11c501c082bD880B3ceC83e6bB249Fa32c9 \
./<script-from-above>
```

**Task (Mode B)**: `STATUS: SKIP` with evidence `SKIP: Mode B keeps real Safes`.
Tier: `haiku`.

**Evidence (Mode A)**: `mock_script=<name>; exit_code=0`
**Evidence (Mode B)**: `SKIP: Mode B keeps real Safes`

### §5.6 — T06 Validate mock state (Mode A only; SKIP in Mode B)

**Task (Mode A, NETWORK ∈ {mainnet, sepolia})**:
```
cast call $PARENT_SAFE  "getThreshold()(uint256)" -r $RPC_URL
cast call $CLABS_SAFE   "getThreshold()(uint256)" -r $RPC_URL
cast call $COUNCIL_SAFE "getThreshold()(uint256)" -r $RPC_URL
cast call $PARENT_SAFE  "getOwners()(address[])"  -r $RPC_URL
cast call $CLABS_SAFE   "getOwners()(address[])"  -r $RPC_URL
cast call $COUNCIL_SAFE "getOwners()(address[])"  -r $RPC_URL
cast call $PARENT_SAFE  "nonce()(uint256)"        -r $RPC_URL
cast call $CLABS_SAFE   "nonce()(uint256)"        -r $RPC_URL
cast call $COUNCIL_SAFE "nonce()(uint256)"        -r $RPC_URL
```
**Task (Mode A, NETWORK=chaos)**: flat Safe — query `getThreshold()`, `getOwners()`,
`nonce()` on the single chaos Safe `0x6F8DB5...66a9` only.
**Task (Mode B)**: `STATUS: SKIP` with evidence `SKIP: Mode B keeps real Safes`.
Tier: `haiku`.

**Evidence (mainnet+sepolia Mode A)**: three thresholds (all `2`), three owner
arrays (match §B.7 mocked defaults), three nonces (match §B.8 row for NETWORK+MODE).
**Evidence (chaos Mode A)**: `threshold=1; owner=<EOA>; nonce=<baseline>`.
**Evidence (Mode B)**: `SKIP: Mode B keeps real Safes`.

### §5.7 — T07 Start x-ray dashboard

**Task**:
1. `lsof -ti:$XRAY_PORT` — if non-empty, FAIL with `xray port $XRAY_PORT busy;
   orchestrator must resolve or override XRAY_PORT in CONTEXT_BLOCK`.
2. Verify the x-ray checkout exists: `[ -d "$XRAY_ROOT" ]` — if missing, FAIL with
   `XRAY_ROOT ($XRAY_ROOT) not found — x-ray now lives in its own repo; clone https://github.com/celo-org/x-ray`.
3. `cd $XRAY_ROOT && python3 -m http.server $XRAY_PORT
   &>/tmp/xray-$RPC_PORT.log & echo $! > /tmp/xray-$RPC_PORT.pid`
4. `sleep 2 && curl -sSf http://localhost:$XRAY_PORT | head -c 200`
Tier: `haiku`.

**Evidence**: `xray_pid=<N>; xray_port=$XRAY_PORT; curl_status=200`.

### §5.8 — T08 Bootstrap v4 OPCM (Mode A only; SKIP in Mode B)

**PRECONDITION**: T06 STATUS=PASS (Mode A) OR T06 STATUS=SKIP (Mode B — then T08 also SKIPs).

**Task (Mode A)**:
```
cd $CELO_ROOT/packages/op-tooling/op-deployer/v4
RPC_URL=$RPC_URL \
NETWORK=$NETWORK \
OP_ROOT=$OP_ROOT_V4 \
MULTISIG_ADDRESS=<§APPENDIX B.3 "Multisig (bootstrap)" row for NETWORK> \
DEPLOYER_PK=<real DEPLOYER_PK, not 0x***> \
./bootstrap.sh
OPCM_V4=$(jq -r '.opcm' $CELO_ROOT/packages/op-tooling/op-deployer/v4/config-upgrade.json)
cast codesize $OPCM_V4 -r $RPC_URL
```
Return `OPCM_V4` as labelled evidence so the orchestrator can mutate CONTEXT_BLOCK
per §3.2.

**Task (Mode B)**: `STATUS: SKIP` with evidence
`SKIP: Mode B — OPC

…(truncated)
