diff --git a/01-RESEARCH/006-mesh-from-scratch/00-overview.md b/01-RESEARCH/006-mesh-from-scratch/00-overview.md index 653dfc9..3f5a8b5 100644 --- a/01-RESEARCH/006-mesh-from-scratch/00-overview.md +++ b/01-RESEARCH/006-mesh-from-scratch/00-overview.md @@ -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. | diff --git a/03-DESIGN/01-to-be/07-the-substrate.md b/03-DESIGN/01-to-be/07-the-substrate.md new file mode 100644 index 0000000..433a8d5 --- /dev/null +++ b/03-DESIGN/01-to-be/07-the-substrate.md @@ -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. diff --git a/03-DESIGN/01-to-be/README.md b/03-DESIGN/01-to-be/README.md index 20c937f..a8800c7 100644 --- a/03-DESIGN/01-to-be/README.md +++ b/03-DESIGN/01-to-be/README.md @@ -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