Reading a converged machine's rendered rules showed the cause: the chain blocks everything passing through and then allows the machine's own containers back by listing their address ranges. 0137 made that list typeable and 0139 tried to generate it; both refined a list that should not exist, because the mesh has no position on a container reaching outward. Constrain what arrives from outside, allow what did not, and let the machine report which links face outside — one fact instead of a list. Ports keep following the modules unchanged. The records check now allows one record to supersede several, and stops requiring a withdrawn record's own citations to be live.
5.4 KiB
status, opened, located-in, fixed-by, amended-design
| status | opened | located-in | fixed-by | amended-design | ||
|---|---|---|---|---|---|---|
| located | 2026-09-28 |
|
03-DESIGN/01-to-be/08-connectivity.md |
141 — The forward chain does not follow the modules, though the modules declare their networks
What was observed
ADR 0137, decided the same week, gave a machine a way to say which networks it routes for its guests, because the derived filter's forward chain had until then allowed two ranges named as constants in the control plane's own source — the container runtime's default bridge pool, and half of the pool its compose files are given.
Checking the last machine still to be converged, the same fault was found to be live there, and the declaration needed to work around it turned out to be wrong in kind.
That machine hosts twenty-one container networks. Nine fall inside the runtime's bridge pool and are forwarded. Twelve sit in the other private range, and six of those fall below the lower bound of the constant, so the flip would have cut their guests off exactly as it did on the workstation that produced 0137.
Naming a range to cover the six was the obvious move, and is what 0137 provides for. But of those six networks, four are networks the mesh's own modules declare — they appear as network resources in the node's plan, created by the host because a module asked for them — and two are leftovers of the predecessor, compose networks of services the mesh does not run. A range wide enough to keep the four would have forwarded the two as well: a firewall widened by hand to protect networks that should not exist.
The mesh already knows which of the twenty-one are its own. It made them.
Why it matters beyond this instance
The node's configuration is supposed to follow the modules assigned to it. That is the mesh's founding shape — the machine runs modules, and its files, its filter and its accounts are composed from what runs there. The forward chain is the one derived thing that does not: it consults two constants and, since 0137, a list a person types. A module added tomorrow brings a network the filter will not forward; a module deprecated leaves a range in the list that outlives it.
A typed range cannot distinguish the mesh's networks from what was left behind. It is stated in addresses, and addresses are what the runtime allocates, so the only honest declaration is one wide enough to include whatever else the runtime has handed out. The derivation is narrower than anything a person can safely write, because it names networks rather than ranges.
0137 rejected deriving this, and was right about what it rejected. It considered deriving the list from what the machine reports and refused, on two grounds: a test bed creates its bridge between one declaration and the next, and a filter that follows whatever appeared on the machine is a firewall that widens itself. Deriving from the declaration is neither. The set is known before the network exists, because a module declared it; and it cannot widen itself, because only a network some module asked for is ever forwarded. What remains genuinely for a machine to say is guests no module declares — a test bed's pool — which is a much smaller residue than the list as it stands.
The gap is invisible in the one place that should show it. The converge preview lists what listens, and routing is not a listener. It says in one line what the machine routes, and a reader has to know the runtime's allocations to tell whether that line is sufficient. On the machine measured here it read as though nothing needed saying.
What was decided
2026-09-28, later the same day. The answer is not a better list. The question in the first open item below — should the chain be derived from the networks the modules declare — was answered no, after a converged machine's rendered rules were read: the chain blocks everything passing through the machine and then allows its own guests back by listing their addresses. Every route to a correct list fails, because the mesh has no position on a container reaching outward in the first place. The filter now constrains what arrives from outside the machine and says nothing about what did not, and a machine says which of its links face outside — one reported fact instead of a list. See ADR 0140, which supersedes both 0137 and the first attempt at answering this.
Open questions
- Should the forward chain be derived from the network resources the node's modules declare, with the host resolving each declared network to its address the way it already resolves a container by name? The controller cannot render the address itself: a module's network resource carries a name, and the runtime allocates the subnet at creation.
- What remains of
node networksonce that exists — only guests no module declares, such as a test bed's pool? And should it then be named for that, rather than for all routing? - The runtime's own default bridge, which containers attach to when no module network is named, is not a module's network. Is it derived from the machine, declared by the module that owns the runtime, or left as the one constant?
- Should the preview say which of a machine's networks are the mesh's and which are not, so a range that exists to protect a leftover is visible as such?