Adopt the glossary's vocabulary in the mutable design docs

"control plane" -> controller and "substrate" -> foundation throughout
03-DESIGN, 00-META and the README, with 06-the-control-plane.md and
07-the-substrate.md renamed to 06-the-controller.md and 07-the-foundation.md.
The immutable 02-DECISIONS records keep their original wording (and links to
them are unchanged) — a term retired here may still appear there, which the
glossary explains how to read.

Claude-Session: https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx
This commit is contained in:
2026-09-16 18:48:52 +02:00
parent f9f48fbbf7
commit 33a00d5656
25 changed files with 233 additions and 233 deletions
+17 -17
View File
@@ -21,25 +21,25 @@ decisions:
# Connectivity
One of [the control plane's](06-the-control-plane.md) ten contexts, and the one with the most
One of [the controller's](06-the-controller.md) ten contexts, and the one with the most
moving parts: **overlay, resolution, exposure, filtering, certificates.**
It is written as a whole because the five are one design. They share inputs, they must agree, and
every one of them today is computed in a different place by a different module from a different
copy of the same facts.
## Why it is control-plane work
## Why it is controller work
Apply [the test](06-the-control-plane.md) — *everything that needs to know about more than one
Apply [the test](06-the-controller.md) — *everything that needs to know about more than one
node* — to each responsibility:
| | needs to know | whose |
|---|---|---|
| **overlay** — who peers with whom, at what address | **every node**, and which of them can be dialled | control plane |
| **resolution** — which name is which node | **every node** | control plane |
| **exposure** — which public name reaches which container | **which node is publicly reachable** ([ADR 0007](../../02-DECISIONS/0007-connectivity.md)) | control plane |
| **filtering** — which port is open, to whom | what is assigned here, and the overlay's shape | control plane decides, host applies |
| **certificates** — who may present which name | which name belongs to which node | control plane |
| **overlay** — who peers with whom, at what address | **every node**, and which of them can be dialled | controller |
| **resolution** — which name is which node | **every node** | controller |
| **exposure** — which public name reaches which container | **which node is publicly reachable** ([ADR 0007](../../02-DECISIONS/0007-connectivity.md)) | controller |
| **filtering** — which port is open, to whom | what is assigned here, and the overlay's shape | controller decides, host applies |
| **certificates** — who may present which name | which name belongs to which node | controller |
**Not one of the five can be answered by a machine on its own.** That is the whole reason this is
a context rather than a set of node-local modules — and it is exactly what the current
@@ -57,7 +57,7 @@ exist; WireGuard, the resolver and the proxy are all *a container or a package,
It is also what removes the last two upward dependencies.
[Research 006](../../01-RESEARCH/006-mesh-from-scratch/host-size.md) counted exactly two modules
opening a direct connection to the control plane's database — `wireguard` and `traefik` — and
opening a direct connection to the controller's database — `wireguard` and `traefik` — and
they are the reason every node permanently holds a credential to it
([ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md)). Both are connectivity
modules. **Closing this context closes that set.**
@@ -74,7 +74,7 @@ wanted the exception.
**What made it look unavoidable:** a peer list cannot be written in a manifest. It is derived from
every other machine, so it differs on each one and changes when any of them changes. So the
manifest says its resources are **computed** — it names something in the control plane that works
manifest says its resources are **computed** — it names something in the controller that works
them out per node — and it is a module in every other respect: assigned, resolved, configured by
settings, and absent from a machine nobody gave it to.
@@ -108,7 +108,7 @@ claim, and the collision is refused by name.
**And the proxy's half, which was the other module reaching into the database.** A web application
requiring a reverse proxy has to say *which name, which port*, and there was nowhere to put it —
`requires` says a thing must exist and never said what to do with it. A module now contributes to
a requirement, the control plane collects every contribution on a node, and the provider is given
a requirement, the controller collects every contribution on a node, and the provider is given
them as a file at a path it named. It reloads when that file changes, by the same `restart-on` the
private network needed when a peer list changed under a running interface.
@@ -177,7 +177,7 @@ which of those it may dial, and which must dial it.
**Keys.** Each node generates its own keypair. **The private key never leaves the machine**; the
public key is published to the mesh. This is already true and it is already right — it is
[ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md)'s *a node holds its own
identity* applied to the overlay, and it means the control plane computes a graph it cannot
identity* applied to the overlay, and it means the controller computes a graph it cannot
itself impersonate.
**Shape: a hub, with direct peering between co-located nodes.**
@@ -217,7 +217,7 @@ files were right, the services were up, and every node reported success.
document's own warning, arriving in its implementation: *a more specific route to a dead
endpoint blackholes; it does not fall back to the general one.*
- **The container runtime closes the door the overlay needs.** Docker sets the FORWARD policy to
DROP, so a hub with `ip_forward` enabled still carries nothing between its spokes. The substrate
DROP, so a hub with `ip_forward` enabled still carries nothing between its spokes. The foundation
at tier 1 silently breaks the network at tier 2, and nothing in either tier's state says so. The
hub inserts its own rule above those chains and removes it on the way down.
@@ -287,7 +287,7 @@ node. What routes it once it arrives is a proxy's, and stays separate.
**The mesh writes the data and runs no daemon.** One wildcard per machine, from the same set that
writes the hosts file. A resolver is third-party software and runs *on* the mesh rather than being
*of* it: the mesh has no business shipping one, choosing which one, or knowing its configuration
language. Swapping dnsmasq for unbound changes that module and nothing in the control plane.
language. Swapping dnsmasq for unbound changes that module and nothing in the controller.
**Two roles, two claims, because they are different things.** systemd-resolved cannot answer a
wildcard at all — it routes the mesh's suffix to something that can. Treating serving and asking
@@ -377,7 +377,7 @@ vocabulary — the mirror of a database grant, where the consumer supplies a tar
name rather than supplying nothing and receiving credentials.
**A workload on an unreachable node is proxied by a reachable one, across the overlay.** Which is
the case is a mesh-level fact, which is the fourth reason exposure is control-plane work.
the case is a mesh-level fact, which is the fourth reason exposure is controller work.
### What was built
@@ -482,7 +482,7 @@ something:
| | why not |
|---|---|
| decide what the machine **forwards** | the container runtime writes its own forwarding rules and a second policy is consulted alongside them, so a drop here stops every container on the node — the control plane included. Nothing in a manifest says what a machine routes, so there is nothing to derive it from either |
| decide what the machine **forwards** | the container runtime writes its own forwarding rules and a second policy is consulted alongside them, so a drop here stops every container on the node — the controller included. Nothing in a manifest says what a machine routes, so there is nothing to derive it from either |
| **flush the ruleset** when loading | that empties every table on the machine, the runtime's among them. Only the mesh's own table is replaced, and it is declared empty first so the replacement works on a machine loading one for the first time |
| carry a **command to load itself** | the link may not carry an action ([ADR 0005](../../02-DECISIONS/0005-the-node-host.md)). A service is declared to reflect the file instead, so replacing it restarts what loads it — the shape that rule leaves, used here for the first time for its real purpose |
@@ -549,7 +549,7 @@ defaults to the public authority's *production* endpoint. Two consequences, and
worse than the lab problem that found it — every certificate experiment on a real node consumes
production issuance quota, and a retry loop can exhaust it for a week.
**The mesh CA is not a bootstrap concern.** A joining node verifies the control plane against the
**The mesh CA is not a bootstrap concern.** A joining node verifies the controller against the
fingerprint in its token ([ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md)),
so nothing needs the CA before membership. It certifies internal names afterwards, and that is
all it does.