Define the substrate, and answer 006's four-or-five conditionally

Same gap as the control plane: load-bearing and unpinned.

The substrate is what the control plane CONSUMES AND CANNOT GRANT ITSELF. Every
module needing a database asks provisioning for one; the control plane needs one
too and cannot ask itself, because it is not running yet. That circularity is
not an awkwardness to work around — it is the definition, and anything on the
wrong side of it must be raised by the bundle the host carries.

Which answers 006's open question in the honest form rather than with a number.
The identity provider is substrate only if the control plane DELEGATES
authentication — then it cannot serve anybody before the provider exists and
cannot grant itself a client. If it authenticates natively, the provider is an
ordinary hosted service. So the count follows from a decision not yet taken, and
asserting four was asserting that decision.

The test also rules out the tempting wrong answer: an identity provider, a mail
server and an analytics service are all infrastructure by any ordinary reading,
and none are substrate, because the control plane starts and runs without them.
Important is not the test.

Records why the bundle is pinned by hand — it is applied when no mesh exists, so
nothing can resolve a version or ask a registry — and why it must be
self-contained, which makes it an artifact built on a machine with a network for
a machine that may have none.
This commit is contained in:
2026-08-26 23:39:11 +02:00
parent 148395ca54
commit 60aea14935
3 changed files with 112 additions and 1 deletions
@@ -90,5 +90,5 @@ the catalogue where modules genuinely change together under one intent. The skel
| One repository per tier, or per context? | Already open from ADR 0015 as "catalogue destination — one repository or many". The skeleton assumes per tier and does not settle it. |
| ~~Does an unprivileged node earn a place in the inventory, or only a presence?~~ | **Answered 2026-08-25** by the operator: a node is a *managed machine inside the mesh*, not an unprivileged something — and a disconnected node is still a node, in a different situation. The question posed a class distinction; the answer is that there is none, and what varies is **state**. Recorded as [ADR 0036](../../02-DECISIONS/0036-a-node-is-a-managed-machine.md). |
| ~~Does absorbing overlay, filtering, packages, supervision and the container runtime make the host too large?~~ | **Answered 2026-08-25** — [`host-size.md`](host-size.md). Measured: the absorption is smaller than the machinery that already applies state, and eight of ten adapters already carry no dependency. The risk is not size but direction, and it is two modules wide. The claim survives with its scope corrected — the host carries one concern, *apply declared state on this machine*, of which the six are instances. Recorded as [ADR 0037](../../02-DECISIONS/0037-the-host-applies-it-does-not-decide.md), designed in [`05-the-node-host.md`](../../03-DESIGN/01-to-be/05-the-node-host.md). |
| Four substrate services or five? | The identity provider passes the tier test only if the control plane delegates authentication rather than doing it natively. |
| ~~Four substrate services or five?~~ | **Answered conditionally**, which is the honest form — [`07-the-substrate.md`](../../03-DESIGN/01-to-be/07-the-substrate.md). The substrate is *what the control plane consumes and cannot grant itself*. The identity provider qualifies only if the control plane delegates authentication; if it authenticates natively it is an ordinary hosted service. The count follows from a decision not yet taken, and asserting four was asserting that decision. |
| Does `feature` survive? | The skeleton splits it in two and argues the conflation is what makes the delivery pipeline hard to reason about. Unproven. |
+110
View File
@@ -0,0 +1,110 @@
---
layer: to-be
status: designed
code: []
updated: 2026-08-26
decisions:
- 02-DECISIONS/0030-the-repository-structure.md
- 02-DECISIONS/0037-the-host-applies-it-does-not-decide.md
- 02-DECISIONS/0038-a-node-joins-by-linking-first.md
---
# The substrate
Tier 1. Defined the same way [the control plane](06-the-control-plane.md) is, because the same
gap applied: the word was load-bearing and unpinned.
## The definition
> **The substrate is what the control plane consumes and cannot grant itself.**
Every module that needs a database asks the control plane's provisioning for one. The control
plane needs a database too — and it cannot ask itself, because it is not running yet. That
circularity is not an awkwardness to work around; it *is* the definition. Anything on the wrong
side of it must be raised some other way, and the other way is the bundle the host carries
([ADR 0038](../../02-DECISIONS/0038-a-node-joins-by-linking-first.md)).
The test, applied:
| | control plane needs it | can it grant itself one? | |
|---|---|---|---|
| a relational store | its own state lives there | no — provisioning needs the store | **substrate** |
| a message bus | it reaches nodes over it ([ADR 0001](../../02-DECISIONS/0001-nodes-communicate-over-a-broker.md)) | no — it cannot grant itself a virtual host | **substrate** |
| an object store | artifacts and blobs it delivers | no — it needs a bucket to hold them | **substrate** |
| an image registry | images it delivers to nodes | no — it needs a repository | **substrate** |
| an identity provider | only if it delegates authentication | — | **conditional, below** |
| anything else the mesh hosts | no | — | not substrate |
## What that resolves
**Four or five?** [Research 006](../../01-RESEARCH/006-mesh-from-scratch/00-overview.md) asks
whether the identity provider is a substrate service, and the test answers it *conditionally* —
which is the honest answer rather than a number.
- If the control plane **delegates** authentication, it cannot serve anybody before the provider
exists, and it cannot grant itself a client. **Substrate.**
- If it **authenticates natively**, the provider is an ordinary hosted service like any other.
**Not substrate.**
So the count follows from a design decision that has not been taken, and the record should say
that rather than assert four.
**Why not "important infrastructure".** An identity provider, a mail server and an analytics
service are all infrastructure by any ordinary reading, and none of them are substrate — the
control plane starts and runs without them. *Important* is not the test; *the control plane
cannot obtain it* is.
## What the substrate is not
- **Not tier 0.** The host raises the substrate; it is not part of it. The host carries the
declaration that brings the substrate up, and depends on nothing.
- **Not the control plane.** These are services with no knowledge of the mesh. A store does not
know what a node is.
- **Not a place for logic.** The skeleton is explicit: tier 1 is *declarations only, no logic of
its own.* A substrate service is an upstream image, pinned, with configuration.
- **Not privileged.** The substrate is provisioned *from* by the control plane and grants
nothing on its own initiative.
## The pinned bundle
The substrate is what `substrate.lock` contains, and this is the only place in the mesh where
versions are pinned by hand rather than resolved.
**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.
**Why self-contained:** a node raising a first mesh may have no route to anything
([research 012](../../01-RESEARCH/012-the-minimum-viable-node/00-overview.md)). Images and
packages the bundle needs travel *with* it, fetched when the bundle was **built** rather than
when it is applied.
**What that makes it:** an artifact built on a machine with a network, for a machine that may
have none — which is the reframing research 012 records, arriving here as a requirement.
## Raising it
The order, from [research 011](../../01-RESEARCH/011-the-module-graph/worked-provider.md):
```
1 the host applies the bundle the store runs; no mesh exists
2 a database is created in it a provisioning step, done locally
3 the control plane's schema applied a migration against that database
4 the control plane starts and only now is there a mesh
5 everything else is provisioned the ordinary path
```
**Steps 2 and 3 happen before there is a mesh to do them**, which is why provisioning is part of
the bootstrap rather than a service consumers use later — and why the host's declaration
vocabulary has to reach further than files, directories and units.
## Open
- **Whether identity is the fifth.** Above; it follows from a decision not yet taken.
- **Whether the host can do step 2.** Creating a database inside a running store is not node
state, and [ADR 0043](../../02-DECISIONS/0043-a-declaration-is-an-ordered-list-of-owned-resources.md)
has the host applying *state on this machine*. At bootstrap the store is on that machine, so it
is at least local. It is the sharpest unresolved thing in the bootstrap path.
- **Whether one host can raise all four.** The claim under stage 2 of
[the node host](05-the-node-host.md), never proved. If it is false, the tier boundary moves.
- **How the substrate is updated once a mesh exists.** Pinned by hand at bootstrap; afterwards
the control plane could deliver it like anything else, and nothing says whether it does.
+1
View File
@@ -16,6 +16,7 @@ document is written and this one's status becomes `implemented`.
| [`04-lab-installation.md`](04-lab-installation.md) | Getting the lab onto a clean machine, and why it verifies capability rather than installation | [ADR 0008](../../02-DECISIONS/0008-a-failed-step-fails-the-job.md) |
| [`05-the-node-host.md`](05-the-node-host.md) | Tier 0 — the one thing installed by hand, and the only thing that changes a machine | [ADR 0037](../../02-DECISIONS/0037-the-host-applies-it-does-not-decide.md) |
| [`06-the-control-plane.md`](06-the-control-plane.md) | Tier 2 — what the term means, and the test for what belongs in it | [ADR 0037](../../02-DECISIONS/0037-the-host-applies-it-does-not-decide.md) |
| [`07-the-substrate.md`](07-the-substrate.md) | Tier 1 — what the control plane consumes and cannot grant itself | [ADR 0038](../../02-DECISIONS/0038-a-node-joins-by-linking-first.md) |
## Not yet written