Files
hq/04-ISSUES/142-the-host-is-the-one-thing-the-mesh-does-not-deliver/01-progress.md
T
jschoubben 02f291a129 Every machine self-updates, verified, and 107's gate has opened
All four machines run a host the mesh built, published and delivered, the
last delivery unattended: each stood aside once for a genuinely newer
version and the delivered launcher started it. A following push that
delivered nothing new was applied and reported by every machine and stood
nobody aside.

The crossover needs one restart of the unit per machine, once, because
the running launcher executes from its own inode. Measured timing: three
seconds on the machine, 17-20 as the operator sees it, the difference
being the control plane composing before it sends.

107 is unblocked: a declaration field is now a build and a push.
2026-09-30 13:51:46 +02:00

186 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 142 — the mesh can now build and publish its own host
*2026-09-30. Both of ADR 0141's named gaps are closed; the host is not yet placed by the mesh.*
## What ADR 0141's insight named, and what each cost
> The remaining work is a way to build the host and a way to name a version in a path, and until both
> exist nothing delivers a version and every machine takes the fallback.
**1. Nothing could compile it.** The toolchain list was a closed set of typescript and python, and its
own warning — every language is another implementation of the contracts modules share, so adding one
commits to keeping N implementations in step — does not attach to Go. Go is how the host, the control
plane and the builder are written, and none of them is a module in that sense: the host is what
*applies* modules.
Closed by a Go toolchain naming a new base module, `mesh-tools-go`. Named and not pinned, so the mesh
answers with the copy it holds and moving compiler is a build rather than an edit to the control
plane's source ([ADR 0044](../../02-DECISIONS/0044-a-public-name-is-provisioned-like-any-capability.md),
[0142](../../02-DECISIONS/0142-the-mesh-delivers-its-own-components-as-binaries.md)).
**2. A version could not reach the path.** An archive named a fixed path and nothing interpolated the
build into it, so nothing could ask for `…/versions/<version>/`.
Closed by `${version}` in any value of a resource that uses an archive or a bundle. **The version is
the artifact's digest, short, and not the commit**: two builds of one commit are meant to be the same
bytes — the toolchains are `-trimpath` for that — so a content-addressed version means an unchanged
build resolves to the path it already had, where a commit-named path would move for an identical binary
and recreate everything reading it.
## Three more things were in the way, and none was in the record
Found by doing it, in the order they appeared:
- **`sourcesFor` turned every entrypoint into a `.ts` file.** One language's file extension, written
into the code that serves every language. The extension is the toolchain's now.
- **The output directory was left to the compiler.** `tsc --outDir` makes one; `go build -o` writes into
a directory and does not create it, failing with a message about a path rather than about a build.
Made for every toolchain, because which compilers are forgiving is not something a reader should have
to know.
- **A bundle was refused if it named what it is built from.** The reason — a bundle is the module's own
directory compiled whole — holds for an interpreted language and cannot hold for a compiled one: a Go
repository carries several commands, the host and its bootstrap among them, and "the module's own
directory" is then not a package at all. A compiled bundle may now say which package; the refusal
stands for every interpreted one.
And a fourth, in the base module itself: its first Dockerfile ran `apt-get`, and the golang image the
mesh holds is Alpine. That is
[issue 136](../136-a-module-may-name-a-program-the-machine-does-not-have/00-report.md) in an image
rather than on a machine, and the build refused rather than a module failing later — which is the
behaviour that issue wants.
## Measured: the mesh compiles its own host and publishes it to its own registry
```
$ mesh-controller build <the host's repository>
host-arch bundle artifact-store://mesh-host/host-arch/blobs/sha256:ad62528c…
mesh-host 1, built on novox from b5196e97
```
Fetched from the mesh's registry and opened:
```
mesh-host: ELF 64-bit LSB executable, x86-64, statically linked, stripped
$ ./mesh-host help
mesh-host — the node host
```
Statically linked matters: what a machine holds is a file rather than a container, so a binary needing
a libc it did not bring is a delivery that works until a machine differs.
**The host is a module now**, with one bundle for `arch` built from `cmd/mesh-host`. A system is
required for a compiled artifact because a binary is pinned at link time so a host refuses to touch a
machine it was not built for ([ADR 0005](../../02-DECISIONS/0005-the-node-host.md)); `arch` is what all
four of this mesh's machines report themselves to be, and another system is another artifact and
another build.
## What is left
**The host module declares no resources**, so nothing places the built bundle on a machine yet. That is
the next piece and it is the one with the interesting question in it: the resource is an archive
unpacked to `…/versions/${version}/`, applied by the host that is running, and the bootstrap is not
circular because the two are different versions ([ADR 0141](../../02-DECISIONS/0141-the-host-delivers-its-own-successor.md)).
The host half of that mechanism — versions side by side, the newest runs, the running one stands aside
between reconciles, rollback picks a directory — is built and tested and has never had a version to
work on.
**One thing worth noticing while writing it.** The systems list exists because "the difference between
two of them is a C library, not a kernel" — and a static Go binary has no C library. So one build would
in fact run on all three. The pin is then a policy (a host refuses a machine it was not built for)
rather than a necessity, which is a reasonable thing to keep and is worth knowing is a choice.
**And a sharper one, asked as a question and worth its own record.** The artifact's system is validated
and then read by nothing: it does not reach the compiler, no machine is matched against it, and nothing
chooses between two artifacts by it. The host built here is x86-64 because the build machine is, not
because anything in the declaration said so — correct for this mesh by coincidence. That is
[issue 159](../159-an-artifacts-system-is-checked-and-then-ignored/00-report.md).
## Delivered, started, and one fact short (2026-09-30, later)
The loop closed. The launcher is delivered as a **file** resource rather than inside the archive, and
that difference is the safety: a file is written atomically — temp file, then rename — so the running
launcher keeps the inode it was started from, where an archive writes in place with truncate and would
cut the script a running shell is reading. The manifest carries a second copy of the launcher and a
test refuses any difference from `packaging/nox-mesh-host-launch`.
On the workstation, in order: the version landed, the launcher was replaced, the running host saw a
delivered version and stood aside, and after one restart of the unit the launcher started
`/usr/lib/nox-mesh-host/versions/637f65559d16/nox-mesh-host`. **A host the mesh compiled, published,
delivered and started.**
It would then have refused the first declaration it was asked to apply. The host's Makefile links in
two facts the mesh's toolchain does not, and one of them — the system it was built for — is read before
anything is applied. That is
[issue 161](../161-a-delivered-host-carries-none-of-its-link-time-facts/00-report.md), and the machine
is back on its hand-placed binary until it is answered.
**The fallback is what made that safe**, and it was not luck: the launcher runs the pinned version, or
the newest delivered one, or the one placed by hand — so moving the delivered versions aside restored
the machine in one step.
## Self-update works (2026-09-30, end of the day)
```
running: /usr/lib/nox-mesh-host/versions/093231796eb0/nox-mesh-host
mesh-controller node show shanks
host 093231796eb0
```
One machine runs a host the mesh compiled, published, delivered and started, applying declarations and
reporting the version it was delivered as. The two facts a delivered binary was missing are
[issue 161](../161-a-delivered-host-carries-none-of-its-link-time-facts/01-resolution.md) and resolved:
the system comes from the artifact, the version from where the binary sits.
**Three machines still run a hand-placed host**, and rolling each forward is one assignment and one
push. The control node is worth last.
One thing this found on the way out: an archive cannot be undeclared, and the attempt stops the machine
applying anything at all — [issue 162](../162-an-archive-cannot-be-undeclared/00-report.md). It is how
undoing the first delivery froze the workstation, and it is not specific to the host.
## Every machine self-updates (2026-09-30, evening)
```
shanks 76f4566bef3d/nox-mesh-host active
g14 76f4566bef3d/nox-mesh-host active
novox 76f4566bef3d/nox-mesh-host active
ace 76f4566bef3d/nox-mesh-host active
mesh-controller status: (no host split)
```
The last delivery was unattended on all four: the fixed host was built, pushed, each machine stood
aside exactly once for the genuinely newer version, and the delivered launcher started it — no
restart by hand. A following push that delivered nothing new was applied and reported by every
machine and stood nobody aside, which is the check
[issue 163](../163-a-delivered-host-stood-aside-on-every-push-and-reported-nothing/00-report.md) asks
for.
**Two more faults on the way, both mine, both found by reading the machine rather than the success
line.** A delivered host compared the newest delivered version against its link-time stamp rather
than the version it was running, so it stood aside on every push and — because standing aside cancels
the report — never reported again (163). And the adopted machine kept its found launcher as the
adoption rule says, so the delivery there needed a `take` before the launcher moved.
**The crossover needs one restart of the unit per machine, once.** The launcher process that was
running on each machine was the old script, executing from its own inode; a new file beside it
changes nothing until the unit restarts. Every subsequent delivery is unattended.
**Timing, measured:** on a machine, hearing a declaration to reporting it applied is about three
seconds. A push as the operator sees it takes 17–20 seconds, and the difference is the control plane
composing the declaration before it sends. A `--wait` shorter than that reads as "did not report" for
a machine that did; the three-minute default read as slowness for a machine that never would. Neither
number is a defect being chased here, and both are worth knowing before reading a push's answer.
## What this leaves
- [Issue 162](../162-an-archive-cannot-be-undeclared/00-report.md): an archive cannot be undeclared, so
the host module — and any module with an archive — cannot be unassigned, and trying stops the machine
applying anything.
- [Issue 107](../107-a-declaration-carries-no-order/00-report.md) is unblocked: a declaration field is
now a build and a push rather than an expedition.
- Three stale version directories on the workstation from the first attempts, moved aside under
`/var/lib/mesh-host/versions-held-back/`, and a backup of the adopted machine's hand-placed binary
beside its state. Both are safe to delete and are not the mesh's to delete.