Jochen: a normal application has 3-5 ADRs, maybe 10 for a large one, and we are at 65. Fair, and the cause is mine -- I recorded every FINDING as a decision rather than every fork in the road. Two merges, both cases where one decision had been split across many records because it was taken over several days rather than at once. 0019 absorbs ten records about how this repository works: what it is and that it is public, the folder flow, the two design layers, the issue front door, status in frontmatter, playbooks, the naming rule, the product name. Those were never ten decisions -- they were one, seen from ten angles as the repository took shape. 0016 absorbs the five about the lab: a node is a virtual machine, a router is scenery, a scenario declares the underlay, a scenario is a closed address space, and the two scenario classes. Same pattern -- one design, split by the order it was worked out in. The consolidated 0019 also raises the bar for what earns a record, since that is what produced 65: a record is warranted when there is a genuine fork -- a direction reversed, an alternative that will be proposed again, something contested. A finding is not a decision, and a bug is certainly not. Everything else belongs in the design document where the reasoning is actually read. The checker earned its place here. Deleting nine records left 13 dangling links across the repository and it named every one, including in AGENTS.md. Nothing was found by reading. Remaining clusters worth the same treatment: the host (8 records), delivery (5), modules (6), connectivity (4), substrate and control plane (4). That would be 52 down to roughly 30.
5.1 KiB
status, date, deciders, reconstructed
| status | date | deciders | reconstructed |
|---|---|---|---|
| accepted | 2026-08-25 | jochen | false |
37. The host applies; it does not decide
Context
The skeleton absorbs overlay membership, packet filtering, package management, service supervision, the container runtime and filesystem management into tier 0, and research 006 called this "the skeleton's biggest unproven claim. A binary whose whole argument is that it has no dependencies now carries six concerns."
That claim has now been measured against the monorepo's main:
host-size.md.
The measurement says the question asked about the wrong axis.
Considered options
- Absorb the six concerns as they are. What the skeleton literally proposes. Rejected on evidence: two of the ten modules implementing them open a direct connection to the control plane's database and compute their own configuration. Absorbing those unchanged puts a Postgres client and knowledge of the mesh schema inside tier 0 — an upward dependency, and the tier rule is the whole of the bootstrap argument.
- Leave them as modules. Keeps the tier rule trivially, and keeps the fault that prompted the skeleton: four modules constituting how a node is reachable with no relationship the mesh can see, so one intent is expressed four times (research 005 finding 4 measures this and finds it is the only place in the catalogue where the shape genuinely occurs).
- Split each concern: decide centrally, apply locally. Chosen.
Decision
The host has one concern: apply declared state on this machine. The six are not six concerns it carries; they are instances of the one.
Each divides:
- Deciding — what this node's overlay, names, exposure, filtering, packages and services should be. This needs every other node, and belongs to the control plane.
- Applying — putting that on the machine. This needs root and locality, and belongs to the host.
The host never queries the mesh database. A host that reads the control plane's schema is tier 0 depending on tier 2, and the tiers stop being a bootstrap answer the moment that is permitted once.
Why the evidence supports it
Size was the wrong worry. The ten modules total 2 755 lines. The machinery that already
applies state on a node — meshware, env-sync, config-sync — is 3 059. Everything being
absorbed is smaller than what already exists to apply it. The host is not a new large thing; it
already exists, spread across three core modules and unnamed.
Eight of the ten are already pure appliers. They receive derived state and put it on the machine. Absorbing them moves code that has no dependency to move.
The split has already been happening, unnamed. dnsmasq-app needs the same mesh-wide data
as wireguard and does not query for it. Its own comments record why: the values were
"duplicated by hand on all four nodes" until someone derived them centrally, after a rename
meant editing four override rows nobody knew about. That is this decision, reached once by
fixing a bug.
Consequences
- Two modules must be split before they can be absorbed, and they are the two hardest.
wireguardneeds every node's key, address, site and endpoint reachability;traefikneeds certificates and every node's exposed names. The measurement says the design is right; it does not say the migration is cheap, and this record does not claim it is. - The overlay and firewall modules stop existing as the skeleton says — but the reason is now sharper than "they are host concerns". The host holds membership and applies filtering; the control plane decides policy; swappable backends stay modules.
- Six vocabularies remain. Zero dependencies, but the host must still know what a WireGuard peer, an nftables rule, a package, a unit, a container and a dataset are. That surface is the residue of the original worry and is not measured by anything here.
- A dependency-direction lint is now load-bearing, not a nicety. This record is a rule about direction, and per this repository's own standard a rule states how it is checked: an upward import fails the build. A tier rule enforced by intention is the same as no tier rule.
- What the host carries versus what it finds is still open.
Issue 007 — the
host manages
wg,nft,pacman,docker; it does not contain them, and installed is not the same as usable.
References
host-size.md— the measurement.- Research 005 — reachability as the only measured co-change cluster in the catalogue.
- ADR 0003 — what the control plane decides from.
- ADR 0019 —
mesh-hostas tier 0. - ADR 0008 — the standard the direction lint is held to.