Poll the release manifest: fetch, verify, cache, on a timer - #398
Merged
Conversation
The manifest reader had no clock and no network, so nothing called it. Now an appliance box discovers a release on its own — the first part of the update design that needs no human to type anything. Poll fetches the manifest and its signature, verifies, parses and caches. Run polls once immediately, then on a jittered hourly tick. The jitter is not in the spec: boxes cluster after a provider window or a regional power cut, and an exact hour boundary would turn that into a spike on a static file host. A failed poll changes nothing on disk. That is the promise the spec makes for an offline box, and seven table cases hold it. Verification happens before the cache is written, so anyone who can answer an HTTP request cannot replace what an offline box acts on. Keys are stamped at build time like internal/version, empty by default, with no environment override: changing which releases a box accepts should take a new apt-signed binary, not an edit to a unit file. A build with no key does not poll at all, which is every build today. Appliance only. A hosted box takes its target from the cloud, so it gets a no-op with the same name. The poll starts last, after the socket is serving and the brain is launched, so a hanging CDN sits in front of nothing. Closes #397 Claude-Session: https://claude.ai/code/session_01DpJrjCXiQygMgNsw2AqH25
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains.
|
| Filename | Overview |
|---|---|
| internal/hostagent/relmanifest/poll.go | Adds the timed fetch, verification, state tracking, bounded-body handling, and cache-update workflow; no eligible follow-up finding remains. |
| internal/hostagent/relmanifest/keys.go | Parses build-stamped minisign public keys while safely skipping malformed entries. |
| cmd/host-agent-real/releasepoll_appliance.go | Wires cache loading and background polling into appliance startup with cancellation and HTTP timeouts. |
| cmd/host-agent-real/releasepoll_hosted.go | Keeps release-manifest polling disabled for hosted builds. |
| cmd/host-agent-real/main.go | Starts polling after brain launch and registers cleanup on normal and explicit exit paths. |
| internal/hostagent/relmanifest/poll_test.go | Exercises polling success, failure preservation, size limits, startup cache loading, timing, keyless behavior, and cancellation. |
| Makefile | Adds the release-manifest trust-key value to linker flags with an intentionally empty default. |
Reviews (2): Last reviewed commit: "Review notes: Poll refuses without a ver..." | Re-trigger Greptile
…ields Two notes from review. Run refuses to start without keys, but Poll is exported and callable on its own, where a zero-value Poller would panic on a nil verifier. It now returns ErrNoKeys, the same refusal a keyless build gives. CLAUDE.md asks for new recurring slog fields to be listed, and this slice added five: brain, ui, minimum_host_agent, keys, state_dir. Claude-Session: https://claude.ai/code/session_01DpJrjCXiQygMgNsw2AqH25
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.
Closes #397.
#396 built the pure half — verify, parse, decide, cache — and nothing called it. This gives it a clock and a network. After this an appliance box discovers on its own that a newer release exists, which is the first thing in the update design that does not need a human to type something.
A failed poll changes nothing on disk. That is the promise
RELEASE_MANIFEST.md# Failure modes makes for an offline box, and it is the thing in this slice most worth getting right: unreachable host, HTTP 500, truncated body, tampered manifest, a signature from a key this box does not accept, wrong schema — seven table cases, each caching a good manifest first and then breaking the CDN one way.Verify before cache, not after, or anyone who can answer an HTTP request could replace what an offline box acts on.
Keys are stamped at build time (
-X ...relmanifest.BakedKeys=...), empty by default, and there is no environment override on purpose: changing which releases a box accepts should take a new binary that arrives through apt and is itself signed, not an edit to a unit file. A build with no key does not poll at all — inert rather than credulous, and it reads in the journal as "this build trusts nobody" instead of "no releases published".Appliance only. A hosted box's target is held per box by the cloud (
UPDATES.md# 8.1), so it gets a no-op twin andmain.gocarries no build tags. The poll starts last, after the socket is serving and the brain is launched, so a hanging CDN sits in front of nothing.Two things not in the spec, both now recorded there as "as built": jitter of up to five minutes per tick (boxes cluster after a provider window or a regional power cut), and a 64 KiB body cap.
A mutation check caught a bad test, which is why they are worth doing. The oversized-body case first served junk — which fails signature verification anyway, so it proved nothing about the cap. It now serves a real, correctly signed, schema-valid manifest padded past the limit, and asserts the error names the size: without the explicit check the poll still fails, but as "signature does not verify", which sends whoever reads that log hunting a key problem that does not exist.
The
-ldflagssymbol path was checked by building a throwaway program against the package (stamped: 1 key, unstamped: 0). A wrong path there fails silently.docs/architecture.mdis also brought up to date for the last four merged slices — it still said the updater had no real-box proof and no release manifest.Progress entry:
docs/progress/release-manifest-poll.md.make test-nopamgreen,gofmtclean, OpenAPI unchanged (no API surface touched).Still true after this lands: no signing key, no
releases.malmo.network, version → image digest undecided, and the ghcr packages still private — so no appliance box can complete an update yet.