ADR 0046 — the installer fetches what it pins

The blocking question was where a container image comes from, and the version
that blocked assumed the machine might have no network. That assumption came
from the LAB: a scenario is a closed address space by design, which is what lets
two scenarios hold the same addresses without meeting. Production is not sealed
— a machine being adopted has a network, and one that does not is a machine
where very little works anyway.

So substrate.lock carries references, not payload: an image name and a digest,
fetched at apply time. A first node pulls from upstream because no mesh registry
exists yet; every node after that pulls from the mesh's own. The lab is the
exception and places images itself, the way it already places the host binary —
a property of a test environment, and letting it dictate the production design
would be the tail wagging the dog.

Pinned by DIGEST rather than tag. Reproducibility comes from pinning the
identity of a thing, not from carrying its bytes, which is what makes fetching
acceptable rather than a compromise.

ADR 0041 survives untouched, which was the point. "Copy it onto a machine and
run it" stays literally true — one binary, a few megabytes, which then fetches
what it was told to. Carrying images would have quietly redefined the property
that decision rests on.

Costs accepted and named: an apply can now fail because something is
unreachable, which a self-contained artifact could not, so it must fail legibly
— naming what it could not fetch and from where. And the lab needs a way to
place images into a machine that also has no container runtime, both of which
are lab-installation concerns and neither solved here.

Research 012's build-time-versus-apply-time reframing narrows accordingly: it
still holds for what a tailored installer contains, and no longer has to hold
for images.
This commit is contained in:
2026-08-26 23:52:46 +02:00
parent 0531d6fc38
commit 5b3d0ebd4f
3 changed files with 87 additions and 6 deletions
@@ -34,6 +34,11 @@ carry everything in the bundle, download at apply time, or have something push t
first. Downloading fails on the first node, which cannot fetch the image registry from the image first. Downloading fails on the first node, which cannot fetch the image registry from the image
registry it is trying to start. registry it is trying to start.
> **Qualified by [ADR 0046](../../02-DECISIONS/0046-the-installer-fetches-what-it-pins.md).** The
> reframing below still holds for what a *tailored installer* contains — the missing pieces for a
> given machine. It does **not** have to hold for container images: the installer fetches those
> by digest, because a real machine has a network and the sealed case is the lab.
**The reframing:** the machine is not offline. What matters is *when* the fetching happens. Move **The reframing:** the machine is not offline. What matters is *when* the fetching happens. Move
it from apply time to **build time** — build the installer on a machine that has a network, it from apply time to **build time** — build the installer on a machine that has a network,
tailored to the target, and apply it on a target that then needs nothing. That is the same move tailored to the target, and apply it on a target that then needs nothing. That is the same move
@@ -0,0 +1,77 @@
---
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.
+5 -6
View File
@@ -73,13 +73,12 @@ versions are pinned by hand rather than resolved.
**Why pinned:** the bundle is applied when no mesh exists, so nothing can resolve a version, ask **Why pinned:** the bundle is applied when no mesh exists, so nothing can resolve a version, ask
a registry, or check a constraint. What the host carries must already be exact. a registry, or check a constraint. What the host carries must already be exact.
**Why self-contained:** a node raising a first mesh may have no route to anything **Why references and not payload:** the bundle names images by **digest** and the host fetches
([research 012](../../01-RESEARCH/012-the-minimum-viable-node/00-overview.md)). Images and them ([ADR 0046](../../02-DECISIONS/0046-the-installer-fetches-what-it-pins.md)). A first node is
packages the bundle needs travel *with* it, fetched when the bundle was **built** rather than a real machine with a network; the sealed case is the lab, and the lab places images itself.
when it is applied.
**What that makes it:** an artifact built on a machine with a network, for a machine that may Reproducibility comes from pinning the identity of a thing rather than carrying its bytes, which
have none — which is the reframing research 012 records, arriving here as a requirement. is what keeps the bundle small enough for a person to read and check.
## Raising it ## Raising it