The installation, written out in full
Every step from a bare machine to a mesh that maintains itself, in three phases, with each step named as the installer prints it. The point of writing it out is the shape it exposes. The installer owns twelve steps and ends at a mesh that RUNS. Seven more turn that into a mesh that WORKS — the shared base, a store that is a provider rather than the control plane's own memory, the catalogue, the replay of what was built before the catalogue existed, the control plane rebuilt through the module path, the private network with the node actually placed on it, and the packet filter. None of those seven is the installer's. They are things somebody types, which is why a test had to be written to discover they were missing. Machines arrive last, in phase three, because a machine joining a mesh that cannot build anything proves enrolment works and nothing else. And five things that are not yet true are named rather than implied: phase two is manual, a second machine cannot pull what the mesh built, ADR 0014 assumes a private package registry that genesis has not installed when the first build needs it, the host agent does not survive a reboot, and nothing can contradict a claim that a machine was installed this way. Claude-Session: https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
---
|
||||
layer: to-be
|
||||
status: proposed
|
||||
code:
|
||||
- mesh-host internal/bootstrap
|
||||
- mesh-host cmd/mesh-bootstrap
|
||||
- mesh-lab test/integration/one-node-mesh.test.ts
|
||||
updated: 2026-09-15
|
||||
decisions:
|
||||
- 02-DECISIONS/0067-genesis-is-a-pivot.md
|
||||
- 02-DECISIONS/0073-the-installer-carries-a-builder.md
|
||||
- 02-DECISIONS/0071-where-genesis-gets-its-source.md
|
||||
- 02-DECISIONS/0014-no-npm-workspace.md
|
||||
---
|
||||
|
||||
# The installation, in full
|
||||
|
||||
Every step from a machine with nothing to a mesh that maintains itself. Written out explicitly
|
||||
because it is the procedure everything else depends on, and because the parts that do not exist
|
||||
yet are easier to see beside the parts that do.
|
||||
|
||||
[`17-raising-a-mesh`](17-raising-a-mesh.md) argues *why* it is shaped this way. This says *what
|
||||
happens*, in order, with each step's name as the installer prints it.
|
||||
|
||||
## What must be true before anything starts
|
||||
|
||||
| | why |
|
||||
|---|---|
|
||||
| a container runtime | the substrate is containers, and the installer refuses without one |
|
||||
| the host binary, where the installer expects it | it is what the machine becomes |
|
||||
| a repository and a commit to build from | the installer carries a builder, not a control plane, so it must be told what to make ([ADR 0073](../../02-DECISIONS/0073-the-installer-carries-a-builder.md)) |
|
||||
| a way out to the internet | the store, the broker and the registry are pulled from it |
|
||||
| a reachable git host, and a commit it serves | what is cloned is the trust anchor for everything this mesh will ever run ([ADR 0071](../../02-DECISIONS/0071-where-genesis-gets-its-source.md)) |
|
||||
| the address other machines will reach this one on | a token carries it verbatim; the installer refuses to guess |
|
||||
|
||||
## Phase one — a machine becomes a mesh of one
|
||||
|
||||
Twelve steps, run by one program, each safe to run again.
|
||||
|
||||
| # | step | what happens | true afterwards |
|
||||
|---|---|---|---|
|
||||
| 1 | `preflight` | everything above is checked | the machine is not half-changed by a missing prerequisite |
|
||||
| 2 | `load` | the carried builder image is loaded | the mesh has the one thing it cannot fetch — the thing that does the fetching |
|
||||
| 3 | `build` | the builder clones the named repository at the named commit and **builds the control plane** | what will run is something this mesh made and can make again |
|
||||
| 4 | `bundle` | the substrate template is written out, with the built control plane's id in place of the placeholder | the machine has a description of what it will become |
|
||||
| 5 | `apply` | store, broker, schemas, and a **temporary** control plane are raised | a mesh of one exists and answers |
|
||||
| 6 | `verify` | the control plane is asked, rather than assumed | it replies, and says it has no machines |
|
||||
| 7 | `enrol` | the machine joins the mesh it is itself running | the mesh has one node, and an agent runs on it |
|
||||
| 8 | `registry` | the mesh's own artifact store is installed | there is somewhere to keep what this mesh makes |
|
||||
| 9 | `publish` | the control plane's image is pushed into it | the image has a digest something other than itself assigned |
|
||||
| 10 | `control-plane` | it is installed again, as an ordinary module pinned to that digest | what runs is a module like any other |
|
||||
| 11 | `retire` | the temporary control plane is dropped | **the pivot is complete** — what raised the mesh is gone |
|
||||
| 12 | `builder` | the carried builder is published, installed as a module, and issued a broker account | the mesh can produce |
|
||||
|
||||
**Steps 8 to 11 are the pivot** ([ADR 0067](../../02-DECISIONS/0067-genesis-is-a-pivot.md)). Before
|
||||
them the control plane is something the installer put there; after them it is something the mesh
|
||||
holds a record of and can upgrade. The account in step 12 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.
|
||||
|
||||
## Phase two — a mesh of one becomes a mesh that works
|
||||
|
||||
**Genesis ends with a mesh that runs, which is not the same as a mesh that works.** It has a control
|
||||
plane, a store, a broker, a registry and a builder. It holds no module graph, has no private
|
||||
network, no packet filter, and cannot resolve a name. Calling that "installed" is what let the
|
||||
catalogue be missing from a test for weeks without anything complaining.
|
||||
|
||||
| # | step | what happens | why it is here |
|
||||
|---|---|---|---|
|
||||
| 13 | the shared base is built | the toolchain and runtime every module with code of its own stands on | nothing else with code can be built until it exists |
|
||||
| 14 | a store module is built and run | a database **provider**, which the substrate's store is not | the substrate's store is the control plane's own memory, and offers nothing to anything |
|
||||
| 15 | the catalogue is built and run | the module graph | without it the mesh cannot say what it holds, what a change reaches, or what must be rebuilt |
|
||||
| 16 | the catalogue asks for what it missed | the builds made before it existed are replayed | on a fresh mesh those are always the base, the store and the catalogue itself ([issue 050](../../04-ISSUES/050-the-catalogue-knows-nothing-built-before-it/00-report.md)) |
|
||||
| 17 | the control plane is rebuilt from its own repository | and rolled out through the module path | the moment the mesh stops depending on the installer for anything |
|
||||
| 18 | `networking` is assigned **and the node placed** | a private network, and names | assigning installs the module; placing says where this machine is on it. Both, or the names file is written empty |
|
||||
| 19 | the packet filter is assigned | rules generated from what modules declared | until this, every rule the mesh computes has never been applied to anything |
|
||||
|
||||
## Phase three — machines arrive
|
||||
|
||||
Only now. A machine joining a mesh that cannot build anything proves that enrolment works, which
|
||||
was never the doubtful part.
|
||||
|
||||
| # | step | what happens |
|
||||
|---|---|---|
|
||||
| 20 | a node record is made and a token issued | one-time, carrying the broker's address and the fingerprint to pin |
|
||||
| 21 | the machine enrols and runs its agent | being heard from once is not an agent running; both are checked |
|
||||
| 22 | it is placed on the private network | or nothing can bind a consumer on it to a provider elsewhere |
|
||||
| 23 | modules are assigned to it | and it pulls what it needs from the mesh's registry |
|
||||
|
||||
## What is not yet true
|
||||
|
||||
Stated plainly, because a procedure that implies otherwise is worse than none.
|
||||
|
||||
**Steps 13 to 19 are not the installer's.** They are things somebody types. The installer ends at
|
||||
step 12, and everything that turns a running mesh into a working one is manual — which is why a
|
||||
test had to be written to find out they were missing.
|
||||
|
||||
**Step 23 does not work for a second machine.** It has no account on the registry
|
||||
([issue 042](../../04-ISSUES/042-nothing-gives-a-node-an-account-for-a-registry/00-report.md)) and
|
||||
no reason to trust a registry serving plain HTTP over the network
|
||||
([issue 048](../../04-ISSUES/048-nothing-makes-a-machine-trust-the-mesh-registry/00-report.md)).
|
||||
Both are invisible on a mesh of one, where the registry is loopback.
|
||||
|
||||
**There is no private package registry**, and [ADR 0014](../../02-DECISIONS/0014-no-npm-workspace.md)
|
||||
assumes one: a module consumes its dependencies, the mesh's own shared library included, from it.
|
||||
At step 13 nothing has installed one, so the first build of the shared base resolves the SDK some
|
||||
other way — today by a git URL, which is
|
||||
[issue 053](../../04-ISSUES/053-the-sdk-is-pinned-twice-and-the-two-disagree/00-report.md).
|
||||
|
||||
**The host does not survive a reboot.** The installer refuses to invent a unit file, and the way it
|
||||
is started otherwise does not come back. Every container returns; the agent does not — so the
|
||||
machine runs the right things and can no longer be told anything.
|
||||
|
||||
**Nothing asserts a mesh was installed this way.** A claim that a machine was brought up by this
|
||||
procedure cannot be contradicted by anything afterwards.
|
||||
@@ -30,6 +30,7 @@ document is written and this one's status becomes `implemented`.
|
||||
| [`18-building-a-module.md`](18-building-a-module.md) | How a build is modelled, and why a recipe that is always a Dockerfile does not fit what a module is | [ADR 0040](../../02-DECISIONS/0040-what-a-module-is.md), [ADR 0039](../../02-DECISIONS/0039-what-the-sdk-holds-and-refuses.md), [ADR 0009](../../02-DECISIONS/0009-modules-and-the-graph.md) |
|
||||
| [`19-the-module-protocol.md`](19-the-module-protocol.md) | What a module's code and the mesh say to each other; an SDK is an implementation of it | [ADR 0074](../../02-DECISIONS/0074-the-wire-is-specified-not-the-types.md), [ADR 0042](../../02-DECISIONS/0042-the-shape-of-an-event-on-the-wire.md), [ADR 0043](../../02-DECISIONS/0043-a-module-broker-account-is-scoped-by-emits-and-consumes.md) |
|
||||
| [`20-writing-a-module.md`](20-writing-a-module.md) | A worked guide: one module, four capabilities, four languages, and the packages it publishes | [ADR 0074](../../02-DECISIONS/0074-the-wire-is-specified-not-the-types.md), [ADR 0040](../../02-DECISIONS/0040-what-a-module-is.md), [ADR 0039](../../02-DECISIONS/0039-what-the-sdk-holds-and-refuses.md) |
|
||||
| [`21-the-installation-in-full.md`](21-the-installation-in-full.md) | Every step from a bare machine to a mesh that maintains itself, and what is not yet true | [ADR 0067](../../02-DECISIONS/0067-genesis-is-a-pivot.md), [ADR 0073](../../02-DECISIONS/0073-the-installer-carries-a-builder.md), [ADR 0014](../../02-DECISIONS/0014-no-npm-workspace.md) |
|
||||
|
||||
## Not yet written
|
||||
|
||||
|
||||
Reference in New Issue
Block a user