ADR 0100 (proposed): a node in use is adopted before it is converged #74

Merged
jschoubben merged 6 commits from feat/adoption-mode into main 2026-09-22 14:35:32 +00:00
7 changed files with 70 additions and 10 deletions
Showing only changes of commit 111456abb5 - Show all commits
@@ -190,7 +190,7 @@ running the predecessor is **adopted** when the mesh comes up on it — the pred
stopped, the machine's firewall and files kept in force, the mesh opening what it needs through
them — and stays adopted while its modules migrate one at a time, until the operator **converges**
it. Measured on the control-node, and weighed against the alternatives, in
[*migrating a node that is in use*](migrating-a-node-in-use.md); proposed as
[*migrating a node that is in use*](migrating-a-node-in-use.md); decided in
[ADR 0100](../../02-DECISIONS/0100-a-node-in-use-is-adopted-before-it-is-converged.md).
## Open questions
@@ -204,7 +204,7 @@ it. Measured on the control-node, and weighed against the alternatives, in
| Can a module say which of its settings are load-bearing? | The question that dissolves the conflict rule rather than choosing a side. A setting the module *requires* cannot be kept from the machine without producing something installed and broken; a setting it merely *prefers* should always yield. Until a module can say which is which, adoption is defaulting in the dark. Belongs with the graph. |
| Does a `failed` line still let adoption complete? | *Flags inform, they do not block* was decided about conflicts, where the mesh chose and the machine works. A failure is *we could not*, which is different in kind — and treating them alike hides the worse one behind the commoner one. |
| How is a flagged conflict reconciled, and by whom? | The briefing hands it to a session. What that session is empowered to change, and whether the resolution is recorded so the next adoption does not re-raise it, is undecided. |
| ~~How long is a machine adopted?~~ | **Proposed** ([ADR 0100](../../02-DECISIONS/0100-a-node-in-use-is-adopted-before-it-is-converged.md)) — for as long as it is being migrated: a mode per node, recorded, ended by an explicit and previewed flip. |
| ~~How long is a machine adopted?~~ | **Decided** ([ADR 0100](../../02-DECISIONS/0100-a-node-in-use-is-adopted-before-it-is-converged.md)) — for as long as it is being migrated: a mode per node, recorded, ended by an explicit and previewed flip. |
| Where does the kept original live, and for how long? | Whether it is recorded in the node's state so adoption is visibly reversible, and whether it is returned when the mesh stops managing the thing. |
| What shape is a briefing? | Structured enough to be acted on, prose enough to be read. It is the first thing a session on a new node sees, which makes it an interface rather than a log. |
| Does owning a package mean owning its version? | Owning configuration and owning the package are different scopes. The second means the mesh decides which version is installed, and that decision then has to survive the machine's own package manager updating it. |
@@ -1,6 +1,6 @@
---
topic: the mesh
status: proposed
status: accepted
date: 2026-09-22
deciders: jochen
reconstructed: false
+1 -1
View File
@@ -88,7 +88,7 @@ python3 00-META/checks/index.py fail if stale
- **0083** — [One push leaves the mesh consistent](0083-one-push-leaves-the-mesh-consistent.md)
- **0088** — [The foundation filters before anything listens](0088-the-foundation-filters-before-anything-listens.md)
- **0090** — [A failure that repeats is said to be stuck](0090-a-failure-that-repeats-is-said-to-be-stuck.md)
- **0100** — [A node in use is adopted before it is converged](0100-a-node-in-use-is-adopted-before-it-is-converged.md) *(proposed)*
- **0100** — [A node in use is adopted before it is converged](0100-a-node-in-use-is-adopted-before-it-is-converged.md)
### Its tiers, from the bottom up
+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