The mesh computes every name under a machine, for a resolver to answer

Services are named under the machine they run on — postgres.novox.internal,
plex.ace.internal. The first label is the service and the rest is the node, so
what has to resolve is anything under a node's name. What routes it once it
arrives is a proxy's concern and stays separate.

A hosts file cannot do that. It answers exact names, and a wildcard there would
mean writing down every service in advance — which is the enumeration the
arrangement exists to avoid. novox/hq 08-connectivity named this exact case as
the trigger for needing a resolver rather than a file, and it is the first
thing to meet it.

The mesh writes the data and runs no daemon. A resolver is third-party
software, and third-party software runs on the mesh rather than being of it
(ADR 0001): the mesh has no business shipping one, choosing which one, or
knowing its configuration language. What only the mesh can know is which
machines exist and where they are. A module that runs a resolver requires what
this provides and reads one file, so swapping the daemon changes that module
and nothing here.

Separate from names rather than part of them: a machine with no container
runtime can still have a hosts file, and folding them together would take exact
names away from a machine that cannot run a daemon in order to give it a
wildcard it cannot use either.

A machine with no address is left out. A wildcard pointing at nothing is worse
than no wildcard — every name under it resolves and then hangs, where an
unresolvable name fails at once and says which name it was.
This commit is contained in:
2026-08-31 11:39:21 +02:00
parent 4d67c48342
commit 3954157555
4 changed files with 222 additions and 3 deletions
+36
View File
@@ -59,6 +59,18 @@ const Addressing = "mesh-addressing"
// something that needed the mesh's own addresses. Which is exactly what happened, once.
const TheNetwork = "the-private-network"
// Resolver is the module that answers every name under a machine, and the claim it holds.
//
// A claim because a machine has one resolver: two daemons answering the same names on one machine
// is a coin toss about which one a query reaches, and the answer differing between them is the
// kind of fault nobody finds by looking at either.
const (
Resolver = "mesh-resolver"
// ResolverData is what a module running a resolver requires: the mesh's own account of which
// machines exist and where, in a file.
ResolverData = "resolver-data"
)
// Domain is the module for people who want a network and do not want to choose one.
//
// It has no files of its own — it is requirements and nothing else. Assigning it finds one
@@ -186,6 +198,30 @@ func NamesManifest() map[string]any {
}
}
// ResolverManifest is what a resolver on this machine must know: every name under every machine.
//
// **It writes the data and runs no daemon.** A resolver is third-party software, and third-party
// software runs *on* the mesh rather than being *of* it
// ([ADR 0001](novox/hq)) — the mesh has no business shipping one, choosing which one, or knowing
// its configuration language. What only the mesh can know is which machines exist and where they
// are, so that is what it computes.
//
// So a module that runs a resolver requires what this provides, and reads one file. Swapping the
// daemon changes that module and nothing here.
//
// **Separate from names rather than part of them**, because a machine with no container runtime
// can still have a hosts file. Folding them together would take exact names away from a machine
// that cannot run a daemon, to give it a wildcard it cannot use either.
func ResolverManifest() map[string]any {
return map[string]any{
"module": Resolver,
"version": "1",
"computed": Resolver,
"requires": []string{Resolution},
"provides": []string{ResolverData},
}
}
// DomainManifest is the module that means "get the network working".
func DomainManifest() map[string]any {
return map[string]any{