Written as one document because the five are one design. They share inputs, they must agree, and every one of them today is computed in a different place by a different module from a different copy of the same facts. The through-line is that none of the five can be answered by a machine alone, so all five are decided centrally and delivered as `file` resources. That costs no new host vocabulary and removes both remaining direct database connections from nodes -- wireguard and traefik are the only two, and both are connectivity. Three decisions fall out, all proposed: 0050 -- reachability is declared, not inferred from an address. The RFC1918 regex is wrong for carrier-grade NAT (100.64/10 tests as public, so an endpoint is written to an address nothing can reach), wrong for IPv6, and wrong for a routable address behind a closed firewall. The lab needing TEST-NET-3 to satisfy the regex is the same bug from the other side. Also kills hub election by address prefix, which fails silently and makes renumbering an outage. 0051 -- the enrolment token carries where the mesh is and how to recognise it. Closes two circles with one mechanism: verifying the mesh needed the CA, and obtaining the CA meant trusting whoever handed it over; and a node had to reach the mesh before it could resolve any mesh name. An address plus a fingerprint, carried out of band, resolves both -- and closes the CA question 0049 deferred. 0052 -- a filter rule names its source. `scope:` is declared in five manifests, is part of no rule type, and is referenced by no code, so those manifests appear to restrict ports and restrict nothing. Removed rather than implemented; the general fix is refusing unknown keys, which the host already does and manifests do not. Also corrects two claims in 0049 asserting wireguard was already handled. Research 006 says both modules still reach upward; neither is.
3.6 KiB
status, date, deciders, reconstructed, extends
| status | date | deciders | reconstructed | extends |
|---|---|---|---|---|
| proposed | 2026-08-27 | jochen | false | 0043-a-declaration-is-an-ordered-list-of-owned-resources.md |
52. A filter rule names its source, or it is not a rule
Context
Research 004 found this while looking for something else:
Several manifests declare
scope: publicon firewall rules —wireguard,traefik,gitea,mailu,qbittorrent. It is not part of the rule type and is referenced by no code in the firewall path. Real scoping is done withfrom:.
So five manifests appear to restrict a port and restrict nothing. Anyone reading them — including whoever wrote the next one by copying — sees an access control that does not exist.
Research 004 already names the shape: it is how-we-build's an unenforced rule is
indistinguishable from a wrong one, and costs more, because people believe it. This is that
rule, in the firewall, on the modules most worth restricting.
The mechanism that let it happen is worth more than the instance. scope: was accepted because
unknown keys were ignored. Nothing rejected it, nothing warned, and it spread by copying for
long enough to reach five manifests.
Decision
A rule names its source. from: is the only way to scope a rule, and a rule without one is
open — which it must therefore say plainly rather than imply otherwise.
scope: is removed, not implemented. Giving it meaning would leave two ways to express one
thing, and a manifest carrying both would need a precedence rule nobody would remember. The five
manifests are corrected to from: where they meant to restrict something, and left open where
they did not — and finding out which is which is part of the work, not a formality.
An unknown key is refused. This is the general fix and the reason to bother:
A manifest carrying a key the schema does not define is rejected, naming the key.
The host already works this way — its declaration parser sets DisallowUnknownFields and
collects every problem into one refusal
(ADR 0043). Manifests are the
layer where that discipline is missing, and scope: is what missing looks like: not a wrong
value, an invented one, silently accepted for months.
Consequences
- This class of fiction stops at validation rather than at an audit. A misspelled
form:, an inventedscope:, a key from a different schema — each fails on the manifest that introduces it, once, instead of spreading. - Existing manifests will fail validation, and some of those failures will be keys somebody believed were doing something. That is the finding, not the cost — but it means the refusal cannot be switched on without reading every manifest first.
- The firewall becomes reviewable. Today a rule's real effect is only visible by knowing which keys are fictional. Afterwards the manifest says what happens.
- Five manifests need a decision each, and
wireguardandtraefikgenuinely are open to the world — they must be, which the manifest should state rather than appear to deny. - It does not make the rules correct, only honest. A rule that says
from:anywhere is legitimately open; this record ensures it says so.
References
- Research 004 §7 — the finding.
how-we-build.md— the rule about unenforced rules.- ADR 0043 — unknown-is-refused, where it already holds.