# Diagnosis ## 2026-09-24 — where the mesh's own record ends, and what stands beside it Checked the mechanism first. The resolver module's config (`mesh-catalog modules/dnsmasq`) is generated whole from `nodes.conf`, the `dnsmasq.fact-node-zones` fact `mesh-controller` computes: one `address=/./
` wildcard per node the mesh has a name for, plus `local=//` — which tells dnsmasq it is *authoritative* for the whole suffix, so anything under it that is not one of those wildcards is refused, not forwarded. That is the exact mechanism the report describes: **the mesh does not lack an upstream to ask, it has told itself there is nothing to ask.** Checked whether "forward to the resolver it replaced" (the third open question, ADR 0104's shape) is literal here the way it was for the proxy. It is not, and the difference matters: the proxy's predecessor kept running throughout its migration, a separate process on ports the mesh's proxy did not yet hold. DNS has one process on one port. `mesh-catalog`'s dnsmasq module, on assignment, replaces `/etc/dnsmasq.conf` whole and restarts the one `dnsmasq.service` unit — the same unit the predecessor's own resolver is running as, right now, on this control-node: ``` $ systemctl status dnsmasq ● dnsmasq.service ... Active: active (running) since Fri 2026-09-18 ... ``` There is no predecessor process left standing after that assignment for a forward rule to reach. An adapter in ADR 0104's literal shape — a second thing running, written into by a first — does not fit; whatever answers this has to be data carried across the cutover, not a live thing forwarded to across it. ## What is actually missing, and where it already exists The report's first open question — *should a carried peer be nameable, the operator saying which machine a carried address is, before it enrols* — turns out not to need a guess. The predecessor's own resolver config, still live on this control-node, already states it: ``` # /etc/dnsmasq.d/hal-dns.conf — Generated by HAL dnsmasq-app address=/novox.internal/10.10.0.1 address=/ace.internal/10.10.0.2 address=/g14.internal/10.10.0.4 address=/shanks.internal/10.10.0.3 ``` Checked against what the mesh itself recorded when it took the tunnel over (`overlay show`): ``` a peer of the tunnel (zTKYP6Sz…) 10.10.0.2 not yet enrolled a peer of the tunnel (XT1l25Z0…) 10.10.0.3 not yet enrolled a peer of the tunnel (mq76YPW0…) 10.10.0.4 not yet enrolled ``` `10.10.0.2`/`.3`/`.4` match `ace`/`shanks`/`g14` exactly, one for one. This is not the operator inventing a fact under time pressure — it is the predecessor's own record, current, and it has been correctly serving these three names for six days without correction. Recording it is transcription, not assertion. **located-in, tentatively:** `mesh-controller internal/inventory` (where a carried peer's record lives today — `CarriedPeer`/`TunnelPeer` in `tunnel.go` carry a public key and an address but no name field) and `cmd/mesh-controller` (a command to set it — nothing today lets an operator attach a name to a carried-tunnel-peer record; `node add` is for enrolling nodes, not naming peers, and `node public-domain ` is the closest existing shape to model a new verb on). Separately, `mesh-controller internal/catalogue` (`facts.go`'s `nodeZones`) would then need to emit an `address=` wildcard for a *named carried peer* the same way it does for a node — it renders only from the mesh's `addresses` map today, keyed by node name, and does not distinguish a named carried peer from an unnamed one. `mesh-catalog modules/dnsmasq` itself needs no change: it already restarts on the `node-zones` fact and would pick up the new wildcard the moment `internal/catalogue` emits it. ## What is still open - The mechanism for *recording* the name is not designed — a new field on the carried-peer record, a new CLI verb, and what happens if the name later disagrees with what the peer states on enrolling (it should win; nothing says so yet). - Whether this generalises: the predecessor's static hosts file was the answer *here* because it happened to be readable and correct. A migration without one still has the report's harder second option (refuse the resolver until every peer enrols) as its fallback. - Not implemented. This diagnosis is what the fix would touch and why the ADR 0104 framing in the report's third option does not transfer directly — not the fix itself.