The supervision was already right — a clean exit means the host stood aside and the launcher runs what is on disk, failures are counted, and a rollback happens at the limit. Two things made it dead code: nothing told the running host a successor was waiting, and the rollback resolved its known-good version through pacman, which no machine here uses and which two of three operating systems do not have. Keeping a version rather than a path was the clue. Versions live side by side in directories named for them; the newest runs; the running one stands aside between reconciles; a reconcile that completes records itself and retires what is older than its predecessor; rollback starts that predecessor. No new resource kind and nothing new on the bus — an archive already fetches by digest, and the path written is never the path executing. Answers issue 142.
4.5 KiB
status, opened, located-in, fixed-by, amended-design
| status | opened | located-in | fixed-by | amended-design | |||
|---|---|---|---|---|---|---|---|
| located | 2026-09-29 |
|
03-DESIGN/01-to-be/05-the-node-host.md |
142 — The host is the one thing the mesh does not deliver
What was observed
A change to the host was merged and could not reach any machine without a person copying a file.
Checked on the mesh of four machines, 2026-09-29:
- The host is not a build target. Asked what had been built for it, the control plane answered
nothing has been built for mesh-host. A merge on the forge builds every changed module and the control plane itself, because the control plane is a module. The host is not one, and nothing builds it. - No declaration delivers it. No resource kind names an executable to place on a machine, and nothing on a machine fetches one.
- The half that recovers from a bad host exists and is unused.
internal/upgradecan report that the executable this process started from has been replaced on disk, and records which version last completed a reconcile so a shell script can roll back a host that will not start. The launcher reads that record and rolls back. ButReplaced()is called by nothing except its own tests — the recovery is wired and the delivery was never built. - Every machine runs a byte-identical binary, stamped by hand. All four carry the same size and the same timestamp, from the last time somebody built it on a workstation and copied it out. No package owns the file.
Why it matters beyond this instance
The component that implements updating is the one thing not updated. The mesh's stated shape is that a push produces the right builds and they reach the machines running them with nobody asking. It is true of every module and of the control plane. It is false for the host, which is what applies all of them.
It is a bootstrap problem being answered by a person. The host cannot be an ordinary module because the host is what applies modules; a module that replaces the thing applying it has to survive its own replacement. That is a real difficulty, and the work already done — noticing that the executable changed, recording a known-good version, a launcher that rolls back — is the hard half of solving it. What is missing is the easy half, and its absence makes the hard half dead code.
A hand-copied binary has no record anywhere. Nothing says which version a machine runs, so nothing can say a machine is behind, and the mesh's own account of itself — every machine current with its source — cannot include the host. Four machines agreeing today is luck, not a property.
And it silently gates any change that starts in the host. A change that needs the host to report something new cannot be rolled out by merging it: the control plane must wait for a person, and until then it either refuses what depends on the new report or renders something wrong. That cost is paid by every future change of this shape, and it was paid today.
Open questions
- How is the host delivered without being applied by itself? A candidate shape: the host is built like
anything else, published as an artifact, and the running host fetches and stages the next one, then
stands aside — which is what
Replaced()was written for and what the launcher's rollback already covers. - Should this ride the bus, rather than becoming a mechanism of its own? Everything else that reaches a machine already does: a declaration is sent over it, a report comes back over it, and a build announces what it produced on it, which is how a module's new version reaches the machines running it. A host build announcing itself the same way, consumed by the host already running, would make this the existing mechanism pointed at one more artifact rather than a second way of delivering things. It would also give the machine somewhere to say which host it is running, on the report it already sends.
- What records which version of the host a machine runs, so "behind" is answerable? Nothing does now.
- Does the host's version belong in its report, beside the other facts a machine states about itself?
- Who decides when a machine takes a new host — the mesh, on a build, or an operator per machine as with converging? The rollback path means a bad host costs a reconcile rather than a machine, which argues for the former.
- Does the same gap apply to the launcher and the units beside the binary, which are also files no declaration names?