The broker precedes the control plane, and it is written in Go
Two things found by trying to build tier 2. The substrate design asked whether the message broker has to be running before the control plane, and framed it as depending on whether the control plane's own parts talk to each other over it. They do not -- it is one process -- so under that framing the broker stays out of the bundle. The framing cannot answer the question. What decides it is how the control plane reaches a node, and the answer was already decided: only ever over the link, and the link is the broker. So provisioning the broker would require the broker. The first node does not escape this by being local, because it enrols the ordinary way, by dialling the broker at the address in its token -- which was deliberate, and worth keeping. The bundle is two images now. The record says what that costs, including a certificate the broker needs at a moment when there is no mesh to issue one. The language had never been decided for tier 2. Go, for the same reason the host is: the bundle pins this image by digest and runs it where nothing can check it, so the image should hold the program and nothing else. Also corrects something already built: the bootstrap created one database and called it 'mesh'. ADR 0008 grants a context only what it exclusively owns and ADR 0006 says the mesh database names a thing that will not exist. One database per context, so one today, called inventory.
This commit is contained in:
@@ -4,14 +4,12 @@ status: designed
|
||||
code: []
|
||||
updated: 2026-08-27
|
||||
decisions:
|
||||
- 02-DECISIONS/0019-how-this-repository-works.md
|
||||
- 02-DECISIONS/0005-the-node-host.md
|
||||
- 02-DECISIONS/0004-a-node-and-how-it-joins.md
|
||||
- 02-DECISIONS/0006-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/0007-connectivity.md
|
||||
- 02-DECISIONS/0005-the-node-host.md
|
||||
- 02-DECISIONS/0008-a-context-owns-its-store.md
|
||||
- 02-DECISIONS/0019-how-this-repository-works.md
|
||||
---
|
||||
|
||||
# The substrate
|
||||
@@ -94,15 +92,16 @@ Being substrate and being in the bundle are two different questions:
|
||||
| | is it substrate? | must it precede the control plane? |
|
||||
|---|---|---|
|
||||
| PostgreSQL | yes — the control plane's own state lives in it | **yes** — there is nowhere to put that state otherwise |
|
||||
| LavinMQ | yes — it cannot grant itself a virtual host | **not established** — see below |
|
||||
| LavinMQ | yes — it cannot grant itself a virtual host | **yes** — the control plane reaches a node only over the link, and the link is the broker ([ADR 0024](../../02-DECISIONS/0024-running-the-control-plane.md)) |
|
||||
| MinIO | yes — it cannot grant itself a bucket | no — nothing is delivered before the mesh exists |
|
||||
| the OCI registry | yes — it cannot grant itself a repository | no — the first node fetches upstream ([ADR 0006](../../02-DECISIONS/0006-the-substrate-and-the-control-plane.md)) |
|
||||
|
||||
The three on the bottom rows are **substrate by role and ordinary by delivery**: by the time
|
||||
they are wanted there is a control plane, and it provisions them the way it provisions anything.
|
||||
That keeps the bundle to roughly one image rather than four, which is what makes it small enough
|
||||
for the review [ADR 0005](../../02-DECISIONS/0005-the-node-host.md)
|
||||
requires.
|
||||
The two on the bottom rows are **substrate by role and ordinary by delivery**: by the time they
|
||||
are wanted there is a control plane, and it provisions them the way it provisions anything.
|
||||
That keeps the bundle to two images rather than four, which is what makes it small enough for the
|
||||
review [ADR 0005](../../02-DECISIONS/0005-the-node-host.md) requires. It was one until
|
||||
[ADR 0024](../../02-DECISIONS/0024-running-the-control-plane.md) established that the broker has
|
||||
to precede the control plane.
|
||||
|
||||
**Why pinned:** the bundle is applied when no mesh exists, so nothing can resolve a version, ask
|
||||
a registry, or check a constraint. What the host carries must already be exact.
|
||||
@@ -121,13 +120,28 @@ The order, from [research 011](../../01-RESEARCH/011-the-module-graph/worked-pro
|
||||
```
|
||||
0 a container runtime exists detected — docker or podman — or installed
|
||||
1 PostgreSQL runs pulled by digest, from the bundle
|
||||
2 a database is created in it an action, run locally
|
||||
3 the control plane's schema applied an action, against that database
|
||||
4 the control plane starts and only now is there a mesh
|
||||
5 LavinMQ, MinIO, the registry, and the ordinary path
|
||||
everything else are provisioned
|
||||
2 a database per context is created an action, run locally — one today, `inventory`
|
||||
3 each context's schema is applied an action, against its own database
|
||||
4 LavinMQ runs pulled by digest, from the bundle
|
||||
5 a virtual host, a credential, and actions, run locally
|
||||
a self-signed certificate
|
||||
6 the control plane starts and only now is there a mesh
|
||||
7 MinIO, the registry, and everything the ordinary path
|
||||
else are provisioned
|
||||
```
|
||||
|
||||
**Steps 4 and 5 are why the bundle is not one image**
|
||||
([ADR 0024](../../02-DECISIONS/0024-running-the-control-plane.md)). The control plane cannot
|
||||
provision the broker, because provisioning means telling a host, and telling a host happens over
|
||||
the broker. The first node does not escape this by being local: it enrols the ordinary way, by
|
||||
dialling the broker at the address in its token.
|
||||
|
||||
**Step 2 is one database per context and not one called `mesh`.** A context is granted only what it
|
||||
exclusively owns ([ADR 0008](../../02-DECISIONS/0008-a-context-owns-its-store.md)), *the mesh
|
||||
database* names a thing that will not exist
|
||||
([ADR 0006](../../02-DECISIONS/0006-the-substrate-and-the-control-plane.md)), and a separate
|
||||
database is a boundary a cross-context join cannot casually cross where a separate schema is not.
|
||||
|
||||
Only PostgreSQL is raised from the bundle, for the reason in *The pinned bundle* above — the
|
||||
rest of the substrate is wanted only once there is a control plane to provision it.
|
||||
|
||||
@@ -161,14 +175,21 @@ host's vocabulary grows by one shape rather than by one resource type per substr
|
||||
## Open
|
||||
|
||||
- **Whether identity is the fifth.** Above; it follows from a decision not yet taken.
|
||||
- **Whether the bus must precede the control plane.** The bundle table marks this *not
|
||||
established*, and it is the one row that could still move. The control plane reaches nodes over
|
||||
AMQP, but at step 4 there is exactly one node and it is the local machine — so whether LavinMQ
|
||||
is needed to *start* or only to *reach a second node* depends on whether the control plane's own
|
||||
contexts talk to each other over the bus. If they do, LavinMQ joins PostgreSQL in the bundle and
|
||||
the bootstrap grows a step; if they do not, it is provisioned like anything else. **This is a
|
||||
question about the control plane's internal shape, not about the substrate**, which is why it is
|
||||
not answered here.
|
||||
- ~~**Whether the bus must precede the control plane.**~~ **Resolved** by
|
||||
[ADR 0024](../../02-DECISIONS/0024-running-the-control-plane.md) — it must, and the question as
|
||||
posed here could not have answered it. This asked whether the control plane's contexts talk to
|
||||
each other over the bus; they do not, being one process, which under this framing would have
|
||||
kept LavinMQ out of the bundle. What decides it is how the control plane reaches a *node*, which
|
||||
is only ever over the link.
|
||||
- **What issues the broker's certificate at bootstrap.** New, and created by the row above. A
|
||||
token pins the fingerprint a host must expect before it sends anything
|
||||
([`09-the-node-lifecycle.md`](09-the-node-lifecycle.md)), so the broker needs a certificate at a
|
||||
moment when there is no mesh to issue one and no public name to obtain one for. Self-signed and
|
||||
pinned is the shape that fits; how it is later replaced by the certificates in
|
||||
[`08-connectivity.md`](08-connectivity.md) is not decided.
|
||||
- **How a context added later gets its database.** By then there is a control plane — but one
|
||||
holding a credential that can create databases holds more than what it exclusively owns
|
||||
([ADR 0008](../../02-DECISIONS/0008-a-context-owns-its-store.md)).
|
||||
- ~~**Whether the host can do step 2.**~~ **Resolved** by
|
||||
[ADR 0005](../../02-DECISIONS/0005-the-node-host.md). A service
|
||||
running on this machine is part of this machine, so the scope was never in question — the real
|
||||
|
||||
Reference in New Issue
Block a user