Conversation
…tate, and write the --no-check-sigs note (#354) Item 4 of #354's 2026-09-14 status comment, and the two CLI answers beside it that read as "fine" when they are not. 1. `status` exits 1 when it cannot read what it is reporting on. /var/lib/update-adopt is 0700 root, so a run as tom read nothing and printed load_state()'s defaults — `"state": "idle"`, every other field null, exit 0 — which is indistinguishable from a healthy host that has nothing staged. It now prints the facts /run and /nix still give it, sets `state_unreadable`, leaves the state-derived fields null, and exits 1. Nothing staged yet on a readable directory is still a real answer and still exits 0. fleet-status already collects every host as root over ssh (modules/fleet-status.nix); if it ever ran the verb unprivileged it would now grade the answer unknown instead of "up to date". 2. A 404 manifest is `no-candidate`, not `fetch-failed`. curl gets `-w %{http_code}`, so "the NAS answered, with nothing published for this host" and "the NAS did not answer" are different journal lines. Both still exit 0: neither is this host's failure. MEASURED against the live NAS — an unpublished host is rc 22 / 404, a real one rc 0 / 200, an unreachable endpoint rc 7 / 000. 3. docs/fleet-candidate-adoption.md writes down when `--no-check-sigs` on the `nix copy` publish step is legitimate: a locally built closure is unsigned, the receiving daemon honours the flag only for a trusted user (modules/common.nix), and the trust in that transfer is the SSH channel and who built the bytes. It also names the three places the flag is never legitimate, starting with substitution from the fleet cache, where the signing key is the whole proof. tests/update-adopt gains three cases (404 -> no-candidate, an unreadable state directory -> rc 1, no state yet -> rc 0) and its fake curl now models a 404 and an unreachable NAS separately. 33 tests pass, in-tree and through `nix build .#checks.x86_64-linux.update-adopt`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Open
22 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Backlog row FDC-M3. Closes item 4 of #354's 2026-09-14 status comment ("the
nix copypublish step needs--no-check-sigs; a 404 manifest logsfetch-failedwhereno-candidatewould be clearer") and the two neighbouring CLI answers that read as "fine" when they are not. #354 stays open: the remaining items there are live-adoption milestones, not this.The three behaviours
1.
update-adopt statusas a non-root user exits non-zero./var/lib/update-adoptis 0700 root. A run as tom read nothing and printedload_state()'s defaults —"state": "idle", every other field null, exit 0 — which is exactly what a healthy host with nothing staged prints. It now reports the facts it can still see (current,booted,profile, the running generation's revision), sets the newstate_unreadablefield, leavesstate/candidate/last_refusal/last_known_good/pending_rebootatnull, and exits 1 with a line on stderr saying to run it as root. Nothing staged yet on a directory this user can read is still a real answer and still exits 0.Verified live on the coordinator, as tom, against the real 0700 state directory:
modules/fleet-status.nixalready collects every host, the coordinator included, as root over ssh precisely because this state is root-only; if the verb is ever run unprivileged,c_updatesnow grades the answerunknowninstead of "up to date".2. A 404 manifest logs
no-candidate, notfetch-failed.curlgets-w %{http_code}, so "the NAS answered, and has published nothing for this host" is told apart from "the NAS did not answer at all". Both still exit 0 — neither is this host's failure — but the journal line an operator reads now names the right one, and the refusal detail carries the URL and the code.Verified against the live NAS publisher (
http://nas:8734/candidates/…): an unpublished host isrc 22 / http_code 404, a published hostrc 0 / 200, an unreachable endpointrc 7 / 000. Verified hermetically bytest_missing_manifest_is_no_candidate, withtest_unreachable_nas_is_not_a_failureholding the other half of the distinction.3.
--no-check-sigshas a runbook note.New
docs/fleet-candidate-adoption.md(indexed indocs/README.md) covers the hand-publication path —nix copy --to ssh-ng://root@nas --no-check-sigs <closure>thenupdate-center --publish-only HOST PATH FLAKEREF— and says why the flag belongs there: a locally built path is unsigned because signing happens on the way into a cache, the receiving daemon honours the flag only for a trusted user (trusted-users = [ "root" "@wheel" ],modules/common.nix), and the trust in that transfer is the authenticated SSH channel plus who built the bytes, not a signature. It also names the three places the flag is never legitimate — substituting fromhttp://nas:8080/fleet, where the signing key is the whole proof; silencing a signature error whose cause is unknown; and any unit or timer, withhome/update-center-seed.nixas the counter-example that needs no flag because its paths are narHash-addressed. Adoption's own trust (thessh-keygen -Ymanifest signature, the store-path shape check, and re-reading the revision from the closure's ownfleet-revision.json) is unchanged.Tests
Three cases added to the existing hermetic suite in
tests/update-adopt/test_update_adopt.py; its fakecurlnow models a 404 and an unreachable NAS separately, instead of collapsing both into exit 22.nix flake checkdoes not pass, on this branch or onmain:checks.x86_64-linux.fleet-connectivityfails evaluation on an unrelated model-roster assertion (coordinator.services.local-models.artifacts, 11 entries against 9 — the twoopenwakeword-*rows andqwen3-tts-tokenizer-f32). Confirmed pre-existing by evaluating that check straight frommain. The targeted check for this change was built instead, above.Nothing here was deployed: no
nixos-rebuild, nohome-manager switch. The liveupdate-adopton the coordinator is still the old build; the exit-code change reaches it at the next switch.🤖 Generated with Claude Code