A module is a repository and a path, and installing is described to its end #33
@@ -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. |
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user