Files
hq/02-DECISIONS/0103-what-an-adopted-node-holds-and-what-its-guard-refuses.md
T

113 lines
7.0 KiB
Markdown

---
topic: the mesh
status: accepted
date: 2026-09-22
deciders: jochen
reconstructed: false
extends: 02-DECISIONS/0100-a-node-in-use-is-adopted-before-it-is-converged.md
---
# 103. What an adopted node holds, and what its guard refuses
## Context
[ADR 0100](0100-a-node-in-use-is-adopted-before-it-is-converged.md) holds a found file or
container until its module is taken. It also has the mesh guard the store's port and the
broker's management port with a table that only refuses. Two independent reviews of the build
found both rules drawn too narrowly and, for the guard, in the wrong place:
1. **Other kinds of resource reach what was found.** An untaken module's directory is re-owned
and re-moded if a predecessor's directory is already at its path, and a database refuses to
start on a data directory whose mode changed. A service of the same name as a predecessor's
unit is started, stopped or re-enabled. An action run *in* a held container runs inside the
predecessor's service. A container that is not found under its own name is created, and can
mount the predecessor's data beside the predecessor's own container.
2. **The guard's two ports are not the only ones a found firewall misses.** A published
container port is forwarded, not received, and a firewall that filters only incoming traffic
never sees it (ADR 0100's own Context). The broker's plaintext port is published on every
interface and admitted by the filter from the private network only. So on an adopted node it
is reachable from anywhere. The same holds for any published port a module restricts to the
private network.
3. **The guard refuses by port alone.** On a machine that routes for others, such as a
predecessor's private-network hub, a packet for another machine's database port is refused as
well. And a port the guard refuses for a module that is assigned but not taken may still be
the predecessor's own, serving the predecessor's other machines.
## Decision
**Found covers every kind that can reach what the machine already has.** On an adopted node,
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** with no record is held when an administrator installed its unit — the service
manager reads the unit from outside the packages' own directory — or when the machine uses it,
running or started at boot. Its state and whether it starts at boot are then 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 a package merely
ships that nothing runs and nothing enables is not found: the private network's own tunnel unit
is one, and holding it kept the private network from ever coming up;
- 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.
What is held is reported as held and is never removed, as ADR 0100 decides for files and
containers.
**The guard refuses what the found firewall may miss, for taken modules only.** Its ports are
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 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
protected on an adopted node the way the derived filter protects it on a converged one.
- Guarding only taken modules means a foundation port on a node joining adopted is guarded once
its module is taken, not when it is assigned. Genesis takes the foundation's modules, so the
control-node's store is guarded from the first push.
## How it is checked
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 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
- [ADR 0100](0100-a-node-in-use-is-adopted-before-it-is-converged.md),
[ADR 0102](0102-the-mesh-writes-into-a-shared-file-never-over-it.md)