Files
hq/03-DESIGN/01-to-be/06-the-control-plane.md
T
jschoubben 8d9282d86b Resolve the ingress gap: a route is a grant
ADR 0048 named ingress as an unclosed hole -- nothing said what terminates
TLS, how a public name reaches a container, or which tier owned it. Resolving
it needed no new concepts, which is why it survived: nobody had applied the
rules already written to it.

Ingress is not substrate. The control plane does not need a route to start,
and no node needs one to reach it -- the node dials out and has no listening
control surface. It grants itself a route afterwards, like a bucket.

A route is an instantiation edge under ADR 0044. The direction mirrors a
database -- the consumer supplies a target and receives a name rather than
credentials -- but it is the same edge.

The substantive finding is that exposure is three facts at two scopes: name
resolution and certificate issuance need to know which node is publicly
reachable, and only the proxy mapping is a single machine's business. That is
why it belongs to the connectivity context, and why Traefik doing all three on
the node is wrong.

Which matters beyond tidiness: research 006 counted traefik as one of two
modules opening a direct Postgres connection, reading nodes and mesh_ca. That
violates 0037, 0045 and 0039 at once, and is why every node permanently holds
a credential to the control plane's database. Deriving the config centrally and
delivering it as `file` resources removes it, costs zero new host vocabulary,
and closes the set 0039 identified -- wireguard was the other.

Left open deliberately: the mesh's internal CA is the other thing traefik
reads, and it belongs to the link's mutual authority, not to exposure.
Conflating the two is what made the gap hard to see.

Also fixes an inconsistency from the previous commit: 06 still claimed the
virtual host was raised from the bundle.

Proposed, not accepted -- for review.
2026-08-27 00:22:52 +02:00

5.9 KiB

layer, status, code, updated, decisions
layer status code updated decisions
to-be designed
2026-08-27
02-DECISIONS/0037-the-host-applies-it-does-not-decide.md
02-DECISIONS/0030-the-repository-structure.md
02-DECISIONS/0015-mesh-brokers-nodes-host-agents-think.md

The control plane

Tier 2. The term appears seventy-nine times across this repository and was defined nowhere, which is how-we-build §5 failing on this repository's own vocabulary.

This document defines it. It does not design the contexts inside it; those are open in research 006.

The definition

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. The host applies and does not decide because deciding needs knowledge the machine does not have. So the line falls exactly there:

Question Whose
write this file, with this content, with this mode the host — one machine
which nodes should run the store the control plane — needs every node
is this unit running the host — one machine
which peers belong in this node's overlay the control plane — needs every node
what does this machine have installed the host reports; the control plane records
has this node been unreachable for a week the control plane — nobody else is watching

A useful consequence: anything a single machine could answer alone is not the control plane's. If it needs no second node, putting it here is a mistake, and the tier rule will not catch it because the dependency direction is still correct.

What is inside it

Ten contexts and one interface, from the skeleton (research 006):

record the event log every other context integrates through
inventory nodes, modules, assignments, versions
config settings, secrets, and deriving them onto nodes
connectivity overlay, resolution, exposure, filtering, certificates — it decides routes; the proxy on a node applies them (ADR 0049)
provisioning resource grants between modules
delivery source to artifact to node
observability health, logs, metrics, alerts
identity agents, humans, services, authorisation
work tasks, workflows, runs
knowledge memory, documents, retrieval
api the one interface every surface speaks to

These are contexts, not services. They are separate in the sense that matters — each owns its own store, and they integrate through the record rather than by reading one another (how-we-build §4). They are not separate deployables, and research 011 records why that constraint is load-bearing: a single surface can compose them only while there is one interface in front of them.

What it is not

  • Not the thing that changes machines. It decides; the host applies. It never reaches into a node except through the host.
  • 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) — 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).

It is also a consumer

The property that makes tier 2 unlike the others: the control plane has requirements of its own. It needs a PostgreSQL database, an AMQP virtual host, and a bucket — the same things any 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, research 011).

Its virtual host and its bucket are not in the bundle — by the time they are wanted there is a control plane to grant them. Whether the bus must come first is open, and it turns on whether these contexts talk to each other over it.

Where it runs

On nodes, like anything else. It is not a place outside the mesh; it is modules the mesh hosts, assigned to nodes by the same mechanism as everything else.

Which raises a question this document does not answer: how many nodes run it, and what happens when the one running it is down. The broker is one per mesh by decision; whether the control plane is, and what a node does while it cannot reach it, is ADR 0036's ordinary situation seen from the other end — and it is not designed.

Open

  • The contexts themselves. Ten is the skeleton's claim, not a settled list. Research 006 asks whether the record belongs here or in the substrate, and whether identity is a context or a substrate service.
  • How far it may be split. One deployable today. Splitting a context out costs the single interface a surface depends on (research 011).
  • How many run, and what a node does without one. Above.
  • What the interface is. One interface is stated; its shape, and whether it is request, subscription or both, is not (research 011).