The bundles refactor took 0188 on main while this waited in a pull request, and the mesh's own code cites that one, so this record moves. Only the number moved; the decision is the one taken on 2026-10-02, and the record says so. Issue 202 re-checked against the refactored main: the fault stands, and the test that surfaced it now fails one step earlier on issue 203's new credential guard. Proven again past both — mint the credential, compose twice, and all eight of dnsmasq's resources appear only with the setting set. ADR 0164 is noted as the decision that answers half of it, and is not built.
94 lines
5.6 KiB
Markdown
94 lines
5.6 KiB
Markdown
---
|
||
status: open
|
||
opened: 2026-10-02
|
||
located-in: [mesh-catalog modules/dnsmasq, mesh-controller cmd/mesh-controller]
|
||
fixed-by:
|
||
amended-design:
|
||
---
|
||
|
||
# 202 — A module whose required setting nobody set is left out of the machine, and the resolver is the module it happened to
|
||
|
||
## What was observed
|
||
|
||
Running the controller's own test suite against the catalogue beside it, 2026-10-02.
|
||
`TestTheResolverIsToldEveryMachineOnTheNetworkAndToldAgainWhenOneLeaves` fails with *"the resolver
|
||
was not handed the machines"*. Composing the same machine by hand and listing what it receives
|
||
shows why: **dnsmasq contributes nothing at all.** Four resources are composed for that node, all
|
||
of them the overlay's. The resolver's package, its configuration, its service and the fact that
|
||
carries every machine's name are simply not there.
|
||
|
||
The cause is one line added to `dnsmasq`'s configuration earlier the same day: the addresses it
|
||
listens on beside the machine's own became an operator setting,
|
||
`listen-address=${setting:listen-addresses}`, with no default. A `${setting:…}` nothing sets is
|
||
refused, a module that cannot be composed is **left out** rather than failing the whole machine
|
||
([ADR 0163](../../02-DECISIONS/0163-taking-a-module-over-is-a-comparison.md)), and so a node
|
||
assigned the resolver is handed a declaration with no resolver in it.
|
||
|
||
The failing test is the symptom that surfaced it. The test is not what is wrong.
|
||
|
||
**Proven rather than inferred.** Composing the same machine a second time with
|
||
`listen-addresses` set to `127.0.0.1` and nothing else changed, every one of dnsmasq's eight
|
||
resources appears — `needs-broker`, `mesh-state`, `package`, `config`, `runtime-dns`, `runtime`,
|
||
`service` and `fact-node-zones`. The only difference between a machine with a resolver and a
|
||
machine without one is whether somebody set a value that did not exist yesterday.
|
||
|
||
## Why it matters beyond this instance
|
||
|
||
**Leaving a module out is right, and being quiet about it is not.** The rule exists so one
|
||
module's broken setting cannot stop a machine converging — a good rule. But the outcome here is a
|
||
machine that applies cleanly, reports current, and is missing its DNS resolver. Every name on that
|
||
machine then resolves through whatever was there before, or not at all, and nothing in the mesh
|
||
says the resolver was dropped. That is the shape
|
||
[issue 152](../152-a-nodes-plan-failure-silently-drops-its-routed-names/00-report.md) records for
|
||
routed names, here for a whole module.
|
||
|
||
**And a setting with no default is a definition that cannot be assigned.** Every other
|
||
`${setting:…}` in the catalogue names something that is genuinely particular to one installation —
|
||
a public domain, an issuer. "Which addresses besides my own do I answer on" has an obvious correct
|
||
default for every machine that is not a LAN gateway: none beside loopback. A definition that
|
||
refuses to compose until somebody sets a value most machines do not need is a definition that
|
||
breaks the next node to be assigned it, and genesis with it.
|
||
|
||
## What this does not claim
|
||
|
||
Whether the live machines are affected was not checked — those four have had the setting set, or
|
||
their resolvers would already be gone. The claim is about a machine assigned the resolver *from
|
||
now on*, and about the silence.
|
||
|
||
## Open questions
|
||
|
||
- Should the declaration say which modules it left out, where a person or the console can see it?
|
||
`left_out` already travels to the host ([ADR 0163](../../02-DECISIONS/0163-taking-a-module-over-is-a-comparison.md));
|
||
what is missing is anything that reads it back and says so.
|
||
- ~~Should a `${setting:…}` be allowed a default?~~ **Decided in principle and not built.**
|
||
[ADR 0164](../../02-DECISIONS/0164-a-setting-is-declared-with-its-default-its-meaning-and-what-changing-it-costs.md)
|
||
(proposed, 2026-10-01) says a setting with a default is a tunable and one without is the
|
||
operator's, and narrows 0155's refusal to exactly the second. `listen-addresses` is a tunable by
|
||
that rule, and dnsmasq declares no settings block at all. So this issue is, in part, 0164 waiting
|
||
to be built — and in part the silence, which 0164 does not address.
|
||
- Is leaving a module out ever right for a module a node is **assigned**, as opposed to one it
|
||
merely pulls in? An assignment is somebody saying *this machine runs this*; silently not running
|
||
it is the one answer nobody asked for.
|
||
|
||
## Still true on 2026-10-04, and the evidence had to be re-taken
|
||
|
||
Re-checked after the bundles refactor landed (fourteen records, ADRs 0188 and 0190–0200). **The
|
||
fault stands and the old evidence no longer reaches it.**
|
||
|
||
`TestTheResolverIsToldEveryMachineOnTheNetworkAndToldAgainWhenOneLeaves` still fails on
|
||
mesh-controller main, with the same message — and now for a *different first reason*. `assign` is
|
||
refused before composition ever happens:
|
||
|
||
> dnsmasq on anchor has no bus credential: nothing was issued for anchor.dnsmasq … (novox/hq issue 203)
|
||
|
||
That is [issue 203](../203-a-fresh-assignment-is-pushed-before-its-credential-exists/00-report.md)'s
|
||
new guard doing its job on a test harness that mints no credential. Two faults are stacked in one
|
||
failing test, and the second was invisible behind the first.
|
||
|
||
Proven again, past both: mint `anchor.dnsmasq` so the assignment stands, then compose the machine
|
||
twice. **Without `listen-addresses` the node composes four resources, all the overlay's. With it
|
||
set to `127.0.0.1`, all eight of dnsmasq's appear** — `needs-broker`, `mesh-state`, `package`,
|
||
`config`, `runtime-dns`, `runtime`, `service`, `fact-node-zones`. Nothing else differs.
|
||
|
||
The test is now wrong about two things and should be fixed with whichever of these is fixed first.
|