Networking is a module, and a domain module is how you avoid choosing

Connectivity was code beside the module system doing the module system's
job: every machine with an address was on the private network and there
was no way to keep one off.

A manifest can now say its resources are computed by the control plane,
which is what a peer list needs — it is derived from every machine at
once, so nothing could be written in advance. The network is a module
from there on: assigned, resolved, settled, and absent from a machine
nobody gave it to.

Three modules rather than one, because WireGuard is one VPN of several:

  mesh-wireguard   provides private-network, mesh-addressing
                   claims the-private-network, one per node
  mesh-names       provides name-resolution, requires mesh-addressing
  networking       requires both, and ships no files of its own

The last is the point. Most people want the network up and do not want
to choose a VPN, so `assign networking` takes the only answer to each
requirement silently. The day the catalogue holds a second one there are
two answers, the resolver refuses and names them, and choosing is
assigning the one you want. No flavor field, nothing to configure.

Names left the WireGuard declaration for their own module. They would be
identical over a different private network, and bundling them made one
module out of two things.

Three faults the walk found:

- choosing tailscale still installed WireGuard, dragged back in by the
  names needing the mesh's own addresses. Caught now by a claim: running
  two VPNs is fine, being *the* mesh network is singular.
- a requirement wanted by two modules was reported twice, identically.
- "this mesh has no hub" was reported when the real cause was that a
  node could not be resolved at all. It now names the node and the why.

And a test that asserts the manifests actually shipped, after the claim
went missing from the real one while every test stayed green.
This commit is contained in:
2026-08-29 23:19:32 +02:00
parent 65ade756f2
commit 44d134ba25
12 changed files with 940 additions and 53 deletions
+21
View File
@@ -69,6 +69,20 @@ type Manifest struct {
// Resources are what this module puts on a node, in the host's own vocabulary.
Resources []map[string]any `json:"resources,omitempty"`
// Computed names something in the control plane that works this module's resources out per
// node, instead of them being fixed here.
//
// Because some files cannot be written in advance. A machine's peer list on the private
// network is derived from every other machine, so it differs on each one and changes when any
// of them changes — there is nothing to put in a manifest.
//
// Being a module anyway is the point: it is assigned like anything else, so a machine that
// should not be on the private network simply is not given it, and the network is worked out
// over the machines that have it. Before this, connectivity was code beside the module system
// doing the same job, and every machine with an address was on the network whether or not
// anybody wanted it there.
Computed string `json:"computed,omitempty"`
}
// ParseManifest reads a module manifest, refusing anything it cannot act on.
@@ -117,6 +131,13 @@ func ParseManifest(raw []byte) (Manifest, error) {
m.Module, c.Name, c.Scope))
}
}
if m.Computed != "" && len(m.Resources) > 0 {
// One or the other. A module that both ships files and has them computed would leave
// nobody able to say where a given file came from.
problems = append(problems, fmt.Sprintf(
"%s has resources of its own and says they are computed by %q; it is one or the other",
m.Module, m.Computed))
}
for i, r := range m.Resources {
id, _ := r["id"].(string)
if id == "" {