Consolidate: 65 decision records to 23

Every remaining cluster merged. Each was one design that had been split across
several records because it was worked out over days rather than at once.

  the node host          8 -> 1    applies not decides, depends on nothing,
                                   per operating system, root service, the
                                   launcher, episodic, what a declaration is,
                                   actions from the bundle only
  a node and how it joins 4 -> 1   what a node is, joining, the link as
                                   security boundary, the enrolment token
  modules and the graph   7 -> 1   everything is a module, no domain modules,
                                   three edges, provisioning, the core library
  substrate and control   6 -> 1   the test, seven contexts, one control plane,
    plane                          the authority is not a database, the named
                                   products, the pinned bundle
  connectivity            3 -> 1   a route is a grant, reachability declared,
                                   filter rules
  delivery                5 -> 1   reconciliation not a pipeline, artifacts,
                                   the three silos, a failed step, the verdict
  the lab                 5 -> 1   (earlier)
  how this repository     10 -> 1  (earlier)
    works

Nothing was dropped. Each consolidated record carries the reasoning of the ones
it absorbs -- the measurements, the incidents, the alternatives rejected --
because that reasoning is the only reason to keep a record at all. What is gone
is the fragmentation: eight files to read to understand tier 0, when tier 0 is
one component.

The four superseded records went too. They existed to point at their
successors, and the successors now contain what they said.

The checker made this safe. Each merge left dangling links -- 38 files after
the host merge alone -- and it named every one. Nothing was found by reading,
and a manual pass would certainly have missed some, including references inside
AGENTS.md which every session loads.
This commit is contained in:
2026-08-28 20:03:24 +02:00
parent 5e83ac2c22
commit 77f3a4cea7
90 changed files with 1041 additions and 4187 deletions
+19 -19
View File
@@ -4,11 +4,11 @@ status: designed
code: []
updated: 2026-08-27
decisions:
- 02-DECISIONS/0037-the-host-applies-it-does-not-decide.md
- 02-DECISIONS/0053-one-control-plane-and-no-failover.md
- 02-DECISIONS/0037-the-node-host.md
- 02-DECISIONS/0048-the-substrate-and-the-control-plane.md
- 02-DECISIONS/0019-how-this-repository-works.md
- 02-DECISIONS/0015-mesh-brokers-nodes-host-agents-think.md
- 02-DECISIONS/0055-the-control-plane-is-the-node-coordinating-contexts.md
- 02-DECISIONS/0048-the-substrate-and-the-control-plane.md
---
# The control plane
@@ -24,7 +24,7 @@ This document defines it. It does **not** design the contexts inside it; those a
> **The control plane is everything that needs to know about more than one node.**
That is the whole test, and it is not arbitrary — it follows from
[ADR 0037](../../02-DECISIONS/0037-the-host-applies-it-does-not-decide.md). The host applies and
[ADR 0037](../../02-DECISIONS/0037-the-node-host.md). The host applies and
does not decide *because deciding needs knowledge the machine does not have*. So the line falls
exactly there:
@@ -44,7 +44,7 @@ catch it because the dependency direction is still correct.
## What is inside it
**Seven contexts and one interface**
([ADR 0055](../../02-DECISIONS/0055-the-control-plane-is-the-node-coordinating-contexts.md)) —
([ADR 0048](../../02-DECISIONS/0048-the-substrate-and-the-control-plane.md)) —
each one earning its place by the test above rather than by being ours:
| | | needs to know about more than one node because |
@@ -88,10 +88,10 @@ because each one alone reads like a detail:
| | |
|---|---|
| [ADR 0037](../../02-DECISIONS/0037-the-host-applies-it-does-not-decide.md) | the host never queries the mesh database |
| [ADR 0039](../../02-DECISIONS/0039-the-link-is-the-security-boundary.md) | a node holds its own identity **and nothing else** — the shared database credential every node carries today is the exposure this exists to remove |
| [ADR 0037](../../02-DECISIONS/0037-the-node-host.md) | the host never queries the mesh database |
| [ADR 0036](../../02-DECISIONS/0036-a-node-and-how-it-joins.md) | a node holds its own identity **and nothing else** — the shared database credential every node carries today is the exposure this exists to remove |
| [ADR 0045](../../02-DECISIONS/0045-a-context-owns-its-store.md) | a context is granted only what it **exclusively** owns: no shared writes, no read-only roles |
| [ADR 0056](../../02-DECISIONS/0056-the-authority-is-the-control-plane-not-a-database.md) | there is no single mesh database, and nothing reads one |
| [ADR 0048](../../02-DECISIONS/0048-the-substrate-and-the-control-plane.md) | there is no single mesh database, and nothing reads one |
### So how does anything get in
@@ -126,14 +126,14 @@ the store it exclusively owns
([ADR 0045](../../02-DECISIONS/0045-a-context-owns-its-store.md)).
**One consumer is a property worth having**, not just a consequence of
[ADR 0053](../../02-DECISIONS/0053-one-control-plane-and-no-failover.md). The as-is records that
[ADR 0048](../../02-DECISIONS/0048-the-substrate-and-the-control-plane.md). The as-is records that
*two consumers accidentally sharing one queue silently split the traffic between them, each
receiving half of what it expects* — which has happened, between a module's daemon and its
capability server. With one consumer that class of fault cannot arise.
**And the broker is the buffer while the control plane is down.** Nodes go on publishing;
messages queue; the control plane drains them when it returns. That is what makes
[ADR 0053](../../02-DECISIONS/0053-one-control-plane-and-no-failover.md)'s single control plane
[ADR 0048](../../02-DECISIONS/0048-the-substrate-and-the-control-plane.md)'s single control plane
tolerable — an outage delays the mesh's *knowledge* rather than losing it.
**With one consequence that must be bounded before it is discovered:** a queue with no limit
@@ -154,7 +154,7 @@ would be exactly the shared-schema mistake 0045 exists to stop, arriving through
marked *performance*.
That leaves one genuine funnel: every context's writes go through the process that owns it, and
[ADR 0053](../../02-DECISIONS/0053-one-control-plane-and-no-failover.md) says there is one of it.
[ADR 0048](../../02-DECISIONS/0048-the-substrate-and-the-control-plane.md) says there is one of it.
For a mesh of a handful of machines this is not a scaling problem, and **it should not be solved
by giving nodes database credentials** — that trades a bounded problem for an unbounded one. If
it ever binds, the answers are at the consumer: batch, apply backpressure, or move the highest
@@ -170,10 +170,10 @@ volume genuinely argues against a relational store.
- **Not a surface.** Tier 3 is how people and agents reach it. It has one interface; the
surfaces are what speak to that interface.
- **Not the substrate.** It *runs on* tier 1 — PostgreSQL, LavinMQ, MinIO, an OCI registry
([ADR 0048](../../02-DECISIONS/0048-the-substrate-is-named.md)) — and cannot start without
([ADR 0048](../../02-DECISIONS/0048-the-substrate-and-the-control-plane.md)) — and cannot start without
them, which is what makes them a lower tier.
- **Not privileged on a node.** It has no more access to a machine than the declaration
vocabulary allows ([ADR 0039](../../02-DECISIONS/0039-the-link-is-the-security-boundary.md)).
vocabulary allows ([ADR 0036](../../02-DECISIONS/0036-a-node-and-how-it-joins.md)).
## It is also a consumer
@@ -184,7 +184,7 @@ module needs, granted the same way.
That is the circularity the tiers exist to resolve rather than hide: the control plane cannot
provision its own database, because it is not running yet. So its **store** is raised from the
bundle the host carries, before there is a control plane to ask
([ADR 0038](../../02-DECISIONS/0038-a-node-joins-by-linking-first.md),
([ADR 0036](../../02-DECISIONS/0036-a-node-and-how-it-joins.md),
[research 011](../../01-RESEARCH/011-the-module-graph/worked-provider.md)).
Its virtual host and its bucket are **not** in the bundle — by the time they are wanted there is
@@ -198,15 +198,15 @@ it.
hosts, assigned to nodes by the same mechanism as everything else.
**One node runs it, and nothing takes over**
([ADR 0053](../../02-DECISIONS/0053-one-control-plane-and-no-failover.md)). The node is assigned,
([ADR 0048](../../02-DECISIONS/0048-the-substrate-and-the-control-plane.md)). The node is assigned,
never elected — no promotion, no quorum, no split brain.
That is sound rather than merely cheap, because the design already tolerates the control plane
being absent by construction: a node reconciles from **its own** store
([ADR 0043](../../02-DECISIONS/0043-a-declaration-is-an-ordered-list-of-owned-resources.md)) and
([ADR 0037](../../02-DECISIONS/0037-the-node-host.md)) and
never needed to ask anybody to hold the state it was last given. So the control plane being down
is not a new failure mode — it is
[ADR 0036](../../02-DECISIONS/0036-a-node-is-a-managed-machine.md)'s ordinary disconnected
[ADR 0036](../../02-DECISIONS/0036-a-node-and-how-it-joins.md)'s ordinary disconnected
situation, happening to every node at once. **What is lost is change, not operation.**
The honest half: this node is a single point of failure, recovery is restore rather than
@@ -216,14 +216,14 @@ every public name.
## Open
- ~~**The contexts themselves.**~~ **Decided** — seven, by
[ADR 0055](../../02-DECISIONS/0055-the-control-plane-is-the-node-coordinating-contexts.md).
[ADR 0048](../../02-DECISIONS/0048-the-substrate-and-the-control-plane.md).
What remains open is narrower and named there: **where the record lives**, which research 006
leaves unresolved because the substrate is the one place it must not go.
- **How far it may be split.** One deployable today. Splitting a context out costs the single
interface a surface depends on
([research 011](../../01-RESEARCH/011-the-module-graph/00-overview.md)).
- ~~**How many run, and what a node does without one.**~~ **Resolved** by
[ADR 0053](../../02-DECISIONS/0053-one-control-plane-and-no-failover.md). What remains is
[ADR 0048](../../02-DECISIONS/0048-the-substrate-and-the-control-plane.md). What remains is
measurement: nothing reports how long the control plane has been unreachable, or how close a
certificate is to expiry — both needed for restore-not-failover to be a plan rather than a
hope.