|
|
|
@@ -0,0 +1,192 @@
|
|
|
|
|
---
|
|
|
|
|
layer: to-be
|
|
|
|
|
status: designed
|
|
|
|
|
code: []
|
|
|
|
|
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
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
# The node host
|
|
|
|
|
|
|
|
|
|
Tier 0. The one thing ever installed by hand, and the only thing that changes a machine.
|
|
|
|
|
|
|
|
|
|
## What it is
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
**Open, and the first thing to settle in build.** The shape is constrained but not chosen:
|
|
|
|
|
|
|
|
|
|
- It is data, not instructions — the host's vocabulary is finite, versioned and auditable, and
|
|
|
|
|
anything outside it is refused rather than best-effort interpreted.
|
|
|
|
|
- It is per-node and complete: what this machine should be, not a delta against what it was.
|
|
|
|
|
A delta requires the sender to know what the receiver holds, which is the coupling the store
|
|
|
|
|
exists to remove.
|
|
|
|
|
- Every addition to the vocabulary widens what a compromised control plane can express, so it
|
|
|
|
|
is a security artefact and additions are reviewed as such.
|
|
|
|
|
|
|
|
|
|
## 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.
|
|
|
|
|
|
|
|
|
|
**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
|
|
|
|
|
|
|
|
|
|
- **What a declaration is.** Above; the first thing to settle.
|
|
|
|
|
- **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)).
|