Records say what the build does: 0101 names only measured daemons; 0102 adds to lists and keeps what it writes over; 0103 names every held kind, the found-service rule, conflicting found rules, guards, and what of 0100 it replaces; designs 05, 09 and 17 in step; issue 084's diagnosis in its own file

This commit is contained in:
2026-09-22 19:38:16 +02:00
parent ca1f648973
commit c74ea2a4a8
8 changed files with 74 additions and 38 deletions
@@ -42,9 +42,8 @@ habit.
## Decision
**A listener held by one of the operating system's own network daemons does not make a machine
in use.** The daemons are the name resolver, the network manager, the address-configuration
client and the time client, named in the installer's code beside the measurement that found
them. Everything else in ADR 0100's definition stands: a running container, or any other
in use.** The daemons are the ones the measurement found: the name resolver and the network
manager, named in the installer's code beside that measurement. Everything else in ADR 0100's definition stands: a running container, or any other
listener on an address other than loopback that is not ssh's, makes the machine in use, and
genesis still names every one it counted.
@@ -42,10 +42,18 @@ the runtime.
**A file the mesh shares with software it did not install is written into, never over.** A
file resource may say it is written *into* a structured file. The host then reads what is there,
sets only the keys the mesh declares, keeps every other key as it found it, and records what each
of its keys held before. Undeclared later, each key goes back to what it held, and a file the
mesh created is removed only if nothing but its own keys is left. A file written into replaces
nothing, so on an adopted node it is never held: it is written whether or not its module has
been taken.
of its keys held before. **A list is added to, never replaced**: where the machine already has a
list under a key the mesh declares, the mesh's members are added to it and the host records
exactly which members it added — setting the key would replace the operator's own list, the harm
this record exists to prevent. Undeclared later, each key goes back to what it held and each
added member is taken out again, and a file the mesh created is removed only if nothing but its
own keys is left. A file written into replaces nothing, so on an adopted node it is never held:
it is written whether or not its module has been taken.
**Whatever the host writes over without a record of it, it keeps first.** On any node, adopted
or converged, before the host writes a file over one it has no record of making, it keeps the
original once and says where; if it cannot keep it, it does not write. A file the mesh takes over
is then never lost, whatever put it there.
**A service that re-reads its configuration on a reload is reloaded, not restarted.** A service
resource may name what it must be *reloaded* on, beside what it must be restarted on. The
@@ -70,8 +78,9 @@ and stays held until networking is taken; a converge preview names it among the
## How it is checked
Unit tests hold the host to setting only the declared keys and keeping the rest, restoring each
key and removing only a file it created when the resource is undeclared, refusing a file that is
not a JSON object rather than overwriting it, never holding a file written into on an adopted
key and removing only a file it created when the resource is undeclared, adding to a list and
removing only the members it added, keeping the original of a file it writes over without a
record, refusing a file that is not a JSON object rather than overwriting it, never holding a file written into on an adopted
node, and reloading rather than restarting a service whose reload-on resource changed. A test in
the controller holds the networking module to declaring the runtime's file written into and the
runtime reloaded. The adoption lab bed gives the machine a runtime file with a setting of its own
@@ -40,9 +40,14 @@ for a module not yet taken:
- a **directory** present with no record is held: its mode and owner are left, and nothing in it
is touched;
- a **service** whose unit is present with no record is held: its state and whether it starts at
boot are left. A reload named by the module still happens, since a reload stops nothing
([ADR 0102](0102-the-mesh-writes-into-a-shared-file-never-over-it.md));
- a **service** the machine runs — a unit running or enabled at boot — with no record is held: its
state and whether it starts at boot are left. A reload named by the module still happens, since
a reload stops nothing ([ADR 0102](0102-the-mesh-writes-into-a-shared-file-never-over-it.md)).
A unit file a package merely ships, which nothing runs, is not found: the private network's own
tunnel unit is one;
- an **archive** whose target is present with no record is not unpacked or re-owned; a
**process** whose unit file is present with no record is not written over or restarted; a
**user** that exists with no record keeps its shell and groups;
- a **container** not found by its name is still held if it would mount a path or a volume that
is present with no record, because it would share the predecessor's data;
- an **action** or one-off step run *in* a held container is held until the module is taken.
@@ -51,23 +56,34 @@ What is held is reported as held and is never removed, as ADR 0100 decides for f
containers.
**The guard refuses what the found firewall may miss, for taken modules only.** Its ports are
derived, not listed. They are every machine port a *taken* module publishes that the filter
would admit from the private network only, together with the store's port and the broker's
management port. A port of a module that is assigned but not taken is not guarded: it may still
derived from each *taken* module: every machine port it publishes that the filter would admit
from the private network only, and the ports its manifest names under `guards` — which is how
the store's port and the broker's management port are included. Only TCP is guarded. A port of a module that is assigned but not taken is not guarded: it may still
be the predecessor's. The guard matches only packets addressed to this machine. Traffic the
machine routes for others is never its business.
**An opening already answered by a found rule is not added.** If the found firewall already
admits what an opening says, the opening is reported as satisfied by the found rule, and the
mesh adds nothing and later removes nothing. The found firewall treats two rules differing only
in their comment as one rule, so adding the mesh's would take over the operator's.
in their action, log setting or comment as one rule, so adding the mesh's would take over the
operator's. Where the found rule is such a twin but is not a plain allow — a deny, a limit, a
logged allow — the opening is refused, naming that rule, and nothing is added.
**A machine raised adopted stays adopted if genesis is run again.** The installer reads the
node's mode from what the machine records, not only from the operator's flag. A run without
the flag on an adopted machine is refused.
**What of ADR 0100 this replaces.** ADR 0100's guard refused two ports, the foundation's own,
checked free at genesis, and could therefore close nothing the machine served. That guard is
replaced by the one above: a guarded port of a taken module may be one the predecessor served
more widely, and taking the module narrows it to the private network. Everything else in ADR 0100
stands, and its rules for files and containers now cover the other kinds listed here.
## Consequences
- Taking a module can narrow a port the predecessor served to anyone: the guard then refuses it
from outside the private network, as the module declares. Nothing says so yet
([issue 086](../04-ISSUES/086-taking-a-module-narrows-a-port-without-saying-so/00-report.md)).
- An untaken module on an adopted node can come up only beside what was found, never on top of
it: whatever would share the predecessor's data waits for the cutover.
- The guard grows with the modules taken, and a module's published private-network port is
@@ -78,14 +94,15 @@ the flag on an adopted machine is refused.
## How it is checked
Unit tests hold the host to holding a found directory, a found service and a container that
would mount found data, to deferring an action run in a held container, and to adding no
opening a found rule answers. They hold the controller to deriving the guard from taken modules'
Unit tests hold the host to holding a found directory, a found service, a found archive, process
and user, and a container that would mount found data; to not holding a unit nothing runs; to
deferring an action run in a held container; to adding no opening a found rule answers, and to
refusing one a conflicting found rule would absorb. They hold the controller to deriving the guard from taken modules'
private-network published ports, and the guard's text to matching only this machine's
addresses. The installer's tests hold a re-run without the flag on an adopted machine to a
refusal. The adoption lab bed asserts that the broker's plaintext port is unreachable from
outside the private network, and that an operator's own rule for a port the mesh opens
survives the opening being removed.
outside the private network even with the found firewall admitting it, and that an operator's
own rule for a port the mesh opens survives the opening being removed.
## References
+5 -2
View File
@@ -158,8 +158,11 @@ keeps its mode and owner, a unit present with no record keeps its state and boot
container that would mount found data is not created, and an action run in a held container
waits for the cutover. **A file the machine shares is written into, never over**
([ADR 0102](../../02-DECISIONS/0102-the-mesh-writes-into-a-shared-file-never-over-it.md)): the host sets the mesh's keys in the object already there,
keeps every other key, records what each of its keys held before and gives them back when the
file is undeclared. Such a file replaces nothing, so it is never held. A service that re-reads
keeps every other key, adds its members to a list already there rather than replacing it,
records what each of its keys held and which members it added, and gives them back when the
file is undeclared. Such a file replaces nothing, so it is never held. And on any node, before
the host writes over a file it has no record of making, it keeps the original once and names
where; if it cannot keep it, it does not write. A service that re-reads
its configuration is **reloaded** for what it names in `reload-on`, never restarted. *How it is
checked:* unit tests hold the host to each of these, and the adoption bed asserts the runtime's
own settings survive adoption and a container without a restart policy keeps running.
+6 -2
View File
@@ -300,8 +300,12 @@ cutover. The firewall found there stays in force and the mesh opens what it need
([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
it will close, which modules it will take and every held thing each will replace, and ends with a
short digest of all of that. **The flip is made only with that digest** — the operator confirms
the preview they read, and a preview that has changed since, or whose account of the machine is
more than fifteen minutes old, is refused. It then takes the modules, loads the mesh's own filter
in place of its refusal-only table and disables the found firewall without flushing it, putting
back the forwarding policy the found firewall had set. 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
+5 -1
View File
@@ -9,6 +9,7 @@ updated: 2026-09-22
decisions:
- 02-DECISIONS/0100-a-node-in-use-is-adopted-before-it-is-converged.md
- 02-DECISIONS/0101-a-machines-own-resolver-does-not-make-it-in-use.md
- 02-DECISIONS/0103-what-an-adopted-node-holds-and-what-its-guard-refuses.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
@@ -117,7 +118,10 @@ refuses ([08-connectivity](08-connectivity.md)). **A converged genesis refuses a
a container running, or a port listening on an address other than loopback that is neither ssh's
nor held by one of the operating system's own network daemons
([ADR 0101](../../02-DECISIONS/0101-a-machines-own-resolver-does-not-make-it-in-use.md)) — 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
names every one it counted, so a forgotten flag cannot close a working machine. **A machine
raised adopted stays adopted when genesis is run again**
([ADR 0103](../../02-DECISIONS/0103-what-an-adopted-node-holds-and-what-its-guard-refuses.md)): the installer reads the mode from what the machine records,
refuses a run without the flag on an adopted machine, and refuses the flag on a converged one. *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.
@@ -1,9 +1,9 @@
---
status: located
opened: 2026-09-22
located-in: [mesh-control internal/overlay, mesh-host internal/apply]
located-in: [mesh-controller internal/overlay, mesh-host internal/apply]
fixed-by:
amended-design:
amended-design: 03-DESIGN/01-to-be/05-the-node-host.md
---
# 084 — Taking the networking module on an adopted node restarts every container on the machine
@@ -56,13 +56,3 @@ and nothing checks for it today.
- Should an adopted node that cannot trust the registry be refused a module that needs to pull?
Or should the refusal come earlier, when the node joins?
## Diagnosis
*2026-09-22.* Worse than reported. The controller's "merge" of the runtime's file merges an
operator's settings into the module's content. The host then writes the result **whole**, so a
machine's own runtime settings, including its data directory, are replaced, not added to.
Measured on a lab machine: the runtime takes a new trusted-registry list on a reload, and a
running container without a restart policy survives it. Decided in
[ADR 0102](../../02-DECISIONS/0102-the-mesh-writes-into-a-shared-file-never-over-it.md): the
runtime's file is written into and the runtime reloaded. The hosts file stays a whole file, held
until networking is taken.
@@ -0,0 +1,10 @@
# 084 — Diagnosis
*2026-09-22.* Worse than reported. The controller's "merge" of the runtime's file merges an
operator's settings into the module's content. The host then writes the result **whole**, so a
machine's own runtime settings, including its data directory, are replaced, not added to.
Measured on a lab machine: the runtime takes a new trusted-registry list on a reload, and a
running container without a restart policy survives it. Decided in
[ADR 0102](../../02-DECISIONS/0102-the-mesh-writes-into-a-shared-file-never-over-it.md): the
runtime's file is written into and the runtime reloaded. The hosts file stays a whole file, held
until networking is taken.