diff --git a/03-DESIGN/01-to-be/21-the-installation-in-full.md b/03-DESIGN/01-to-be/21-the-installation-in-full.md new file mode 100644 index 0000000..d3c7d91 --- /dev/null +++ b/03-DESIGN/01-to-be/21-the-installation-in-full.md @@ -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. diff --git a/03-DESIGN/01-to-be/README.md b/03-DESIGN/01-to-be/README.md index b3e2835..ebc054a 100644 --- a/03-DESIGN/01-to-be/README.md +++ b/03-DESIGN/01-to-be/README.md @@ -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