From 37252f9c3ebbf987638ca36e2342285f6764f24e Mon Sep 17 00:00:00 2001 From: jochen Date: Tue, 22 Sep 2026 16:34:53 +0200 Subject: [PATCH] ADR 0100: the guard lets the machine itself through; in use is a non-loopback listener; openings say from where; 09 in step with the flip --- .../migrating-a-node-in-use.md | 7 ++-- ...n-use-is-adopted-before-it-is-converged.md | 39 +++++++++++-------- 03-DESIGN/01-to-be/07-the-foundation.md | 2 +- 03-DESIGN/01-to-be/08-connectivity.md | 18 +++++---- 03-DESIGN/01-to-be/09-the-node-lifecycle.md | 12 +++--- 03-DESIGN/01-to-be/17-raising-a-mesh.md | 4 +- 6 files changed, 47 insertions(+), 35 deletions(-) diff --git a/01-RESEARCH/012-the-minimum-viable-node/migrating-a-node-in-use.md b/01-RESEARCH/012-the-minimum-viable-node/migrating-a-node-in-use.md index c0a8013..ee05d80 100644 --- a/01-RESEARCH/012-the-minimum-viable-node/migrating-a-node-in-use.md +++ b/01-RESEARCH/012-the-minimum-viable-node/migrating-a-node-in-use.md @@ -117,13 +117,14 @@ intention, and each thing the mesh would otherwise take must say what it does in module's data has moved. Assigning prepares; taking migrates. Without the distinction the rule never fires: the host only ever sees what assigned modules declare. - **The firewall found on the machine stays in force.** The mesh loads no table on an adopted node - that drops by default or accepts. What it needs open it declares as openings the host converges + that drops by default or holds an accept. What it needs open it declares as openings the host converges *through the found firewall*, on the incoming and the forwarded path, marked as the mesh's and re-checked on every reconcile so a reload or reboot does not lose them. An accept in a table of its own would not help: the found firewall's drop would still be final. What a table of its own *can* do is refuse, and a refusal is final too — so the mesh guards the store and the broker's - management port from outside the private network in a table that only refuses, which the found - firewall may not do and cannot undo. The bus, the registry and the hub's port stay open to + management port from everyone but the private network and the machine itself, in a table that + only refuses, ahead of the container runtime's redirect — which the found firewall does not do + and cannot undo. The bus, the registry and the hub's port stay open to anywhere: a node enrols before it has a private-network address. - **The foundation's ports are the node's to give** — set at genesis, checked free, and kept as that node's settings, read everywhere they are used, so adopting the foundation as modules does diff --git a/02-DECISIONS/0100-a-node-in-use-is-adopted-before-it-is-converged.md b/02-DECISIONS/0100-a-node-in-use-is-adopted-before-it-is-converged.md index fd1df4a..20bc719 100644 --- a/02-DECISIONS/0100-a-node-in-use-is-adopted-before-it-is-converged.md +++ b/02-DECISIONS/0100-a-node-in-use-is-adopted-before-it-is-converged.md @@ -77,9 +77,9 @@ have been taken on it. A node stays adopted until the operator converges it. An said to be adopted wherever the mesh reports a node's state. **A converged genesis refuses a machine in use.** A machine is in use when a container is running -on it or a port is listening that is neither ssh nor one of the operating system's own services. -Raised without saying adopted on such a machine, genesis refuses and names what it found — a -forgotten flag must not close a working machine. +on it, or a port is listening on an address other than loopback that is not ssh's. Raised without +saying adopted on such a machine, genesis refuses and names every container and listener it +counted — a forgotten flag must not close a working machine. **Before a node is adopted, its predecessor's control is stopped by the operator** — the daemons that write its configuration. Its services keep running on what they have. @@ -99,11 +99,13 @@ rewritten, a container stopped or replaced — is reported as changed by somethi or restarted: that is how a predecessor still writing is caught. **The firewall found on the machine stays in force.** The mesh loads no table on an adopted node -that drops by default or that accepts — neither genesis's base ruleset nor the filter module's +that drops by default or holds an accept — neither genesis's base ruleset nor the filter module's derived one. What the mesh needs reachable is declared as **openings**: a resource that says a port is reachable, from where, on the incoming path or the forwarded path — a published container port is -forwarded. The controller derives them from the same inputs as the filter: the `listens` of the -modules assigned there, the private network's hub port, and the foundation's ports. The host +forwarded. The controller derives them from the same inputs as the filter, each from where the +filter would admit it: the `listens` of the modules assigned there, the private network's hub port +and the bus and the registry from anywhere, the store's port and the broker's management port from +the private network. The host converges an opening through the found firewall in that firewall's own terms, marks it as the mesh's, and removes only what it marked; it re-checks each opening on every reconcile, so a reload or a reboot of the found firewall does not lose it for longer than one reconcile. An opening is a @@ -111,12 +113,14 @@ state, not a command, which is what lets it travel over the link. The host repor it found. A machine with no firewall needs no openings; a machine with a kind no host speaks is refused adoption. -**The mesh guards its own ports itself, in a table of its own that only refuses.** It accepts by -default and holds nothing but refusals, so it cannot close anything the machine serves — a drop in -any chain is final and an accept in it would change nothing — and it is the mesh's, so the found -firewall reloading does not touch it. It refuses the store's port and the broker's management port -from anywhere but the private network, matched on the port the packet was sent to, before the -container runtime redirects it. The bus and the registry stay reachable from anywhere, as a node +**The mesh guards its own ports itself, in a table of its own that only refuses.** It passes +everything by default and holds nothing but refusals, and the two ports it refuses are the +foundation's own, checked free at genesis, so it cannot close anything the machine serves; it is +the mesh's, so the found firewall reloading does not touch it. It refuses the store's port and the +broker's management port except from the private network and from the machine itself — its +loopback and the container runtime's own networks — at the prerouting hook, ahead of the runtime's +destination translation, so it matches the port the packet was sent to, for both address +families. The bus and the registry stay reachable from anywhere, as a node enrols over the bus and pulls from the registry before it has a private-network address ([ADR 0088](0088-the-foundation-filters-before-anything-listens.md)); so does the private network's hub port. The store is unreachable from outside whatever the found firewall does, and on a machine @@ -134,11 +138,12 @@ checks that too. found container: each service is taken on its own, when its data has moved, never by the flip. The preview lists what is reachable on the machine now — every listening socket and every published container port — and for each whether an assigned module declares it or it will close, and every -module the flip will take, with what each will replace. The flip then takes those modules, loads the +module the flip will take, with the held files each will replace. The flip then takes those modules, loads the mesh's derived filter in place of its refusal-only table, and retires the found firewall by disabling it, never by flushing: the container runtime's rules and the found firewall's own configuration stay on disk. Returning a converged node to adopted unloads the derived filter, -restores the refusal-only table and enables the found firewall again; what was taken stays taken. A +restores the refusal-only table, enables the found firewall again and converges the openings +through it once more; what was taken stays taken. A node converges when its migration is done; the mesh is migrated when every node has converged. **The order is the operator's:** the control-node first, adopted, its modules assigned and taken @@ -179,15 +184,15 @@ port and denying the rest, a service container listening on that port under a na module also uses, a file at a path that module declares, a stand-in for the predecessor's control that would rewrite that file, and a container holding the registry's port. Then: -- **Genesis converged** on it refuses and names the running container and the listener. +- **Genesis converged** on it refuses and names every container and listener it counted. - **Genesis adopted, with the registry's port held**, refuses and names the holder; with another port given, the foundation comes up — and adopting the foundation as modules leaves it on that port. - **Nothing that serves changed**: the service is reachable from a second machine, the file is byte for byte what it was, and the found firewall's rules differ only by rules marked as the mesh's. - **The store is unreachable from outside** — probed from a machine off the private network, and - again after the found firewall is reloaded — and reachable over it; the bus is reachable from a - machine that has not yet enrolled. + again after the found firewall is reloaded — and reachable over it and from a container on the + node itself; the bus is reachable from a machine that has not yet enrolled. - **The mesh works through the found firewall, and keeps working after it is reloaded and after the machine reboots**: the second machine enrols, and the openings are there again. - **A predecessor still writing is caught**: with the stand-in left running, the held file's change diff --git a/03-DESIGN/01-to-be/07-the-foundation.md b/03-DESIGN/01-to-be/07-the-foundation.md index 3033842..d80a0e6 100644 --- a/03-DESIGN/01-to-be/07-the-foundation.md +++ b/03-DESIGN/01-to-be/07-the-foundation.md @@ -179,7 +179,7 @@ machine already serving under a predecessor has a firewall of its own, and a sec table would close everything it serves. There the base ruleset is not loaded and the filter module is not assigned until the node converges; the foundation's ports are opened through the found firewall, and a table of the mesh's that only refuses keeps the store's port and the broker's -management port from anyone off the private network — the same promise, the store's port never +management port from anyone but the private network and the machine itself — the same promise, the store's port never answering from outside, kept by other means. The bus and the registry stay reachable from anywhere, as they are here, because a node enrols and pulls before it has a private-network address. The foundation's ports themselves are the node's, given at genesis and kept as its settings. **Checked** diff --git a/03-DESIGN/01-to-be/08-connectivity.md b/03-DESIGN/01-to-be/08-connectivity.md index 31db740..ed52fa0 100644 --- a/03-DESIGN/01-to-be/08-connectivity.md +++ b/03-DESIGN/01-to-be/08-connectivity.md @@ -537,21 +537,25 @@ 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 there that drops by default or that accepts — neither genesis's base ruleset nor +loads no table there that drops by default or holds an accept — neither genesis's base ruleset nor the derived one. Every base chain at a hook runs and a drop in any is final, whatever the other firewall is written in, so a second, stricter table would close every port the machine serves, and an accept in one would open nothing the found firewall drops. What the mesh needs reachable it declares as **openings**: a port, from where, on the incoming path or the forwarded path — a published container port is forwarded, and a firewall that filters only incoming traffic never -sees it. The controller derives them from what the filter would be derived from: the assigned -modules' `listens`, the hub's port, the foundation's ports. The host converges each opening through +sees it. The controller derives them from what the filter would be derived from, each from where +the filter would admit it: the assigned modules' `listens`, the hub's port, the bus and the registry +from anywhere; the store's port and the broker's management port from the private network. The host converges each opening through the found firewall in that firewall's own terms, marks it as the mesh's, removes only what it marked, and re-checks every opening on each reconcile so a reload or a reboot does not lose it for longer than one reconcile. An opening is state, not a command, so it travels over the link like any other resource. **The mesh guards its own ports in a table of its own that only refuses** — -accepting by default and holding nothing but refusals, so it cannot close what the machine serves, -and the found firewall's reload does not touch it. It refuses the store's port and the broker's -management port from outside the private network, matched on the port the packet was sent to; the +passing everything by default and holding nothing but refusals of the foundation's own two ports, +checked free at genesis, so it cannot close what the machine serves, and the found firewall's +reload does not touch it. It refuses the store's port and the broker's management port except from +the private network and the machine itself — loopback and the container runtime's networks — at +the prerouting hook, before the container runtime redirects the packet, so it matches the port the +packet was sent to, for both address families; the bus, the registry and the hub's port stay reachable from anywhere, as a node enrols and pulls before it has a private-network address. One kind of found firewall is spoken; a machine with none needs no openings, and a machine with another kind is refused adoption. Converging the node refuses @@ -560,7 +564,7 @@ sockets and published ports — what will close and which modules it will take, derived filter in place of the refusal-only table and disables the found firewall without flushing it. *How it is checked:* the adoption bed asserts the found firewall's rules differ only by the mesh's marked rules, that the store is unreachable from off the private network before and after -the found firewall reloads, that a machine not yet enrolled reaches the bus, that a second machine +the found firewall reloads and reachable from a container on the node, that a machine not yet enrolled reaches the bus, that a second machine enrols through the openings before and after a reload and a reboot, and that after the flip the declared port is open and the undeclared one closed. diff --git a/03-DESIGN/01-to-be/09-the-node-lifecycle.md b/03-DESIGN/01-to-be/09-the-node-lifecycle.md index 6b50933..fc6d16e 100644 --- a/03-DESIGN/01-to-be/09-the-node-lifecycle.md +++ b/03-DESIGN/01-to-be/09-the-node-lifecycle.md @@ -297,11 +297,13 @@ the others — and the controller records it and says so in every declaration, w **taken** on that node. On an adopted node what is found is held until its module is taken ([05-the-node-host](05-the-node-host.md)): assigning a module prepares it, taking it is its cutover. The firewall found there stays in force and the mesh opens what it needs through it -([08-connectivity](08-connectivity.md)). **Converging is one act per node, previewed**: it lists -what is reachable on the machine now — listening sockets and published ports — and whether an -assigned module declares each or it will close, then takes every module not yet taken, loads the -mesh's own filter and disables the found one without flushing it. Returning a converged node to -adopted enables the found firewall again. *How it is checked:* a lab bed prepares a machine the way +([08-connectivity](08-connectivity.md)). **Converging is one act per node, previewed**: it refuses +while an assigned module still holds a found container; otherwise it lists what is reachable on the +machine now — listening sockets and published ports — whether an assigned module declares each or +it will close, and which modules it will take, then takes them, loads the mesh's own filter in place +of its refusal-only table and disables the found firewall without flushing it. Returning a +converged node to adopted unloads the derived filter, restores the refusal-only table, enables the +found firewall again and converges the openings through it; what was taken stays taken. *How it is checked:* a lab bed prepares a machine the way a predecessor leaves one and asserts nothing that serves changes until a module is taken or the node is converged, and that the flip closes exactly what the preview said. diff --git a/03-DESIGN/01-to-be/17-raising-a-mesh.md b/03-DESIGN/01-to-be/17-raising-a-mesh.md index 429d874..a9d062b 100644 --- a/03-DESIGN/01-to-be/17-raising-a-mesh.md +++ b/03-DESIGN/01-to-be/17-raising-a-mesh.md @@ -113,8 +113,8 @@ checks the private network's range does not overlap a tunnel the predecessor run **does not load the base ruleset**: the machine's own firewall already filters; the mesh opens its foundation's ports through it and keeps the store from outside with a table of its own that only refuses ([08-connectivity](08-connectivity.md)). **A converged genesis refuses a machine in use** — -a container running, or a port listening that is neither ssh nor the operating system's own — so a -forgotten flag cannot close a working machine. *How it is checked:* the adoption bed raises genesis converged on a machine in use and +a container running, or a port listening on an address other than loopback that is not ssh's — +and names every one it counted, so a forgotten flag cannot close a working machine. *How it is checked:* the adoption bed raises genesis converged on a machine in use and asserts the refusal, then adopted with the registry's port held and asserts the refusal names its holder, then with another port given asserts the foundation comes up, stays on that port once adopted as modules, and the machine's service is still reachable.