Asked whether postgres has to be installed, and the answer exposed a missing step. The store is a container, so something must run containers before anything else happens — and a container runtime is a PACKAGE, not a container. Step 0 is where several threads meet. It is what the host's capability detection already reports, and the first use of that report by something other than a person. It is adopted rather than installed when the machine already has a runtime with configuration somebody chose. And it is a package, needing the machine's own package manager and a network, both of which ADR 0046 permits. So the host's bootstrap vocabulary is six shapes: package, container, file, directory, service, action. Stage 2 built three of them. The node host design now names which three remain and why the lab cannot yet exercise them — a sealed scenario fetches nothing and its machines carry no container runtime, which is lab-installation work rather than a constraint on the design, because production machines have a network.
221 lines
11 KiB
Markdown
221 lines
11 KiB
Markdown
---
|
|
layer: to-be
|
|
status: in-progress
|
|
code: [mesh-host]
|
|
updated: 2026-08-26
|
|
decisions:
|
|
- 02-DECISIONS/0030-the-repository-structure.md
|
|
- 02-DECISIONS/0036-a-node-is-a-managed-machine.md
|
|
- 02-DECISIONS/0037-the-host-applies-it-does-not-decide.md
|
|
- 02-DECISIONS/0038-a-node-joins-by-linking-first.md
|
|
- 02-DECISIONS/0039-the-link-is-the-security-boundary.md
|
|
- 02-DECISIONS/0008-a-failed-step-fails-the-job.md
|
|
- 02-DECISIONS/0041-the-host-depends-on-nothing.md
|
|
- 02-DECISIONS/0043-a-declaration-is-an-ordered-list-of-owned-resources.md
|
|
---
|
|
|
|
# The node host
|
|
|
|
Tier 0. The one thing ever installed by hand, and the only thing that changes a machine.
|
|
|
|
## What it is
|
|
|
|
A **statically linked binary that requires nothing to be present** — copy it onto a machine and
|
|
run it, and that is the whole installation
|
|
([ADR 0041](../../02-DECISIONS/0041-the-host-depends-on-nothing.md)). Written in Go, because the
|
|
job is system-level and because the host shares no code with any other tier.
|
|
|
|
A single binary with one job: **apply declared state on this machine**
|
|
([ADR 0037](../../02-DECISIONS/0037-the-host-applies-it-does-not-decide.md)). Overlay
|
|
membership, packet filtering, packages, services, containers and filesystems are not six
|
|
concerns it carries; they are six instances of the one.
|
|
|
|
It replaces three things that exist today
|
|
([`00-as-is/05`](../00-as-is/05-runtime-and-installation.md)): the three hand-run bootstrap
|
|
scripts — first node, joining, rescue — and the synchronisers that rewrite managed files
|
|
([`00-as-is/06`](../00-as-is/06-configuration-and-secrets.md)).
|
|
|
|
**What it is not:** it does not decide anything that needs another node, it never queries the
|
|
mesh database, and it has no listening surface.
|
|
|
|
## The parts
|
|
|
|
| Part | Owns |
|
|
|---|---|
|
|
| `apply` | reconciling declared state on this machine |
|
|
| `store` | local state, authoritative while disconnected |
|
|
| `link` | the single outbound connection to the control plane |
|
|
| `profile` | what this machine can be asked to do |
|
|
| `inventory` | what this machine is and has |
|
|
| `substrate.lock` | the pinned tier-1 descriptor, appliable with no mesh present |
|
|
|
|
### apply
|
|
|
|
Takes a **declaration** and makes the machine match it. Idempotent: applying the same
|
|
declaration twice changes nothing the second time, and applying it to a drifted machine
|
|
returns it.
|
|
|
|
Three properties, each following a recorded decision:
|
|
|
|
**A failed step fails the apply.** Not "logs and continues"
|
|
([ADR 0008](../../02-DECISIONS/0008-a-failed-step-fails-the-job.md)). A partial apply that
|
|
reports success is the mesh's most expensive shape.
|
|
|
|
**Each applier reads back.** Setting a value is not evidence the value took. The firewall is
|
|
asked whether the rule loaded; conntrack is asked what timeout it holds. This is `how-we-build`
|
|
§5 as a component requirement rather than a review habit.
|
|
|
|
**What was applied is recorded after it works, never before**
|
|
([ADR 0035](../../02-DECISIONS/0035-a-picture-is-read-from-what-runs.md)). A failed apply leaves
|
|
the machine in whatever state it reached, and nothing must claim otherwise.
|
|
|
|
### store
|
|
|
|
Local, and **authoritative while disconnected**. Not a cache of the control plane — the record
|
|
of what this node has applied and what it currently holds.
|
|
|
|
This is structural rather than convenient: if disconnection is an ordinary situation rather
|
|
than an exception ([ADR 0036](../../02-DECISIONS/0036-a-node-is-a-managed-machine.md)), the
|
|
store is what makes it ordinary. A laptop shut for a week comes back and reconciles; it does
|
|
not come back and ask what it is.
|
|
|
|
### link
|
|
|
|
The node's one connection to the control plane, and its security boundary
|
|
([ADR 0039](../../02-DECISIONS/0039-the-link-is-the-security-boundary.md)).
|
|
|
|
It is the broker connection that already exists
|
|
([ADR 0001](../../02-DECISIONS/0001-nodes-communicate-over-a-broker.md)) — outbound,
|
|
node-initiated, per-node addressed — carrying **per-node identity instead of a shared
|
|
credential**. The node owns no password. It owns an identity, and that identity is what it
|
|
presents.
|
|
|
|
What arrives is bounded by form: **declarations of known shape, never a command to run.**
|
|
|
|
**It must fail legibly.** A node that cannot link says which side refused and on what grounds.
|
|
A boundary that refuses without saying why is worse than the password it replaced.
|
|
|
|
### profile
|
|
|
|
What this machine can be asked to do — a graphical session, a container runtime, an
|
|
architecture, a network position.
|
|
|
|
**Detected, never assumed.** A package being installed does not mean a capability is present
|
|
([04-ISSUES/007](../../04-ISSUES/007-an-installed-package-is-not-a-capability/00-report.md)): a
|
|
capability is real when it is present, running and working, and the difference is the whole
|
|
point of detecting it.
|
|
|
|
The profile is what makes [ADR 0036](../../02-DECISIONS/0036-a-node-is-a-managed-machine.md)
|
|
work: a node is a node, and what varies between them is here rather than in the definition.
|
|
|
|
### inventory
|
|
|
|
What this machine *is* — its identity, what it holds, what it has applied. Reported upward over
|
|
the link; never asked downward.
|
|
|
|
## Where a declaration comes from
|
|
|
|
One behaviour, two sources
|
|
([ADR 0038](../../02-DECISIONS/0038-a-node-joins-by-linking-first.md)):
|
|
|
|
| Situation | Source |
|
|
|---|---|
|
|
| no mesh reachable | `substrate.lock` — the pinned bundle the host carries |
|
|
| mesh reachable | the control plane, over the link |
|
|
|
|
**The first node is not a different kind of node.** It is a node whose mesh is not up yet. It
|
|
applies the bundle it carries, the control plane comes up on top of it, and from that moment it
|
|
takes declarations like every other node. Its specialness is temporary and self-erasing.
|
|
|
|
**A joining node does the minimum to be reachable and nothing else** — an identity, an address,
|
|
one peer — and then stops deciding. It does not compute the overlay; it needs one peer to reach
|
|
the mesh, and the full peer set arrives derived.
|
|
|
|
## What a declaration is
|
|
|
|
Settled by [ADR 0043](../../02-DECISIONS/0043-a-declaration-is-an-ordered-list-of-owned-resources.md).
|
|
|
|
**JSON**, because the host has no dependencies to spend and the standard library carries no
|
|
YAML. **An ordered list of typed resources**, each with a stable identity — the order is stated
|
|
rather than derived, because deriving it would be the host deciding the thing most likely to
|
|
differ from what the control plane intended.
|
|
|
|
**Unknown is refused, never skipped.** An unknown version, type or field refuses the whole
|
|
declaration. A host that skipped what it did not understand would apply most of it and report
|
|
success.
|
|
|
|
**Complete for what the host owns, and only that.** It removes what it previously applied and
|
|
is no longer declared — a fact it holds, from the store, rather than an inference — and never
|
|
removes anything it did not create.
|
|
|
|
**Addressed.** A host with an identity refuses a declaration addressed elsewhere; a host
|
|
without one, applying the bundle it carries, has nothing to check against.
|
|
|
|
## Build order
|
|
|
|
Staged so each stage is verifiable in the lab before the next exists.
|
|
|
|
**1 — profile and inventory.** The host runs on a machine, detects what it can do, and reports
|
|
what it is. No control plane, no declarations, no network. Verifiable immediately: the lab's
|
|
`place:` gains its first implementation, and a raised scenario finally contains something.
|
|
|
|
**2 — apply, from the bundle.** The host applies `substrate.lock` with no mesh present. This is
|
|
the first node's path, and it is the claim the skeleton's Move 1 rests on and has never proved:
|
|
that one host can raise the substrate alone.
|
|
|
|
Raising the substrate needs six shapes in the host's vocabulary, and stage 2 built three:
|
|
|
|
| | |
|
|
|---|---|
|
|
| `directory`, `file`, `service` | **built** |
|
|
| `package` | a container runtime must exist before a container can run |
|
|
| `container` | pulled by digest ([ADR 0046](../../02-DECISIONS/0046-the-installer-fetches-what-it-pins.md)) |
|
|
| `action` | provisioning steps the bundle declares and the host verifies ([ADR 0047](../../02-DECISIONS/0047-the-bundle-may-carry-actions-the-link-may-not.md)) |
|
|
|
|
The lab cannot yet exercise the last three: a scenario is a closed address space, so nothing can
|
|
be fetched there, and its machines carry no container runtime. That is lab-installation work
|
|
([`04-lab-installation.md`](04-lab-installation.md)) rather than a constraint on the design —
|
|
production machines have a network.
|
|
|
|
**3 — link and store.** The node connects, receives declarations, and holds what it applied.
|
|
|
|
**4 — enrolment.** The one genuinely new mechanism in
|
|
[ADR 0039](../../02-DECISIONS/0039-the-link-is-the-security-boundary.md); everything else there
|
|
is configuration of what already runs.
|
|
|
|
Stage 1 is deliberately the smallest useful thing. The lab currently raises **empty machines**
|
|
([`00-as-is/11`](../00-as-is/11-the-lab.md)) because `place:` has nothing to place; stage 1 ends
|
|
that, and every later stage is tested by a lab that already works.
|
|
|
|
## How it is verified
|
|
|
|
**The lab is the harness.** A scenario places a host on a machine and asserts what it did —
|
|
against a real hypervisor, with the boundary never mocked
|
|
([ADR 0034](../../02-DECISIONS/0034-a-test-defends-a-decision.md)).
|
|
|
|
Each decision above owes a test:
|
|
|
|
| Decision | What asserts it |
|
|
|---|---|
|
|
| 0037 — the host never queries the mesh database | no database client in the dependency tree; a dependency-direction lint failing on an upward import |
|
|
| 0038 — one behaviour, two sources | the same code path raises a first node and joins a second |
|
|
| 0039 — a node holds no shared credential | a raised node's store contains no credential to any service |
|
|
| 0036 — disconnection is a situation | a node cut off and returned reconciles without being re-adopted |
|
|
| 0008 — a failed step fails the apply | an apply with a failing step reports failure |
|
|
|
|
## Open
|
|
|
|
- **Whether one host can raise the substrate alone.** Move 1 assumes it. Stage 2 tests it, and
|
|
if it is false the tier boundary moves.
|
|
- **What the host carries versus what it finds.** It manages `wg`, `nft`, `pacman`, `docker`;
|
|
it does not contain them, and how it obtains one it lacks is undecided —
|
|
[04-ISSUES/007](../../04-ISSUES/007-an-installed-package-is-not-a-capability/00-report.md).
|
|
- **Six vocabularies.** Zero dependencies, but the host must still know what a peer, a rule, a
|
|
package, a unit, a container and a dataset *are*. Nothing has measured that surface, and it is
|
|
the residue of the question [`host-size.md`](../../01-RESEARCH/006-mesh-from-scratch/host-size.md)
|
|
answered.
|
|
- **Rescue.** [ADR 0038](../../02-DECISIONS/0038-a-node-joins-by-linking-first.md) suggests it is
|
|
a node whose local state is discarded so the mesh re-derives it, and does not decide it.
|
|
- **What may expire.** An identity needing refresh to stay valid would make a laptop fail for
|
|
being a laptop ([ADR 0036](../../02-DECISIONS/0036-a-node-is-a-managed-machine.md)).
|