Issues 111 and 112: the resolver was told the wrong set of names, twice over #98

Merged
jschoubben merged 1 commits from issues/111-112-the-resolver-was-told-the-wrong-names into main 2026-09-23 23:32:42 +00:00
2 changed files with 103 additions and 0 deletions
Showing only changes of commit f704e2ca64 - Show all commits
@@ -0,0 +1,52 @@
---
status: resolved
opened: 2026-09-24
located-in: [mesh-controller cmd/mesh-controller/plan.go, mesh-controller internal/catalogue/facts.go]
fixed-by: mesh-controller — tell the resolver the machines, not the names the mesh merely serves
amended-design:
---
# 111 — The resolver is told every name the mesh serves, not only the machines
## What was observed
Composing the resolver's first assignment on the control-node, 2026-09-24, and reading the plan
before pushing it. The file the mesh would have handed the resolver:
```
local=/internal/
address=/<a routed name>.internal/<the hub>
address=/<the node>.internal/<the hub>
address=/<another routed name>.internal/<the hub>
```
Two of the three are not machines. They are names the mesh was **told to serve** — a module's public
name, resolved mesh-wide so that a container reaching it finds the proxy serving it
([ADR 0066](../../02-DECISIONS/0066-public-routing-is-name-agnostic.md)) — with the
mesh's own suffix appended to each, producing a name nobody will ever ask for.
The control plane hands a resolution one map of names, and it holds both kinds. A container's hosts
file wants all of it. The resolver's zones want only the machines, and the difference matters
because of the line above them: told that the mesh's suffix is its own, the resolver answers
authoritatively for everything under it and forwards none of it. So the invented names sit beside
the real ones looking exactly as real, and a fourth is absent —
[issue 112](../112-adopting-a-tunnel-gives-the-mesh-addresses-without-names/00-report.md).
Nothing was pushed. The module was unassigned and the machine left as it was.
## Why it matters beyond this instance
The two sets are not the same and the mesh had one word for both. Every fact the mesh computes about
"the names" now has to say which it means, and the one that leaks is the one that is authoritative:
a resolver that answers for a name it invented cannot be asked again.
It is also a near miss of the kind worth writing down. The conversion was reviewed, its own tests
passed, and the fault was invisible in them because they were written with a map of machines — the
shape the composer's own comment assumed. It appeared the first time the composer met a live mesh
that also serves routed names, and only because the plan was read before it was pushed.
## What it is now
The control plane carries the two sets separately — every name it serves, and the machines — and
each fact is handed the one it is true of. The hosts file keeps every name; the resolver's zones take
the machines. A test pins both halves together, so neither can drift into the other again.
@@ -0,0 +1,51 @@
---
status: open
opened: 2026-09-24
located-in: []
fixed-by:
amended-design:
---
# 112 — Adopting a tunnel gives the mesh the peers' addresses but not their names
## What was observed
On the control-node, 2026-09-24, reading what the resolver would answer before it was assigned.
The predecessor's resolver answers for **four** machines — the hub and the three that reach it over
the tunnel. The mesh's would have answered for **one**: itself. The other three are *carried peers*
of the tunnel the hub took over
([ADR 0105](../../02-DECISIONS/0105-the-mesh-adopts-the-predecessors-tunnel-in-place.md)) — the mesh
holds an address for each, and keeps it, and has no name for any of them, because a name comes from
enrolling and they have not enrolled.
Taking the resolver in that state replaces a file that resolves four machines with one that resolves
one, and — because the resolver is told the mesh's suffix is its own and forwards nothing under it —
the other three stop resolving rather than falling through. Everything on the machine that reaches
another by name breaks at once: the predecessor's own agents, its pipeline, anything an operator
typed.
## Why it matters beyond this instance
Adoption was designed so that what the mesh does not know yet, it leaves alone. The tunnel is the
first place the mesh takes over something whose *content is knowledge the predecessor has* — three
machines exist, and these are their names — and it takes the addresses without the names. Nothing
says so; the mesh reads as complete.
This is an ordering rule the migration did not have: **a name the predecessor answers for must keep
resolving until the machine behind it is a node.** It applies to the resolver first and to anything
else derived from "the machines the mesh knows" while the mesh knows fewer than the machine does.
## Open questions
- Should a carried peer be nameable — the operator saying which machine a carried address is, before
it enrols — so the resolver can answer for it in the meantime? The mesh would be recording a name
it cannot verify.
- Or should the resolver simply not be taken until every carried peer has enrolled, stated as a
refusal rather than a note in a runbook?
- Or should the mesh's resolver, while any peer is carried, forward the suffix it cannot answer to
the resolver it replaced — which is the adapter shape
([ADR 0104](../../02-DECISIONS/0104-a-provision-may-be-answered-by-an-adapter-to-the-predecessor.md))
applied to names instead of routes?
- What checks it? Nothing compares what the predecessor's resolver answers with what the mesh's
would.