Fleet¶
CLI overview · Router commands
The fleet family answers questions that span more than one declared host.
fleet version is cross-host anvil-serving version skew; fleet drift is
live-home-vs-repo-snapshot skew.
Workloads¶
Read a bounded canonical fleet snapshot through one explicit controller aggregator:
anvil-serving fleet workloads --controller-url http://127.0.0.1:8765 --auth-env ANVIL_WORKLOAD_TOKEN --expected-node controller-a --recent-seconds 3600 --limit 200
anvil-serving fleet workloads --controller-url http://127.0.0.1:8765 --auth-env ANVIL_WORKLOAD_TOKEN --expected-node controller-a --owner media --active-only --json
This command does not collect topology locally, use SSH, or treat an
unreachable node as idle. The controller identity must match
--expected-node, and the client credential must carry workloads:read.
--host filters returned records rather than selecting the aggregator. See
workload visibility for query bounds, node/fleet
envelopes, source provenance, and honest partiality.
Fleet version¶
Without --host, hosts come from the operator topology (default
$ANVIL_SERVING_HOME/operator-topology.toml): every declared [[hosts]] id
except the local one, determined by a case-insensitive prefix match against
socket.gethostname(). Pass --host NAME (repeatable) to name hosts
explicitly instead, overriding the topology-derived list.
For each remote host it runs ssh -n -o BatchMode=yes <host> anvil-serving
--version (per-host timeout --timeout, default 10s) and reports:
| State | Meaning |
|---|---|
ok |
Version read successfully; compared against the local version. |
unreachable |
SSH failed to connect or the command otherwise errored. |
not-installed |
SSH connected but anvil-serving is not on that host's PATH. |
timeout |
The probe did not complete within --timeout. |
--json emits the same report structurally.
This exists because on 2026-08-08 a fleet host ran a release two minors behind the operator host; the older code resolved transports differently and produced an error naming the wrong cause. See Strategy: make divergence loud and the fleet operator persona.
Exit codes: unreachable is not skew¶
fleet version exits 1 when any reachable host reports a different
version, or any host is reachable but missing the CLI (not-installed). It
exits 0 when every discrepancy is unreachable or timeout — a sleeping
laptop is an availability gap, not proof of divergent code (the strategy
doc's availability-class reasoning: detect divergence, don't conflate it with
downtime). With no remote hosts declared at all, it prints the local version
and exits 0.
Drift¶
--repo is required — the private operator repository root (ADR-0032). The
tool never guesses it. Without --host, hosts are every directory under
PATH/hosts/ that contains an operator-home/; pass --host NAME
(repeatable) to compare only named hosts, each of which must exist as a repo
snapshot or the command exits 2.
For each host, fleet drift compares PATH/hosts/<host>/operator-home/
against that host's live operator home, file by file, content-based
(sha256):
| Status | Meaning |
|---|---|
identical |
Live bytes match the repo snapshot (newline-normalized). |
differs |
Live bytes differ from the repo snapshot. |
missing-live |
The repo-tracked file does not exist in the live home. |
The LOCAL host (hostname prefix-match, same rule fleet version uses) is
read directly: --home if given, else $ANVIL_SERVING_HOME, else the
platform default config home (paths.config_home()). Every other declared
host is REMOTE and is probed with one ssh -n -o BatchMode=yes <host>
python3 -c "..." call per host (per-host timeout --timeout, default 10s):
a small embedded script hashes exactly the repo-tracked filenames it is
given and prints {name: sha256|null} as JSON — one round trip per host,
not one per file. An unreachable or timed-out host reports that state, not
drift, and does not fail the gate (same availability-class reasoning as
fleet version: a sleeping host is not evidence of divergent config).
Comparison is newline-normalized (\r\n treated the same as \n) because a
Windows checkout of the repo snapshot must not read as "differs" against a
POSIX live home, or vice versa, when the underlying content is identical.
Only repo-tracked files are ever compared, listed, or read. A live home
routinely holds files the repo snapshot never will — .env, backups, lock
files — and those are expected, not findings; fleet drift never lists the
live directory and never opens a file whose name isn't already in the repo
snapshot, so a credential file such as .env is never read, locally or
over SSH, under any circumstance.
--json emits the same report structurally: per host, per-file path /
status, plus compared / differs / missing counts.
Exit codes¶
fleet drift exits 1 when any compared file is differs or
missing-live on a reachable host. It exits 0 when every host is
either fully identical or unreachable.
This exists because on 2026-08-08 a host's live operator home was found six commits behind its repository snapshot while serving production, and a second host's live home was a wholesale byte-copy of the wrong host's home — both undetected until someone happened to SSH in and look. Feature 7 of Strategy: make divergence loud, ADR-0034 §9 ("Operator home: git as record, materialized for deployment").