type: meta

slug: cluster-source-of-truth

base_branch: origin/main

gate0_mode: strict

preset: cursor-codex


type: goal

text: Make overdeck Git the single declared source of truth for one four-node fleet (workstation, debian1, debian2, debian3), converged by push from the workstation, with the workstation eligible to run builds or agents only under a controller-issued last-resort lease.


type: methodology

text: Contract-first: one fleet declaration, one convergence interface over local and ssh transports, idempotent audit/converge/harden. Delete the legacy capacity-spill path rather than adapt it. Land each wave through ship.sh; verify on live hosts, never only in tests.


type: intent

text: Tools, settings, CLAUDE.md/AGENTS.md and runtimes drifted per box because each was configured by hand; and the workstation silently absorbed build spill under load, which is exactly when it must stay free. One declaration and one lease make both impossible.


type: direction

verbatim: i basically want all 3 to have identical setup, the only difference between the 3 should be the number


verbatim: everything needs to be with one source of truth, and i guess it is overdeck?


verbatim: never synchronize one builder from another


verbatim: workstation is disabled unless debian1/debian2/debian3 are all down


verbatim: New key, legacy path stays dead


type: anchor

what: second-disk design, landed; debian1 converged, debian2/3 pending host return


note: Landing is ship.sh land <branch> <worktree> from /home/user/Projects/overdeck; raw pushes to main are rejected by the harness pre-push guard.


note: debian2 and debian3 were offline for disk upgrades on 2026-08-06; a 17-minute cron re-probes and hardens them when they answer on port 2222.


note: debian2/debian3 lost sshd on the boot after the extra disk went in: ip_nonlocal_bind was not yet set and Debian's ssh.service lists exit 255 as unrecoverable, so the failed ListenAddress bind was permanent. Only debian1 carried a hand-installed drop-in clearing RestartPreventExitStatus. Fixed in Git 2026-08-07 and proven on debian1; the two dark boxes need one power-cycle.


type: task

seat: coder

tier: high

desc: Read docs/plans/2026-08-06-cluster-source-of-truth.md Task 1 (Fleet declaration, schema and loader) for the full contract. Add spec/fleet.schema.json, modules/fleet/fleet.json, the module descriptor modules/fleet/deck.module.json, the DesiredItem/Transport JSDoc typedefs in lib/fleet/types.mjs, and the sole validating loader lib/fleet/loader.mjs with its query interface. Acceptance: node --test modules/fleet/test/loader.test.mjs and bash tests/os/run-all.sh. Commit only: spec/fleet.schema.json modules/fleet/fleet.json modules/fleet/deck.module.json lib/fleet/types.mjs lib/fleet/loader.mjs modules/fleet/test/loader.test.mjs modules/fleet/test/node.test.sh.

acceptance: node --test modules/fleet/test/loader.test.mjs passes


tier: medium

desc: Read docs/plans/2026-08-06-cluster-source-of-truth.md Task 2 (Profile expansion) for the full contract. Expand a node into ordered DesiredItems composed from existing manifests and devtools.json, never a second inventory. Acceptance: node --test modules/fleet/test/expand.test.mjs. Commit only: lib/fleet/expand.mjs modules/fleet/test/expand.test.mjs.

acceptance: node --test modules/fleet/test/expand.test.mjs passes


desc: Read docs/plans/2026-08-06-cluster-source-of-truth.md Task 3 (Local transport) for the full contract. Implement the Transport interface over the workstation filesystem and user systemd with staged atomic promote. Acceptance: node --test modules/fleet/test/transport-local.test.mjs. Commit only: lib/fleet/transport-local.mjs modules/fleet/test/transport-local.test.mjs.

acceptance: node --test modules/fleet/test/transport-local.test.mjs passes


desc: Read docs/plans/2026-08-06-cluster-source-of-truth.md Task 4 (SSH transport) for the full contract. Implement the Transport interface over the existing buildbox SSH path, resolving connection params from the buildbox registry. Acceptance: node --test modules/fleet/test/transport-ssh.test.mjs. Commit only: lib/fleet/transport-ssh.mjs modules/fleet/test/transport-ssh.test.mjs.

acceptance: node --test modules/fleet/test/transport-ssh.test.mjs passes


desc: Read docs/plans/2026-08-06-cluster-source-of-truth.md Task 5 (Shared audit/converge engine) for the full contract. Audit six fields and converge only drifted fleet-owned items; delegate agent-home items to remote-seat-provision.mjs and emit the parity report. Acceptance: node --test modules/fleet/test/engine.test.mjs. Commit only: lib/fleet/engine.mjs modules/fleet/test/engine.test.mjs docs/reports/fleet-agent-home-parity.md.

acceptance: node --test modules/fleet/test/engine.test.mjs passes


desc: Read docs/plans/2026-08-06-cluster-source-of-truth.md Task 6 (Hardening path) for the full contract. Root convergence for exactly one SSH buildbox node with reboot verification and last-known-good restore. Acceptance: node --test modules/fleet/test/harden.test.mjs. Commit only: lib/fleet/harden.mjs modules/fleet/test/harden.test.mjs.

acceptance: node --test modules/fleet/test/harden.test.mjs passes


desc: Read docs/plans/2026-08-06-cluster-source-of-truth.md Task 7 (deckctl fleet command) for the full contract. Add the fleet audit/converge/harden/status surface behind the existing deckctl dispatcher contract; add fleet to BOTH the usage() string on bin/deckctl line 8 and the case allow-list on line 23. Acceptance: bash modules/fleet/test/cli.test.sh. Commit only: bin/deckctl lib/deckctl/fleet.sh modules/fleet/test/cli.test.sh.


desc: Read docs/plans/2026-08-06-cluster-source-of-truth.md Task 8 (Convert deckctl sync apply into a fleet adapter) for the full contract. One mapping row only: convergeNode over the agent-home items of expandNode(fleet,'workstation'), wrapped by the existing sync_assert_not_denied / sync_backup_target / sync_record_baseline / sync_secret_scan layers, which must still run. Acceptance: bash modules/fleet/test/adapters.test.sh && bash tests/os/run-all.sh. Commit only: lib/deckctl/sync.sh tests/os/deckctl-sync.test.sh modules/fleet/test/adapters.test.sh.


desc: Read docs/plans/2026-08-06-cluster-source-of-truth.md Task 8b (Add engine audit coverage alongside the existing buildbox audit|bootstrap checks) for the full contract. Additive only: every pre-existing check stays, an auditNode/convergeNode pass is added, engine drift alone exits nonzero. Acceptance: bash modules/fleet/test/adapters-audit.test.sh && bash tests/os/run-all.sh. Commit only: modules/buildbox/bin/buildbox modules/fleet/test/adapters-audit.test.sh.


desc: Read docs/plans/2026-08-06-cluster-source-of-truth.md Task 8c (Add hardenNode alongside the existing buildbox harden apply path) for the full contract. Additive only: apply.sh shipping stays, hardenNode runs inside the armed-deadman window before the reboot, zero selected items is a hard error. Acceptance: bash modules/fleet/test/adapters-harden.test.sh && bash tests/os/run-all.sh. Commit only: modules/buildbox/bin/buildbox modules/fleet/test/adapters-harden.test.sh.


desc: Read docs/plans/2026-08-06-cluster-source-of-truth.md Task 9 (Move hosts out of build-remote.json) for the full contract. Host list becomes fleet.json only; the two real consumers of the hosts key (lib/deckctl/agents.sh, modules/workstation/claude/lib/remote-build.mjs) route through the loader, the four fixtures carrying a hosts key are updated, and a leftover hosts key fails the audit. cpu-guard.sh, remote-seat.mjs, remote-seat.sh, buildbox-checks.sh and host-config/apply.sh mention build-remote.json but read no hosts key and are OUT of scope. Acceptance: bash modules/fleet/test/hosts-migration.test.sh plus the repo os suite. Commit only: modules/fleet/fleet.json lib/fleet/loader.mjs lib/deckctl/agents.sh modules/workstation/claude/lib/remote-build.mjs modules/workstation/claude/tests/local-gate-requeue.test.sh modules/workstation/claude/tests/buildbox-registry.test.mjs modules/workstation/claude/tests/remote-build-transport.test.sh modules/harness/seat/test/seat-contract.test.sh modules/fleet/test/hosts-migration.test.sh.


note: Prerequisites 0a/0b/0c are all landed and verified on 2026-08-07: second disk on debian1/2/3, zero-drift buildbox audit across all three, and lib/deckctl/system-units.sh is opt-in (declared paths only, dies on an undeclared systemd/system tree). The earlier note saying debian2/3 were pending host return is stale.


note: fleet harden reboots the target. Gate it per host: the sshd RestartPreventExitStatus drop-in must be present on THAT host and that host must already have survived one power-cycle since it landed. debian1 proof does not transfer to debian2/debian3. Headless boxes, sshd on 2222 is the only door.