Order the records the way the system is learned

Jochen asked whether the order made sense. It did not -- it followed when
things happened to be decided, which after consolidation is fictional anyway
since record 5 alone folds decisions taken across a week.

Concretely wrong before: the domain statement sat at 8, after five engineering
rules; the constitution was scattered across 5, 12 and 17; the tiers landed at
15, 16, 21 and 22 with process records in between.

Now it walks: what the mesh is (1-3), its tiers from the bottom up (4-8), what
runs on them and how it gets there (9-10), how it is built (11-16), how it is
checked (17-18), how we work (19-23).

Two things made this safe rather than free. It is a permutation, not a
compaction, so the renames go through temporary names -- otherwise two files
want one slot and one is lost. And the reference rewrite is a single
simultaneous pass, because almost every number moved into a slot another number
was vacating; replacing one at a time would have cascaded and pointed things at
the wrong record while still resolving.

Verified: 284 [ADR NNNN](path) links across the repository, all with matching
text and target.

The ordering principle is now stated in 19 rather than left implicit -- the
repository already said "the numbering is the flow" about its folders, and
there was no reason for the records to be the exception.
This commit is contained in:
2026-08-28 23:30:42 +02:00
parent e1febe8e0f
commit 333356cff3
85 changed files with 471 additions and 465 deletions
+25 -25
View File
@@ -4,11 +4,11 @@ status: designed
code: []
updated: 2026-08-27
decisions:
- 02-DECISIONS/0016-the-node-host.md
- 02-DECISIONS/0021-the-substrate-and-the-control-plane.md
- 02-DECISIONS/0011-how-this-repository-works.md
- 02-DECISIONS/0008-mesh-brokers-nodes-host-agents-think.md
- 02-DECISIONS/0021-the-substrate-and-the-control-plane.md
- 02-DECISIONS/0005-the-node-host.md
- 02-DECISIONS/0006-the-substrate-and-the-control-plane.md
- 02-DECISIONS/0019-how-this-repository-works.md
- 02-DECISIONS/0001-mesh-brokers-nodes-host-agents-think.md
- 02-DECISIONS/0006-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 0016](../../02-DECISIONS/0016-the-node-host.md). The host applies and
[ADR 0005](../../02-DECISIONS/0005-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 0021](../../02-DECISIONS/0021-the-substrate-and-the-control-plane.md)) —
([ADR 0006](../../02-DECISIONS/0006-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 |
@@ -64,7 +64,7 @@ anything else does. A task does not need to know a node exists, and *being ours
something infrastructure*. `ai` is folded into `config`: a provider licence is an ordinary grant.
**`record` is an open question rather than an eighth entry.** Contexts integrate through it
([ADR 0020](../../02-DECISIONS/0020-a-context-owns-its-store.md)), which makes it load-bearing,
([ADR 0008](../../02-DECISIONS/0008-a-context-owns-its-store.md)), which makes it load-bearing,
and [research 006](../../01-RESEARCH/006-mesh-from-scratch/skeleton.md) leaves *where it lives*
unresolved — putting it in the substrate risks recreating the circularity the tier design just
removed. Listing it here would settle by naming what has not been settled by arguing.
@@ -88,10 +88,10 @@ because each one alone reads like a detail:
| | |
|---|---|
| [ADR 0016](../../02-DECISIONS/0016-the-node-host.md) | the host never queries the mesh database |
| [ADR 0015](../../02-DECISIONS/0015-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 0020](../../02-DECISIONS/0020-a-context-owns-its-store.md) | a context is granted only what it **exclusively** owns: no shared writes, no read-only roles |
| [ADR 0021](../../02-DECISIONS/0021-the-substrate-and-the-control-plane.md) | there is no single mesh database, and nothing reads one |
| [ADR 0005](../../02-DECISIONS/0005-the-node-host.md) | the host never queries the mesh database |
| [ADR 0004](../../02-DECISIONS/0004-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 0008](../../02-DECISIONS/0008-a-context-owns-its-store.md) | a context is granted only what it **exclusively** owns: no shared writes, no read-only roles |
| [ADR 0006](../../02-DECISIONS/0006-the-substrate-and-the-control-plane.md) | there is no single mesh database, and nothing reads one |
### So how does anything get in
@@ -123,17 +123,17 @@ node ──► broker ──► the control plane, consuming
Seven contexts, **one deployable** — they are not separate services, so this is one process
consuming and dispatching internally, not seven consumers racing. Each context then writes only
the store it exclusively owns
([ADR 0020](../../02-DECISIONS/0020-a-context-owns-its-store.md)).
([ADR 0008](../../02-DECISIONS/0008-a-context-owns-its-store.md)).
**One consumer is a property worth having**, not just a consequence of
[ADR 0021](../../02-DECISIONS/0021-the-substrate-and-the-control-plane.md). The as-is records that
[ADR 0006](../../02-DECISIONS/0006-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 0021](../../02-DECISIONS/0021-the-substrate-and-the-control-plane.md)'s single control plane
[ADR 0006](../../02-DECISIONS/0006-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
@@ -149,12 +149,12 @@ before designing for throughput.** The registry is `inventory`'s store: nodes, m
assignments, versions. Those change when somebody changes something.
**Logs, metrics and health checks belong to `observability`**, which owns a different store
([ADR 0020](../../02-DECISIONS/0020-a-context-owns-its-store.md)). Sending them to the registry
([ADR 0008](../../02-DECISIONS/0008-a-context-owns-its-store.md)). Sending them to the registry
would be exactly the shared-schema mistake 0045 exists to stop, arriving through the back door
marked *performance*.
That leaves one genuine funnel: every context's writes go through the process that owns it, and
[ADR 0021](../../02-DECISIONS/0021-the-substrate-and-the-control-plane.md) says there is one of it.
[ADR 0006](../../02-DECISIONS/0006-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 0021](../../02-DECISIONS/0021-the-substrate-and-the-control-plane.md)) — and cannot start without
([ADR 0006](../../02-DECISIONS/0006-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 0015](../../02-DECISIONS/0015-a-node-and-how-it-joins.md)).
vocabulary allows ([ADR 0004](../../02-DECISIONS/0004-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 0015](../../02-DECISIONS/0015-a-node-and-how-it-joins.md),
([ADR 0004](../../02-DECISIONS/0004-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 0021](../../02-DECISIONS/0021-the-substrate-and-the-control-plane.md)). The node is assigned,
([ADR 0006](../../02-DECISIONS/0006-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 0016](../../02-DECISIONS/0016-the-node-host.md)) and
([ADR 0005](../../02-DECISIONS/0005-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 0015](../../02-DECISIONS/0015-a-node-and-how-it-joins.md)'s ordinary disconnected
[ADR 0004](../../02-DECISIONS/0004-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 0021](../../02-DECISIONS/0021-the-substrate-and-the-control-plane.md).
[ADR 0006](../../02-DECISIONS/0006-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 0021](../../02-DECISIONS/0021-the-substrate-and-the-control-plane.md). What remains is
[ADR 0006](../../02-DECISIONS/0006-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.