Two accepted decisions collide, and testing found it rather than review. 0046 pins images by digest and has the host refuse anything unpinned. The lab places images by exporting them from the workstation, because a sealed scenario cannot reach a registry -- and that loses the digest, since a repo digest only exists for an image a registry served. Measured: the load says 'Loaded image ID:' rather than 'Loaded image:', and the image lands dangling. So a tag is refused by the host and a digest is unusable in the lab. There is currently no declaration the lab can raise that exercises the container shape, which matters because the container shape IS the substrate -- every bootstrap step past the runtime is one. The resolution is a registry inside the scenario, and that is not a workaround: 0048 already names an OCI registry as substrate and every node after the first pulls from the mesh's own. It also removes the lab's export-and-push mechanism rather than repairing it. 0046 now carries a pointer, since its own consequence is where the collision was predicted -- half of it is closed and the other half turned out to be harder than 'not solved here' suggested.
4.4 KiB
status, date, deciders, reconstructed, extends
| status | date | deciders | reconstructed | extends |
|---|---|---|---|---|
| accepted | 2026-08-26 | jochen | false | 0038-a-node-joins-by-linking-first.md |
46. The installer fetches what it pins
Context
Stage 2 of the node host needs to raise the substrate, and a substrate service is a container, and a container needs an image. Where the image comes from had been blocking implementation.
The blocking version of the question assumed the machine might have no network, which produced a bad trilemma: carry every image inside the artifact, fetch at apply time, or have something else place them first. Carrying them makes a three-megabyte binary into a multi-hundred-megabyte one and strains ADR 0041.
The assumption was wrong, and it came from the lab. A scenario is a closed address space by design — that is what lets two scenarios hold the same addresses without meeting. Production is not: a machine being adopted has a network, and one that does not is a machine where very little works anyway.
Decision
The installer fetches what the bundle pins.
substrate.lock carries references, not payload — an image name and a digest. At apply time
the host fetches them.
| Situation | Fetched from |
|---|---|
| a first node, no mesh yet | upstream, wherever the image ordinarily lives |
| every node after that | the mesh's own registry |
| the lab | nowhere — the lab places them first |
Pinned by digest, not by tag. A tag moves; a digest does not. Reproducibility comes from pinning the identity of the thing, not from carrying its bytes — which is what makes fetching acceptable rather than a compromise.
The lab is the exception, and it is the lab's problem. A sealed scenario cannot reach a registry, so the lab places images into a machine the same way it already places the host binary. That is a property of a test environment, and letting it dictate the production design would be the tail wagging the dog.
Consequences
- ADR 0041 survives untouched. Copy it onto a machine and run it remains literally true: one binary, a few megabytes, which then fetches what it was told to fetch. The alternative would have quietly redefined the property that decision rests on.
- The bundle stays small and reviewable. A list of pinned references is something a person can read and check. A bundle containing images is not.
- An apply can fail because something is unreachable, which a self-contained artifact could not. That is the cost, it is accepted, and it must fail legibly — naming what it could not fetch and from where, not "install failed".
- The lab needs a way to place images, and the machine it places them into needs a container runtime, which a sealed scenario cannot install either. Both are lab-installation concerns and neither is solved here. The runtime half is now done — the lab builds a base image on a machine with a network and raises sealed machines from it. The image half turned out to collide with this record: an image placed from an archive cannot keep its digest, and this record has the host refuse anything unpinned, so the lab can satisfy neither form. See 04-ISSUES/009; the resolution is a registry inside the scenario, which is what a real node pulls from anyway.
- The build-time-versus-apply-time reframing in research 012 narrows. It still holds for what a tailored installer contains — the missing pieces for a given machine — but it does not have to hold for images, because fetching them is available and cheap. Recorded because the reframing was general and is now qualified.
- Nothing here says what happens when a fetch is impossible on a real node. An air-gapped machine is not a case the mesh has, and if one appears this decision is what it revisits.
References
- ADR 0041 — the property this preserves.
- ADR 0038 — the bundle this fills in.
07-the-substrate.md— what the bundle pins.- Research 012 — the reframing this qualifies.