Files
hq/03-DESIGN/01-to-be/17-raising-a-mesh.md
T
jschoubben da6414c27c Issue 072 diagnosed: genesis registers the manifest its build produced; design 17 says so
ADR 0069 had already placed the controller's manifest in its own repository, and the
raising design called the catalogue copy a thing to remove. The installer's step 3 was
handed that manifest by the build and step 9 read a second copy anyway.
2026-09-21 15:18:32 +02:00

13 KiB

layer, status, code, updated, decisions
layer status code updated decisions
to-be in-progress
mesh-host cmd/mesh-bootstrap
mesh-host internal/bootstrap
mesh-lab test/integration/whole-mesh-full.test.ts
2026-09-21
02-DECISIONS/0067-genesis-is-a-pivot.md
02-DECISIONS/0069-a-module-is-a-repository-and-a-path.md
02-DECISIONS/0006-the-substrate-and-the-control-plane.md
02-DECISIONS/0005-the-node-host.md
02-DECISIONS/0010-delivery.md
02-DECISIONS/0036-bootstrap-ends-at-a-usable-mesh.md

Raising a mesh

How a mesh comes into existence on machines that have none, and how a machine joins one that already exists. This is the procedure an operator runs. It is not a description of the lab, and the lab does not have one of its own.

Two moments, and only two

A mesh is raised once and joined many times, and the two are not variations of each other.

Genesis happens on one machine, when there is no mesh. Nothing can be asked, nothing can be granted, and nothing has been published anywhere. It is the only moment at which the ordinary rules cannot all hold at once, and it is resolved by a pivot (ADR 0067).

Joining happens on every machine after the first, and it is ordinary. A mesh exists, so it can be asked for a token and told what the machine should be. Joining installs the host and nothing else: no temporary anything, no foundation raised by hand, no registry.

Confusing the two is what produced a procedure that only ever worked in a fixture. A bed that raises four machines the same way has not tested genesis at all — it has tested joining, four times, with the first one hand-fed.

What changed, and what did not

2026-09-13. The installer carries a builder now, and builds the controller it raises. Three records settle it: ADR 0070 that genesis builds rather than carries, ADR 0071 where it clones from, and ADR 0073 how the builder arrives — which also records an argument that failed. It was put that a produced image must be published before anything can fetch it, so the registry would have to come up before the controller. It does not: the machine that builds the image is the machine that runs it, and a local image is named by the digest of its own configuration exactly as a carried one is. Building changes where the bytes came from, not where they are.

So the pivot is unchanged, the registry is where it was, and one step was added before the bundle is written. What follows describes the program that exists.

Genesis

The installer is a single program carrying the builder inside it — not the controller (ADR 0073). What cannot be fetched is the thing that does the fetching, so that is what is carried; everything else is made here.

It proceeds in one direction, and every step is safe to run again.

First it refuses to start if the machine is not ready. A container runtime, the ability to write where it must write, the host binary where it expects it — and a repository and a commit to build from, because an installer told nothing would raise a store and a broker and then have nothing to raise a controller from. A machine that is not ready is told what is missing rather than half-changed.

Then it loads the carried builder and builds the controller with it, from a repository on a mesh that already exists and a named commit (ADR 0071). This is the same repository and path every later rebuild of the controller will use, so what raises the mesh is the same thing that will maintain it.

Then it describes what the machine will become. The image it just made is named by the digest of its own configuration — content-addressed and unforgeable, and requiring nothing to have served it. That is legal precisely where nothing could have served one, and it is why building here needs no registry: the machine that made the image is the machine that will run it.

Then it raises the foundation and a temporary controller, and waits for that controller to answer. At this point the machine is a mesh of one node with nothing joined to it.

Then the machine joins the mesh it is itself running. It enrols, and an agent runs on it. Being heard from once is not the same as an agent running, and the installer checks both, because enrolling is itself the thing that makes a mesh hear from a machine.

Then it installs a registry, so the mesh has somewhere to keep its own images.

Then it publishes the controller's image to that registry, which is the moment the image first receives a digest assigned by something other than itself. This is the carrying step, and it is the same step for all three things the build loop cannot produce for itself — the controller, the registry, and the builder. The rule and its closed list are in 12-a-module-repository.

Then it installs the controller again, as an ordinary module pinned to that digest, and drops the temporary one. The pivot is complete: what raised the mesh is gone, and what runs is a module like any other. From here the mesh can build and roll out its own upgrades, including to the thing that runs it.

After the pivot, and still part of installing

Genesis ends with a mesh of one that runs, and that is not the same as a mesh that works. What it has is a controller, a store, a queue and a registry. What it cannot yet do is produce anything — and almost every module in the catalogue is waiting to be produced, because a manifest names what its artifacts are and nothing has made them.

So installing continues:

The builder arrives, and installing is what brings it. It is a module like any other and is assigned to a machine like any other, but it cannot be built by the thing it is — see 12-a-module-repository. So it is carried, and it is already here: it is what built the controller. The last step of installing publishes it into the mesh's own registry, installs it as an ordinary module pinned to that digest, and issues it a broker account — the same two acts the controller went through, plus the one thing only a builder needs. The account is issued before the machine is sent anything, because a builder that arrives without its credential starts, finds nothing it may read, and waits, which looks exactly like a builder with no work.

The core modules are built. Each is named by a repository and a path (ADR 0069), and the builder is asked for each in turn: it clones, reads the manifest at that path, produces what it declares, publishes each artifact into the mesh's own registry, and hands back the module with its artifacts pinned and the commit recorded. The mesh records that, and from then on the module is described by something it made rather than by a placeholder.

The controller is built like the rest. It was carried in and published once, which got the mesh running; building it from its own repository and path is what makes it upgradeable. The first time that happens is the moment the mesh stops depending on the installer for anything.

And then the catalogue. Every module with source of its own is built the same way. Until this has happened a mesh can install only what is public or carried, which is the foundation and little else.

Only after all of that is the ordinary loop available: change a module's source, the mesh notices its own copy is older than the source, rebuilds it, and rolls it out. That loop is what makes moving services across one at a time possible, so it is part of installing rather than something that comes later.

What remains after that belongs to somebody else: adding machines, and deciding what they run.

Joining

A machine joins with the host binary and a token. It does not raise a foundation, does not install a registry, and is never enrolled twice. The mesh already knows how to tell a machine what to be; joining is the point at which a machine starts listening.

Where the line falls

The installer owns everything that is the mesh's own. The lab owns only what is the lab's: raising virtual machines, giving them addresses that resolve nowhere, and injecting faults.

The lab runs the installer. It does not describe installation. This is the whole point. A second description kept in step with the first is the arrangement that already failed — the fixture invented a registry that exists in no production, and hid two separate faults for as long as it existed. Anything the lab must do that the installer does not is either a lab concern or a hole in the installer, and saying which is a decision, not a convenience.

What is not yet true

Stated plainly, because a document that implies otherwise is worse than none.

The installer is not packaged. It builds from source. An operator raising a first machine still needs a toolchain and a working tree, which is most of the burden the installer exists to remove.

A mesh cannot say how it was raised. Nothing afterwards can contradict a claim that a machine was brought up the supported way, so the rule that it must be is, today, unenforced.

Nothing asks for the core modules yet. Installing ends with a builder that works — a raised machine will build the shared base and a module on top of it when asked — and nothing asks. The paragraphs above describing the catalogue being built are a thing somebody now types, rather than a thing that cannot happen.

A module's declaration still has to be copied onto the machine by hand. The installer reads the registry's and the builder's manifests from a checkout somebody put there. The controller's is no longer among them: it lives in the controller's own repository (ADR 0069), the build the installer runs reports it with the artifact resolved, and genesis registers that (issue 072). The builder's manifest belongs in the same repository and is still read from the catalogue; that is the last of this.

A machine has no account for a registry that asks for one. The mesh grants a consumer a credential for a database; it does not yet do so for the store its own images live in. Genesis avoids the question by carrying the image it needs, which makes this a joining problem and a pulling problem, not a genesis one.

The SDK still comes from a git URL, and the ordering that fixes it is decided but not built. ADR 0076 settles that the package registry (gitea) comes up and the SDK is published into it before the base toolchain is built, so the toolchain resolves the SDK by version rather than cloning it — closing issue 053. The builder already knows how to be handed a package-registry credential and inject it into a build; what is not yet wired is the genesis step that raises gitea and publishes the SDK ahead of the base, and the toolchain's own manifest still names the SDK by a git URL. Until both land, the base build clones the SDK inside docker build, which is slow and names a branch head rather than a version.

How these rules are checked

Rule Checked by
Genesis works on a machine that is not the lab The bed raises its first machine by running the installer, not around it. A bed that stops doing so fails its own acceptance check.
The other machines join, and are not re-raised The bed gives them the host binary and a token only. A second enrolment of the first machine is a failure, not a no-op.
Every step may be run again The installer is re-run against a raised machine and must change nothing and report why.
An image is named exactly A machine refuses a bundle naming an image by tag. The refusal is exercised, not assumed.
The installer is what installed this Nothing. See above.
The builder can arrive on a fresh mesh The installer carries it and installs it as its last step, and the genesis bed raises a machine by running the installer. A bed that raises one any other way fails its own acceptance check.
The controller a mesh runs is one it built The genesis bed asserts the running controller is pinned to a digest this mesh's own registry serves, for an image built from a named repository and commit — not one the installer carried.
A core module is built rather than only carried The controller is rebuilt from its own repository and path, and the running mesh is upgraded to the result — the same path any module takes.
Installing produced a mesh that can produce A module with source of its own is asked for and comes back pinned to a digest this mesh's registry assigned, not to a placeholder. Done by hand on a raised machine, not yet by a bed — it built the shared base and then a module naming that base. Nothing automated asserts it, which makes this the weakest check on this page.