Reported as 'the host does not survive a reboot', which reads as a mesh that cannot come back. mesh-host/packaging/ ships nox-mesh-host.service and two companions. The installer declines to place them because a unit file is a packaging decision, and the lab starts the host with --host-in-background, which says in its own help that it does not survive a reboot. So the lab run failing this was the lab being honest, and the gap is the step that puts a shipped unit on a machine — narrower and more fixable than what I wrote. Claude-Session: https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx
123 lines
8.2 KiB
Markdown
123 lines
8.2 KiB
Markdown
---
|
|
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 installer does not install the host's unit, though one exists.** `mesh-host/packaging/` ships
|
|
`nox-mesh-host.service` and two companions; the installer declines to place them because a unit file
|
|
is a packaging decision. So an install that does nothing further leaves a machine whose containers
|
|
come back after a reboot and whose agent does not — it runs the right things and can no longer be
|
|
told anything.
|
|
|
|
**This is a gap in packaging, not in the mesh**, and the distinction matters: the lab's
|
|
`--host-in-background` says in its own help that it does not survive a reboot, so a lab run failing
|
|
this is the lab being honest rather than the mesh being broken. What is missing is the step that
|
|
puts the shipped unit on the machine.
|
|
|
|
**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.
|