ADR 0100 accepted; the node host, connectivity, the node lifecycle and raising a mesh amended for a node adopted before it is converged

This commit is contained in:
2026-09-22 16:20:27 +02:00
parent 5fc3cbde4c
commit 111456abb5
7 changed files with 70 additions and 10 deletions
+15 -1
View File
@@ -2,8 +2,9 @@
layer: to-be
status: in-progress
code: [mesh-host]
updated: 2026-09-21
updated: 2026-09-22
decisions:
- 02-DECISIONS/0100-a-node-in-use-is-adopted-before-it-is-converged.md
- 02-DECISIONS/0019-how-this-repository-works.md
- 02-DECISIONS/0004-a-node-and-how-it-joins.md
- 02-DECISIONS/0005-the-node-host.md
@@ -130,6 +131,19 @@ the link; never asked downward.
[`09-the-node-lifecycle.md`](09-the-node-lifecycle.md)**, in full and in one place. This document
is the component; that one is what happens to it.
## What it finds, on an adopted node
*2026-09-22, [ADR 0100](../../02-DECISIONS/0100-a-node-in-use-is-adopted-before-it-is-converged.md).* A declaration says whether the node is adopted. On an adopted node
the host does not replace a file it finds at a declared path: it records what was there — its
content, so the original is kept before anything is written — and holds the file as found. A
module's files are converged when that module is assigned, because assigning a module is
migrating it; the rest when the node is converged. The host's report says which files it is
holding rather than converging, so an adopted node never reads as converged. This is the
companion the host's ownership rule needed: *never touch what you did not create, unless adoption
made it yours — and while the node is adopted, keep it as you found it.* *How it is checked:*
unit tests hold the host to keeping a found file on an adopted node and replacing it on a
converged one, and the adoption bed asserts a found file byte for byte unchanged.
## Where a declaration comes from
One behaviour, two sources
+16 -1
View File
@@ -7,8 +7,9 @@ code:
- mesh-controller internal/identity/authority.go
- mesh-host internal/identity/serving.go
- mesh-host internal/apply (the service that reflects a rule set)
updated: 2026-09-21
updated: 2026-09-22
decisions:
- 02-DECISIONS/0100-a-node-in-use-is-adopted-before-it-is-converged.md
- 02-DECISIONS/0099-a-step-that-runs-once-names-what-it-reads.md
- 02-DECISIONS/0098-a-fact-a-provider-makes-at-first-start-is-fetched-from-it.md
- 02-DECISIONS/0005-the-node-host.md
@@ -532,6 +533,20 @@ undeclared one does not — then the module is removed and the port closes with
rule. *A rule set that is written but never loaded passes every check that reads the file, which
is why the check reads packets.*
### On an adopted node
*2026-09-22, [ADR 0100](../../02-DECISIONS/0100-a-node-in-use-is-adopted-before-it-is-converged.md).* **The firewall found on the machine stays in force**, and the mesh
loads no table of its own there — neither genesis's base ruleset nor the derived one. The kernel
runs every table at a hook and a drop in any is final, so a second, stricter table would close
every port the machine serves. What the mesh needs reachable — its foundation's ports and each
migrated module's `listens` — it opens *through the found firewall*, as rules marked as the mesh's,
and it removes only rules it marked. Converging the node replaces the found firewall with the
derived filter in one step, after previewing which open ports will close. The mesh must speak the
found firewall in its own terms; one kind is found on the machines measured, and a machine with
another is not covered until someone writes for it. *How it is checked:* the adoption bed asserts
the found firewall's rules differ only by the mesh's marked rules, that a second machine enrols
through them, and that after the flip the declared port is open and the undeclared one closed.
## 5 — Certificates
**Two authorities, kept separate on purpose.**
+19 -3
View File
@@ -8,8 +8,9 @@ code:
- mesh-host packaging/nox-mesh-host-network.sh
- mesh-controller internal/token
- mesh-controller internal/inventory/nodes.go
updated: 2026-08-31
updated: 2026-09-22
decisions:
- 02-DECISIONS/0100-a-node-in-use-is-adopted-before-it-is-converged.md
- 02-DECISIONS/0004-a-node-and-how-it-joins.md
- 02-DECISIONS/0005-the-node-host.md
- 02-DECISIONS/0004-a-node-and-how-it-joins.md
@@ -282,8 +283,23 @@ runs. Building the start mechanism before deciding that would be building it for
## Adoption: what happens to what is already there
Adoption is not a state. It is what the **first apply** does when it is told to own something a
machine already has ([research 012](../../01-RESEARCH/012-the-minimum-viable-node/00-overview.md)).
**An enrolled node is adopted or converged, and the mesh records which** ([ADR 0100](../../02-DECISIONS/0100-a-node-in-use-is-adopted-before-it-is-converged.md)).
This was once said the other way — adoption as only what the first apply does — and a machine
being migrated from a mesh already running on it made the middle state last: while its services
move one at a time, what it already has must stay in force. So *adopted* is a second axis of an
enrolled node, not a step on the path above: a machine in use is enrolled adopted, and stays so
until its migration is done and the operator converges it. A machine that was empty is enrolled
converged, as before
([research 012](../../01-RESEARCH/012-the-minimum-viable-node/00-overview.md)).
On an adopted node the firewall found there stays in force and the mesh opens what it needs
through it ([08-connectivity](08-connectivity.md)); a file found at a path the mesh declares is
kept until the module declaring it migrates ([05-the-node-host](05-the-node-host.md)).
**Converging is one act per node, previewed**: it lists every port the found firewall allows and
whether an assigned module declares it or it will close, then loads the mesh's own filter, retires
the found one and converges what was kept. *How it is checked:* a lab bed prepares a machine the
way a predecessor leaves one and asserts nothing on it changes until the flip, and that the flip
closes exactly what the preview said.
A candidate machine is not empty. It has a package manager, probably a container runtime,
configuration somebody chose. [ADR 0005](../../02-DECISIONS/0005-the-node-host.md)
+16 -1
View File
@@ -5,8 +5,9 @@ code:
- mesh-host cmd/mesh-bootstrap
- mesh-host internal/bootstrap
- mesh-lab test/integration/whole-mesh-full.test.ts
updated: 2026-09-21
updated: 2026-09-22
decisions:
- 02-DECISIONS/0100-a-node-in-use-is-adopted-before-it-is-converged.md
- 02-DECISIONS/0067-genesis-is-a-pivot.md
- 02-DECISIONS/0069-a-module-is-a-repository-and-a-path.md
- 02-DECISIONS/0006-the-substrate-and-the-control-plane.md
@@ -97,6 +98,20 @@ the temporary one.** The pivot is complete: what raised the mesh is gone, and wh
like any other. From here the mesh can build and roll out its own upgrades, including to the thing
that runs it.
### Genesis on a machine in use
*2026-09-22, [ADR 0100](../../02-DECISIONS/0100-a-node-in-use-is-adopted-before-it-is-converged.md).* A control-node that is already running a predecessor mesh is raised
**adopted**. The predecessor's control on it is stopped first — the daemons that write its
configuration — and its services keep running. **Every port the foundation binds is an input to
genesis**, not a constant in the bundle or the catalogue, and genesis checks each is free before it
raises anything, refusing with the name of what holds it. On such a machine the store's and the
bus's usual ports were free and the registry's, the broker's management port and the private
network's port were held. Genesis adopted **does not load the base ruleset**: the machine's own
firewall already filters, and the mesh opens its foundation's ports through it
([08-connectivity](08-connectivity.md)). *How it is checked:* the adoption bed raises genesis
with the registry's port held and asserts the refusal names its holder, then with another port
given asserts the foundation comes up and the machine's service is still reachable.
## After the pivot, and still part of installing
Genesis ends with a mesh of one that runs, and that is not the same as a mesh that works. What it