# Devnet governance rehearsal

This runbook uses the recorded mint, Squads multisig, and explicit devnet genesis. It never selects a replacement address. `mainnet-readonly` cannot send. Check [`STATUS.md`](../STATUS.md) before starting.

## Independent signer handoff

Ops-2 and ops-3 currently have private key files on the worker host. Transfer each existing key manually through a trusted offline channel to a **different** host. On each host, place the key outside the repository with mode `0600`, confirm its public key equals the corresponding entry in `config/signers.json`, and install the repository dependencies with `npm ci --prefix scripts/realms` and `npm ci --prefix scripts/squads-js`. Never send the key through a chat, issue, log, CI artifact, or Git.

Use one stable `REALMS_OPERATION_ID` for all retries. On the worker host, generate a public challenge:

```bash
export PROTOCOL_NETWORK_PROFILE=devnet REALMS_OPERATION_ID=devnet-realm-1
mkdir -p .bendingpoors
node scripts/realms/custody.mjs issue > .bendingpoors/custody-challenge.json
```

Copy only the challenge to both hosts. Each signer runs `node scripts/realms/custody.mjs sign /path/challenge.json /absolute/private/key.json > proof.json` and returns only `proof.json`. After checking both public keys and removing ops-2/ops-3 key files from this host, run:

```bash
node scripts/realms/custody.mjs verify .bendingpoors/custody-challenge.json /path/ops2-proof.json /path/ops3-proof.json > .bendingpoors/custody-attestation.json
```

This proves both keys signed the exact challenge and that the usual key files are absent here. It cannot prove deletion of historical copies or backups. Rotate keys before treating custody as suitable for mainnet.

## Realm bootstrap and council deposits

First run `node scripts/realms/compatibility.mjs` and `python -m skills.cli status --verify`. The recorded devnet Realm is currently absent, while the recorded Token-2022 mint and ops-1 bootstrap token are present. The primary fee payer had sufficient devnet SOL at the last check; check again before sending.

```bash
export REALMS_CUSTODY_ATTESTATION="$PWD/.bendingpoors/custody-attestation.json"
bash scripts/tge-realms.sh
REALMS_OPERATION_ID=devnet-realm-1 node scripts/realms/export-signer-manifest.mjs > .bendingpoors/realms-signer-manifest.json
node scripts/realms/verify.mjs
python -m skills.cli status --verify
```

The bootstrap stores signed transactions before broadcast and reconciles each finalized account on retry. Reuse the same operation ID after interruption; a changed genesis, mint, signer list, or name is rejected. The script records the new Realm only after governance and its authority relationship finalize.

Ops-2 and ops-3 currently have zero devnet SOL. After verifying their challenge proofs, fund each public key with only enough SOL for transaction fees (for example, `0.02 SOL`) from ops-1. On each signer host, copy the public Realm manifest and run:

```bash
export PROTOCOL_NETWORK_PROFILE=devnet
export REALMS_MANIFEST=/path/realms-signer-manifest.json
export REALMS_SIGNER_KEY=/absolute/private/key.json
node scripts/realms/deposit-council.mjs
node scripts/realms/deposit-council.mjs  # must report already_deposited
```

Do not transfer the council token back to the worker host. The treasury membership bag is never deposited into Realms.

## Proposal, external votes, execution, and Squads split

On the worker host, create one council proposal and export its public manifest:

```bash
export REALMS_CYCLE_ID=devnet-cycle-1
export REALMS_MANIFEST="$PWD/.bendingpoors/realms-signer-manifest.json"
node scripts/realms/governance-cycle.mjs prepare > .bendingpoors/cycle-manifest.json
export REALMS_CYCLE_MANIFEST="$PWD/.bendingpoors/cycle-manifest.json"
```

Copy both public manifests to the two signer hosts. Each host sets `REALMS_CYCLE_MANIFEST`, `REALMS_MANIFEST`, `REALMS_CYCLE_ID`, and its own `REALMS_SIGNER_KEY`, then runs `node scripts/realms/governance-cycle.mjs vote`. Both votes must finalize. Back on the worker host, run `governance-cycle.mjs status` and `governance-cycle.mjs execute` with the same manifest variables. The public governance configuration has a 24-hour instruction hold-up; `execute` reports `waiting_hold_up` until the chain clock allows execution. Re-run it then and verify `completed` and the treasury balance delta.

For the Squads proof, propose a **1 USDC devnet** split with a unique operation ID, then export the exact public approval intent:

```bash
python -m skills.cli split --amount 1 --propose --operation-id devnet-split-1
python -m skills.cli ops run --once
python -m skills.cli ops manifest devnet-split-1 > .bendingpoors/split-approval.json
```

On one external host, set `SQUADS_SIGNER_KEY` and `SQUADS_APPROVAL_MANIFEST` to its private key and the copied public manifest, then run `node scripts/squads-js/approve-external.mjs`. It checks the on-chain message hash and every transfer before signing. Repeat once to verify `already_approved`. Back on the worker host, run `python -m skills.cli ops run --once` until `python -m skills.cli ops show devnet-split-1` reports `attested`; compare finalized balances and the posted memo. Reusing the same operation ID must not create another split.

## Remaining gates

Inventory deployment and swaps remain blocked until a selected cell has a verified on-chain oracle feed and adversarial CPI tests. Raydium, Streamflow, privacy payments, and other provider paths need their own devnet rehearsals. No mainnet send profile is configured.
