Files
hq/02-DECISIONS/0199-a-module-that-answers-names-declares-its-zone-and-a-nodes-hosts-file-is-one-modules.md
T
jschoubben 8184585213 ADR 0199: a module that answers names declares its zone, and a node's hosts file is one module's
The per-node resolvers 0194 retires held two kinds of names that are neither nodes nor routes: the
lab's scenario machines and an operator's own lines. A zone a module declares is forwarded by the
mesh's resolver to that module; /etc/hosts is held per node through node-hosts-file, the operator's
lines in its kept region. Research 023 parks seats that define what their holder owns.
2026-10-03 23:19:36 +02:00

8.0 KiB

topic, status, date, deciders, reconstructed, extends
topic status date deciders reconstructed extends
the tiers accepted 2026-10-03 jochen false 0194-the-mesh-has-one-resolver-and-every-node-asks-it-for-the-meshs-names.md

199. A module that answers names declares its zone, and a node's hosts file is one module's

Context

ADR 0194 and ADR 0196 leave one resolver holding the nodes' internal domains, and retire the resolver every node ran. Two kinds of names lived in those per-node resolvers that are neither a node nor a route, and both were found on the workstation on 2026-10-03:

  • Names a module answers. The lab raises scenario machines and gives them addresses from its scenario files — the anchor's stand-in at a documentation address, the home server's on the LAN — and the workstation resolved <machine>.incus through two wildcard lines in a drop-in file its resolver read. The lines were written by hand; the addresses are the lab's, known only while a scenario runs.
  • The operator's own names, unrelated to the mesh. Twelve <loopback> <name> lines for a client's development hosts, kept in /etc/hosts and again in /etc/hosts.local, which the per-node resolver read as additional hosts.

A manifest never names an address, a node or a domain (ADR 0112). So the lab cannot list <machine>.incus → <address> in its definition, and the operator's twelve lines are not any module's to define.

Considered Options

For a module's names:

  1. The manifest lists its records. Refused by ADR 0112: the addresses are the lab's runtime facts and the scenario's choice.
  2. The module reports its records at runtime to the mesh's resolver, which writes them into its configuration. It works, and it makes the resolver hold every module's runtime state and decide, per call, whether the caller may write the name it sent — authorisation for a write, on the one server every node depends on.
  3. The module declares the zone it answers and the listen that answers it; the mesh's resolver forwards that zone there. The definition names a zone (from a setting) and one of its own listens, which ADR 0112 allows; the address and the port are the mesh's facts. The records stay where they are known — in the module, at runtime. Chosen.

For the operator's names:

  1. Records the controller holds, served by the mesh's resolver. They are not the mesh's: a client's development hosts on one machine are nothing any other node should resolve, and the controller would become the keeper of a workstation's private notes.
  2. A node-scoped module owns /etc/hosts, and the operator's lines live in its kept region (ADR 0174), changed through that module's tools on that machine. Chosen.

Decision

1. A module that answers names declares a zone. Its definition names the zone — a single label or a dotted name, from a setting, never a domain the mesh knows — and the listen that answers DNS for it. The controller refuses two modules in the mesh declaring one zone, and a zone that is the mesh's suffix, under it, or one of a node's public domains: a module may not shadow names the mesh or the public DNS answers.

2. The mesh's resolver forwards each zone to the module that declared it. The controller hands the holder of mesh-dns-resolver every declared zone with the private address of the node its module runs on and the port that listen is published on; the holder places one forwarding rule per zone into its configuration and answers nothing in that zone itself. What names exist in the zone, and their addresses, are the module's — answered by its own long-running code (ADR 0198), from its own state, as they change. Whether an answered address is reachable from the asking node is the module's matter, not the resolver's.

3. A node's /etc/hosts is held by one module, through a node seat, node-hosts-file. The seat is the definition: its holder owns /etc/hosts, and implements three verbs — MCP tool definitions served as <node>/node-hosts-file.<verb> (ADR 0195): entries (the file's lines, the module's and the operator's, each marked whose), add (one address and its names, into the operator's region) and remove (one name or address from it). The module writes the machine's own lines — loopback and the machine's name — and keeps a region for the operator, which survives every push and is given back when the module goes. Its tools change that region on that machine, escalating as the packet filter's do (ADR 0175 §4). The controller holds none of it: an operator's line is the machine's, not a record.

4. No other module writes /etc/hosts. The private network's region goes, as ADR 0194 already has it; a module that once wrote a line there asks the mesh's resolver instead.

Consequences

  • The lab's names follow its scenarios. A scenario raised is resolvable from every node at once; a scenario torn down is gone, with no line left behind in any file.
  • The mesh's resolver holds no module's state. It holds the nodes' domains and a table of who answers which zone, both composed by the controller; nothing writes to it at runtime.
  • A module answering a zone needs a DNS answerer of its own — a long-running bundle, or a resolver it runs. The lab gains one.
  • The operator's names reach the machine's own programs, not its containers. A container does not read the machine's /etc/hosts. For names unrelated to the mesh that is the right boundary; a name a container needs belongs in a zone.
  • Taking /etc/hosts keeps what is there. The first time the module writes the file, every line that is not the machine's own goes into the operator's region, so a workstation's twelve lines survive the take — the same adoption ADR 0102 gives every shared file.

How each is checked:

  • Zones: the controller's catalogue tests refuse a second module declaring a zone, a zone under the mesh suffix, and a zone equal to a node's public domain.
  • Forwarding: on the holder, the resolver's configuration carries one forwarding rule per declared zone, at the declaring node's private address and published port; asking any node's resolver for a name in the lab's zone while a scenario runs returns the scenario's address.
  • The hosts file: a push leaves the operator's region byte for byte; add followed by entries shows the line as the operator's; unassigning the module gives the region back.

References