Status: stub

# Runbook: Tracker upgrade path (rolling restart)

Covers restarting/upgrading tracker processes in a hive without losing
strike/ban/reputation state or interrupting settlement, per the ADR-0016 §4
guarantee that reputation carries forward across restarts.

## Trust assumptions (read first)

Per [ADR-0016](../../../docs/adr/0016-alpha-scope-and-known-limitations.md),
only one operator-designated tracker holds the treasury keypair and runs the
settlement loop ([ADR-0015](../../../docs/adr/0015-usdt-custodial-settlement.md));
other hive members replicate for routing only. Raft (`packages/tracker/meshnet_tracker/raft.py`)
elects a leader for shard-assignment/registration commands — settlement
leadership is a separate, operator-configured concept, not the Raft leader.
Plan restarts so the settlement tracker's downtime window is minimized
independent of routing-tracker restarts.

## Known gap — read before relying on this runbook

Strike/ban/reputation persistence itself was implemented in issue 05
(`packages/contracts/meshnet_contracts/__init__.py::RegistryEventLog`,
SQLite-backed, same pattern as billing/accounts). **As of this writing,
`packages/tracker/meshnet_tracker/cli.py` does not expose a `--registry-db`
flag, nor does it construct a `contracts=` instance to pass into
`TrackerServer`.** Running the tracker via the stock CLI entry point leaves
`server.contracts` as `None`, which means:

- Ban checks (`_registration_ban_error`), reputation-weighted routing
  (`_reputation_multiplier`), and the `/v1/registry/wallets` endpoint are
  inert.
- There is nothing to persist across restarts in that configuration — the
  "survives restart" guarantee only holds for deployments that construct
  `LocalSolanaContracts(registry_db=<path>)` and wire it into `TrackerServer(contracts=...)`
  themselves (e.g. a custom entrypoint or embedding the server programmatically).

Before following the restart steps below, confirm which mode this deployment
runs in. If it's the stock CLI with no custom `contracts` wiring, strike/ban
state is RAM-only regardless of this runbook, and a restart resets it — treat
that as a pre-existing gap to flag to the owner, not something this runbook
can work around.

## Prerequisites

- Confirm registry persistence is actually wired (see gap above) and note the
  registry DB path in use.
- Confirm billing (`--billing-db`) and accounts (`--accounts-db`) persistence
  paths — these already default to `billing.sqlite` / `accounts.sqlite` and
  persist regardless of the registry gap.
- Know which tracker in the hive is currently the settlement leader (holds
  `--treasury-keypair`) versus routing-only peers.
- `--hive-secret` / `MESHNET_HIVE_SECRET` configured identically across all
  hive members (ADR-0017) — a mismatched secret on restart fails gossip
  closed, not open.
- Take a [ledger backup](01-ledger-backup.md) before any upgrade that touches
  schema or dependency versions.

## Steps

1. **Routing-only trackers first.** For each non-settlement tracker in the
   hive:
   a. Confirm it's not the current Raft leader (`GET /v1/raft/status`); if it
      is, this restart forces a re-election — acceptable, but expect a brief
      registration-proxy gap while a new leader is elected.
   b. Stop the process, deploy the new code/config, restart with the same
      `--billing-db` / `--accounts-db` / registry DB paths and the same
      `--hive-secret` and `--cluster-peers`.
   c. Check `/v1/raft/status` and `/v1/registry/wallets` (if registry is
      wired) come back consistent with peers within one gossip interval.
   d. Move to the next routing tracker only after this one rejoins cleanly.
2. **Settlement tracker last**, and only during a low-settlement-activity
   window if possible:
   a. Confirm no payout is mid-flight (check tracker logs / pending balance
      levels against `--settle-period` / `--payout-threshold`).
   b. Stop the process. The treasury keypair file itself is untouched by the
      restart — do not regenerate it (see
      [treasury key rotation](02-treasury-key-rotation.md) for that separate
      procedure).
   c. Deploy new code/config, restart with identical `--treasury-keypair`,
      `--solana-rpc-url`, `--usdt-mint`, `--settle-period`,
      `--payout-threshold`, `--payout-dust-floor`, and DB paths.
   d. Verify strike/ban/reputation state (if wired) matches pre-restart values
      via `/v1/registry/wallets`, and that billing/accounts ledgers show the
      same balances as immediately before shutdown.
3. Confirm all hive members show each other as alive peers and gossip
   (`/v1/registry/gossip`, `/v1/billing/gossip`, `/v1/accounts/gossip`) is
   flowing without HMAC auth failures in logs (ADR-0017).

## Rollback

- Each tracker's on-disk SQLite files are untouched by a code-only upgrade;
  rolling back means redeploying the previous binary/version against the same
  DB paths. Because billing/accounts/registry are append-only event logs, a
  version rollback does not lose data written by the newer version as long as
  the schema didn't change — if the upgrade included a schema migration,
  restore from the pre-upgrade [ledger backup](01-ledger-backup.md) instead.
- If a settlement-tracker restart leaves it unable to reach the treasury RPC
  endpoint, routing-only trackers continue serving traffic — settlement simply
  pauses until the leader recovers; no funds are at risk since payouts require
  the loaded keypair.

## Secrets handling

- Never commit `.env.devnet`, `--hive-secret` / `MESHNET_HIVE_SECRET`,
  `--validator-service-token`, or the treasury keypair file as part of a
  deploy/config change. Deploy scripts should read these from the existing
  secrets store, not from a file checked into the repo.
