Runbooks
Concrete, ordered procedures for the operations you run on a live deployment. They assume a node started as:
merod --home data/ --node node1 runso its home directory is data/node1/, holding config.toml, the RocksDB
datastore (data/node1/data/), and the blob store (data/node1/blobs/).
Upgrade a node binary
Section titled “Upgrade a node binary”Replacing the merod binary in place. Because a node that runs an older
binary safely buffers newer-schema deltas in its absorb buffer (see
stale-schema deltas),
a rolling upgrade across a fleet is tolerated — but each node still needs a
clean stop/replace/restart.
-
Confirm the running build. Scrape
merod_build_info(the constant-1beacon carriesversionandpeer_id) so you know what you are replacing:Terminal window curl -s http://127.0.0.1:2528/metrics | grep merod_build_info -
Quiesce client traffic. Stop new JSON-RPC / admin calls against this node at your load balancer or proxy. There is no in-process “drain” command; draining means cutting client traffic upstream and letting in-flight syncs settle.
-
Stop the node. Send
SIGINT/SIGTERMto themerodprocess (e.g.Ctrl-C,systemctl stop, ordocker stop) and wait for it to exit. Do notkill -9unless it hangs — an abrupt kill risks leaving RocksDB to recover on next start. -
Replace the binary. Install the new
merod(andmeroctl) over the old one. The home directory is untouched. -
Restart. Start the node with the same home and node name:
Terminal window merod --home data/ --node node1 run -
Verify. Confirm the new version is live and the node re-converges:
Terminal window curl -s http://127.0.0.1:2528/metrics | grep merod_build_infomeroctl --node node1 peersWatch
governance_pending_queue_depthand any absorbed-delta counters drain toward zero — an upgraded reader can now decode what the old binary buffered.
Recover a stuck upgrade
Section titled “Recover a stuck upgrade”Use this when an application upgrade (a new bytecode version) does not finish on a node: a context stays on its old code, or a peer never reports as migrated. The recovery walks the node-local migration & recovery markers — inspect what they say, request a resync, then verify they clear.
The endpoints below are on the loopback HTTP server (default 127.0.0.1:2528);
reach them the same way you reach the rest of the admin API for your auth setup.
-
Inspect — find the stuck members. The migration-status rollup is the operator-facing “have all peers migrated?” view. It snapshots each member’s migration heartbeat, including any failure:
Terminal window curl -s http://127.0.0.1:2528/admin-api/groups/<namespace_id>/migration-statusRead
rollup.allMigrated(the go/no-go) androllup.failed. Any member whosemigrationFailedis set names why it stalled — these map directly onto theContextMigrationFailedmarker:migrationFailedvalueWhat happened check_abortedThe app’s migration check rejected the upgrade; the migrate was rolled back (zero residue). apply_failedThe migrate apply itself errored (e.g. the target wasm would not run). no_migration_pathThe context cannot replay its next upgrade rung — the intermediate bytecode is unobtainable. Recovery is a resync (below) or reinstalling the missing version. Under the hood, a context that aborted its migrate is pinned to its pre-upgrade code by
ContextExecutingBlob, whileContextActivatedBloblags the group’s current app key — that gap is the “running old code” symptom. See the marker reference for what each column means. -
Decide the recovery. A
check_aborted/apply_failedmember usually recovers by re-driving the upgrade once the underlying cause is fixed (a bad migration function, a missing dependency). Ano_migration_path(stranded) context cannot replay its ladder locally — its recovery is a full-state resync from a healthy peer that already holds the upgraded state. -
Request the resync. This sets the
ContextResyncRequestedmarker and pulls a fresh snapshot, overwriting local state for that context:Terminal window curl -s -X POST http://127.0.0.1:2528/admin-api/contexts/<context_id>/resync \-H 'content-type: application/json' \-d '{"force": false}'A successful call returns
{"contextId": "...", "resyncStarted": true}. -
Verify the markers clear. Re-run the migration-status rollup. As the snapshot lands,
ContextResyncRequestedis cleared on completion,ContextActivatedBlobadvances to the group’s current app key (so the context now runs the new code), and theContextMigrationFailedmarker self-clears — the member should move out offailedtowardmigrated:Terminal window curl -s http://127.0.0.1:2528/admin-api/groups/<namespace_id>/migration-statusrollup.allMigrated == truemeans the cohort converged. If a member is stillfailedafter the resync settles, the cause is upstream (the upgraded state isn’t actually available on the peers you resynced from) — fix that before retrying.
Back up & restore the datastore
Section titled “Back up & restore the datastore”The datastore (RocksDB) and blob store are plain directories under the node home. A consistent backup is a cold copy taken while the node is stopped.
Back up
Section titled “Back up”-
Stop the node (
SIGINT/SIGTERM, wait for clean exit) so RocksDB flushes and releases its lock. -
Copy the whole node home. This captures config, identity keypair, datastore, and blobs together — they must be restored as a set:
Terminal window tar czf node1-backup-$(date +%Y%m%d).tar.gz -C data node1 -
Restart the node and confirm it is healthy (
meroctl --node node1 peers).
Restore
Section titled “Restore”-
Stop the node if it is running.
-
Replace the home directory with the backed-up copy (keep the original aside until you have verified the restore):
Terminal window mv data/node1 data/node1.brokentar xzf node1-backup-YYYYMMDD.tar.gz -C data -
Restart with the same
--home/--node:Terminal window merod --home data/ --node node1 run -
Let it re-converge. A restored node may be behind the live group; the reconciler and sync will catch it up from peers once it reconnects.
Recover a context from divergence
Section titled “Recover a context from divergence”Use this when a context will not converge on its own — a scope_root / root-hash
mismatch the reconciler keeps reporting, sync_verification_failures_total
incrementing, or snapshot selection climbing without resolution. Escalate from
gentlest to most forceful.
-
Confirm divergence, don’t guess. Check the sync counters (Observability) and logs for the context before acting. Transient lag after a partition is normal and self-heals.
-
Force a targeted sync. This is the first, non-destructive lever:
Terminal window meroctl --node node1 context sync --context <context_id> -
Drive governance convergence if the deltas are buffered awaiting authorisation (growing
governance_pending_queue_depth). Sync the owning group’s state so the governance DAG catches up:Terminal window meroctl --node node1 group sync <group_id> -
Sync everything if multiple contexts drifted (e.g. after a long partition):
Terminal window meroctl --node node1 context sync --all -
Last resort — force a clean resync from peers. If the local copy is genuinely corrupt and will not reconcile, opt the node out of the context and re-join so it pulls a fresh snapshot from a healthy peer:
Terminal window meroctl --node node1 context leave-context <context_id># then rejoin via the owning group / namespacemeroctl --node node1 group join-context <context_id>
Rotate & leave safely
Section titled “Rotate & leave safely”Removing this node (or identity) from a group, namespace, or context in a way that the rest of the fleet records correctly.
Publishes a MemberLeft operation to the group DAG, so peers record the
departure:
meroctl --node node1 group leave <group_id>A namespace is a root group; leaving cascades through its descendant groups:
meroctl --node node1 namespace leave <namespace_id>Local-only opt-out (no DAG op) — drop this node’s copy of a context without leaving the group:
meroctl --node node1 context leave-context <context_id>Signing keys
Section titled “Signing keys”A node holds ONE signing key, minted at merod init and used in every
namespace it takes part in. There is nothing to register and nothing
per-group to rotate: the key identifies the node, and the thing that
actually turns over on a membership change is the group’s encryption
key, which the rotation pipeline re-keys without the departed account.