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