Files
hq/03-DESIGN/01-to-be/07-the-substrate.md
T
jschoubben 5b3d0ebd4f 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.
2026-08-26 23:52:46 +02:00

5.5 KiB

layer, status, code, updated, decisions
layer status code updated decisions
to-be designed
2026-08-26
02-DECISIONS/0030-the-repository-structure.md
02-DECISIONS/0037-the-host-applies-it-does-not-decide.md
02-DECISIONS/0038-a-node-joins-by-linking-first.md

The substrate

Tier 1. Defined the same way the control plane is, because the same gap applied: the word was load-bearing and unpinned.

The definition

The substrate is what the control plane consumes and cannot grant itself.

Every module that needs a database asks the control plane's provisioning for one. The control plane needs a database too — and it cannot ask itself, because it is not running yet. That circularity is not an awkwardness to work around; it is the definition. Anything on the wrong side of it must be raised some other way, and the other way is the bundle the host carries (ADR 0038).

The test, applied:

control plane needs it can it grant itself one?
a relational store its own state lives there no — provisioning needs the store substrate
a message bus it reaches nodes over it (ADR 0001) no — it cannot grant itself a virtual host substrate
an object store artifacts and blobs it delivers no — it needs a bucket to hold them substrate
an image registry images it delivers to nodes no — it needs a repository substrate
an identity provider only if it delegates authentication — conditional, below
anything else the mesh hosts no — not substrate

What that resolves

Four or five? Research 006 asks whether the identity provider is a substrate service, and the test answers it conditionally — which is the honest answer rather than a number.

  • If the control plane delegates authentication, it cannot serve anybody before the provider exists, and it cannot grant itself a client. Substrate.
  • If it authenticates natively, the provider is an ordinary hosted service like any other. Not substrate.

So the count follows from a design decision that has not been taken, and the record should say that rather than assert four.

Why not "important infrastructure". An identity provider, a mail server and an analytics service are all infrastructure by any ordinary reading, and none of them are substrate — the control plane starts and runs without them. Important is not the test; the control plane cannot obtain it is.

What the substrate is not

  • Not tier 0. The host raises the substrate; it is not part of it. The host carries the declaration that brings the substrate up, and depends on nothing.
  • Not the control plane. These are services with no knowledge of the mesh. A store does not know what a node is.
  • Not a place for logic. The skeleton is explicit: tier 1 is declarations only, no logic of its own. A substrate service is an upstream image, pinned, with configuration.
  • Not privileged. The substrate is provisioned from by the control plane and grants nothing on its own initiative.

The pinned bundle

The substrate is what substrate.lock contains, and this is the only place in the mesh where 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 a registry, or check a constraint. What the host carries must already be exact.

Why references and not payload: the bundle names images by digest and the host fetches them (ADR 0046). A first node is a real machine with a network; the sealed case is the lab, and the lab places images itself.

Reproducibility comes from pinning the identity of a thing rather than carrying its bytes, which is what keeps the bundle small enough for a person to read and check.

Raising it

The order, from research 011:

1  the host applies the bundle          the store runs; no mesh exists
2  a database is created in it          a provisioning step, done locally
3  the control plane's schema applied   a migration against that database
4  the control plane starts             and only now is there a mesh
5  everything else is provisioned       the ordinary path

Steps 2 and 3 happen before there is a mesh to do them, which is why provisioning is part of the bootstrap rather than a service consumers use later — and why the host's declaration vocabulary has to reach further than files, directories and units.

Open

  • Whether identity is the fifth. Above; it follows from a decision not yet taken.
  • Whether the host can do step 2. Creating a database inside a running store is not node state, and ADR 0043 has the host applying state on this machine. At bootstrap the store is on that machine, so it is at least local. It is the sharpest unresolved thing in the bootstrap path.
  • Whether one host can raise all four. The claim under stage 2 of the node host, never proved. If it is false, the tier boundary moves.
  • How the substrate is updated once a mesh exists. Pinned by hand at bootstrap; afterwards the control plane could deliver it like anything else, and nothing says whether it does.