From 3d939b5c7708d80cd3a417d85fe4e2e8a7c43565 Mon Sep 17 00:00:00 2001 From: jochen Date: Sat, 12 Sep 2026 16:45:14 +0200 Subject: [PATCH] Describe how a mesh is raised, because only a test did MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The one complete account of standing a mesh up was an integration test, and a fixture is free to invent what it needs — which is how a registry that exists in no production hid two faults for as long as the lab existed. Written from what the installer does, not what it should do: genesis and joining are separate moments, the lab runs the installer rather than describing installing, and three things that are not true yet are named rather than glossed, including one rule nothing checks. Claude-Session: https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx --- 03-DESIGN/01-to-be/17-raising-a-mesh.md | 159 ++++++++++++++++++++++++ 03-DESIGN/01-to-be/README.md | 1 + 2 files changed, 160 insertions(+) create mode 100644 03-DESIGN/01-to-be/17-raising-a-mesh.md diff --git a/03-DESIGN/01-to-be/17-raising-a-mesh.md b/03-DESIGN/01-to-be/17-raising-a-mesh.md new file mode 100644 index 0000000..b999bab --- /dev/null +++ b/03-DESIGN/01-to-be/17-raising-a-mesh.md @@ -0,0 +1,159 @@ +--- +layer: to-be +status: in-progress +code: + - mesh-host cmd/mesh-bootstrap + - mesh-host internal/bootstrap + - mesh-lab test/integration/whole-mesh-full.test.ts +updated: 2026-09-12 +decisions: + - 02-DECISIONS/0067-genesis-is-a-pivot.md + - 02-DECISIONS/0006-the-substrate-and-the-control-plane.md + - 02-DECISIONS/0005-the-node-host.md + - 02-DECISIONS/0010-delivery.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](../../02-DECISIONS/0067-genesis-is-a-pivot.md)). + +**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 substrate 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. + +## Genesis + +The installer is a single program carrying the control plane's image inside it. That is what makes +genesis possible without a network to fetch from and without a registry to name: the image is +present because the installer is present. + +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. A machine that is not ready is told +what is missing rather than half-changed. + +**Then it loads the carried image and describes what the machine will become.** The image is named +by the digest of its own configuration — content-addressed and unforgeable, and requiring nothing +to have served it. This is legal precisely where nothing could have served one, and nowhere else. + +**Then it raises the substrate and a temporary control plane, and waits for that control plane 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 control plane'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 control plane, +the registry, and the builder. The rule and its closed list are in +[`12-a-module-repository`](12-a-module-repository.md#the-three-the-loop-cannot-build-and-there-are-only-three). + +**Then it installs the control plane 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 control plane, 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.** 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`](12-a-module-repository.md#the-three-the-loop-cannot-build-and-there-are-only-three). +**How it arrives is unsettled**, and it is the one gap that stops everything after this paragraph +from being possible on a machine nobody is sitting at. + +**The core modules are built.** Each is named by a repository and a path +([ADR 0069](../../02-DECISIONS/0069-a-module-is-a-repository-and-a-path.md)), 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 control plane 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 substrate 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 substrate, 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. + +**The builder has no way to arrive.** It cannot be pulled from the public internet, because the +mesh builds it; and the installer carries one image only. So the paragraphs above describing the +core modules being built are, today, describing something that cannot start. + +**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. + +## 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 | **Nothing.** There is no route for it yet. | +| A core module is built rather than only carried | The control plane 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 | After installing, 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. | diff --git a/03-DESIGN/01-to-be/README.md b/03-DESIGN/01-to-be/README.md index fda651a..c84d6b5 100644 --- a/03-DESIGN/01-to-be/README.md +++ b/03-DESIGN/01-to-be/README.md @@ -26,6 +26,7 @@ document is written and this one's status becomes `implemented`. | [`14-model-access.md`](14-model-access.md) | Model access as a provision, and what a licence is bound to | [ADR 0024](../../02-DECISIONS/0024-model-access-is-a-provision.md), [ADR 0009](../../02-DECISIONS/0009-modules-and-the-graph.md) | | [`15-the-agent-session.md`](15-the-agent-session.md) | One mechanism started twice — a node's session and the mesh's | [ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md), [ADR 0026](../../02-DECISIONS/0026-the-mesh-has-a-session-of-its-own.md) | | [`16-module-coverage.md`](16-module-coverage.md) | What a module must be able to say, measured against 127 that exist | [ADR 0009](../../02-DECISIONS/0009-modules-and-the-graph.md), [ADR 0005](../../02-DECISIONS/0005-the-node-host.md) | +| [`17-raising-a-mesh.md`](17-raising-a-mesh.md) | How a mesh comes into existence, and how a machine joins one that exists | [ADR 0067](../../02-DECISIONS/0067-genesis-is-a-pivot.md), [ADR 0006](../../02-DECISIONS/0006-the-substrate-and-the-control-plane.md), [ADR 0005](../../02-DECISIONS/0005-the-node-host.md) | ## Not yet written