--- status: accepted date: 2026-08-26 deciders: jochen reconstructed: false extends: 0038-a-node-joins-by-linking-first.md --- # 46. The installer fetches what it pins ## Context Stage 2 of [the node host](../03-DESIGN/01-to-be/05-the-node-host.md) 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](0041-the-host-depends-on-nothing.md). **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](0041-the-host-depends-on-nothing.md) 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 build-time-versus-apply-time reframing in [research 012](../01-RESEARCH/012-the-minimum-viable-node/00-overview.md) 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](0041-the-host-depends-on-nothing.md) — the property this preserves. - [ADR 0038](0038-a-node-joins-by-linking-first.md) — the bundle this fills in. - [`07-the-substrate.md`](../03-DESIGN/01-to-be/07-the-substrate.md) — what the bundle pins. - [Research 012](../01-RESEARCH/012-the-minimum-viable-node/00-overview.md) — the reframing this qualifies.