diff --git a/01-RESEARCH/011-the-module-graph/worked-provider.md b/01-RESEARCH/011-the-module-graph/worked-provider.md index cc54105..57df117 100644 --- a/01-RESEARCH/011-the-module-graph/worked-provider.md +++ b/01-RESEARCH/011-the-module-graph/worked-provider.md @@ -155,9 +155,13 @@ a file or a unit — and at bootstrap it is, at least, local: the store is on th the host applying the bundle. Later it is not. A consumer on one node provisioned from a store on another is the ordinary -case, and reaching it is not the host's job. So the same operation is local at bootstrap and -remote afterwards, which is either two mechanisms or one mechanism with a boundary crossing in -it. Undecided, and it is the sharpest unresolved thing in this file. +case, and reaching it is not the host's job. + +**Resolved as two mechanisms, which is the answer rather than a compromise** +([ADR 0047](../../02-DECISIONS/0047-the-bundle-may-carry-actions-the-link-may-not.md)). The host +runs bootstrap actions locally from the bundle; the control plane provisions across the mesh +afterwards. Different actors, different scopes, different trust paths — so there is no single +operation with a tier boundary running through it. ## Several modules, one database — and the case for refusing diff --git a/02-DECISIONS/0047-the-bundle-may-carry-actions-the-link-may-not.md b/02-DECISIONS/0047-the-bundle-may-carry-actions-the-link-may-not.md new file mode 100644 index 0000000..4ba332c --- /dev/null +++ b/02-DECISIONS/0047-the-bundle-may-carry-actions-the-link-may-not.md @@ -0,0 +1,99 @@ +--- +status: accepted +date: 2026-08-26 +deciders: jochen +reconstructed: false +extends: 0043-a-declaration-is-an-ordered-list-of-owned-resources.md +--- + +# 47. The bundle may carry actions the link may not + +## Context + +[The substrate](../03-DESIGN/01-to-be/07-the-substrate.md) is raised in five steps, and steps two +and three happen before a mesh exists: + +``` +1 the host applies the bundle the store runs; no mesh exists +2 a database is created in it before there is anything to ask +3 the control plane's schema applied a migration against that database +4 the control plane starts +``` + +That was recorded as *the sharpest unresolved thing in the bootstrap path*, on the grounds that +creating a database inside a running store is not state on a machine. + +**That framing was wrong, and it was reading +[ADR 0043](0043-a-declaration-is-an-ordered-list-of-owned-resources.md) too narrowly.** *State +on this machine* does not mean the filesystem and the service manager. A service running on this +machine is part of this machine. Writing a file and creating a database in a local store differ +in mechanism, not in scope. + +The real question was underneath: **must the host learn what a database is?** + +## Considered options + +1. **Give the host a `database` resource type.** Then tier 0 knows Postgres — and next a bucket, + a virtual host, a repository. The host acquires the substrate's vocabulary one service at a + time, which is what [ADR 0037](0037-the-host-applies-it-does-not-decide.md) exists to stop. + Rejected. +2. **Have the control plane do all provisioning, including the bootstrap.** Rejected because it + is not there yet: at step 2 the thing that would do the provisioning does not exist. +3. **The bundle declares an action; the host runs it and reads back.** Chosen. + +## Decision + +**The host runs actions the bundle declares, and never learns what they mean.** + +A bundle step says *run this, against this, and here is how to tell whether it worked*. The host +executes it and verifies. What a database is stays knowledge of the module that provides one; +the host knows only how to run a declared action against something local and check the result. + +**Actions are permitted in the bundle and forbidden over the link.** + +[ADR 0039](0039-the-link-is-the-security-boundary.md) says what arrives over the link is +*declarations of known shape, never a command to run*. That stands, unchanged. The bundle is a +different trust path and the asymmetry is deliberate: + +| | the bundle | the link | +|---|---|---| +| where it comes from | the installer somebody built and copied onto the machine | a remote party | +| when it is fixed | at build time, pinned and reviewable | at any moment | +| what compromise means | whoever built the installer, who also built the binary | a control plane, which may be compromised separately | +| may contain an action | **yes** | **no** | + +The reasoning is that a bundle arrives *with* the binary. Anyone able to put a hostile action in +it could equally have put it in the host itself, so refusing actions there buys nothing while +costing the bootstrap. The link has no such property: it is a separate party, reachable +separately, and an action there is the unbounded blast radius ADR 0039 refuses. + +**And ongoing provisioning is not the host's at all.** Once a mesh exists, a consumer on one +node granted a database on another is provisioned by the control plane. The host never performs +a provisioning action from the link, so the asymmetry costs it nothing. + +## Consequences + +- **There are two provisioning paths, and that is the answer rather than a problem.** The host + runs bootstrap actions locally from the bundle; the control plane provisions across the mesh + afterwards. The earlier worry — *one mechanism with a tier boundary inside it* — dissolves, + because they are two mechanisms with different actors, scopes and trust models. +- **The host's vocabulary grows by one shape, not one per service.** `action` joins `directory`, + `file` and `service`. Adding a substrate service does not add a host resource type. +- **An action must say how to verify itself.** A step that runs and reports success without a + read-back is the fault this project is about, and an action is the easiest place to reintroduce + it — so the verification is part of the declaration rather than left to the applier. +- **This is the escape hatch [research 011](../01-RESEARCH/011-the-module-graph/features.md) + warned about.** Arbitrary code, in the one place it is hardest to remove later. It is bounded + by being bundle-only and by requiring its own verification, and that boundary is the whole + defence — it should be watched rather than trusted. +- **The bundle becomes something a person must be able to read.** If it can run commands, the + reason to keep it small and pinned stops being convenience and becomes review. + +## References + +- [ADR 0043](0043-a-declaration-is-an-ordered-list-of-owned-resources.md) — what a declaration is. +- [ADR 0039](0039-the-link-is-the-security-boundary.md) — bounded by form, over the link. +- [ADR 0037](0037-the-host-applies-it-does-not-decide.md) — why the host must not learn a + service's vocabulary. +- [`07-the-substrate.md`](../03-DESIGN/01-to-be/07-the-substrate.md) — the bootstrap order this + unblocks. diff --git a/03-DESIGN/01-to-be/07-the-substrate.md b/03-DESIGN/01-to-be/07-the-substrate.md index fdd7a9e..de17dd5 100644 --- a/03-DESIGN/01-to-be/07-the-substrate.md +++ b/03-DESIGN/01-to-be/07-the-substrate.md @@ -93,16 +93,20 @@ The order, from [research 011](../../01-RESEARCH/011-the-module-graph/worked-pro ``` **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. +the bootstrap rather than a service consumers use later. They are **actions** the bundle +declares and the host runs +([ADR 0047](../../02-DECISIONS/0047-the-bundle-may-carry-actions-the-link-may-not.md)) — so the +host's vocabulary grows by one shape rather than by one resource type per substrate service. ## 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 the host can do step 2.**~~ **Resolved** by + [ADR 0047](../../02-DECISIONS/0047-the-bundle-may-carry-actions-the-link-may-not.md). A service + running on this machine is part of this machine, so the scope was never in question — the real + question was whether the host must learn what a database is, and it must not. The bundle + declares an **action**; the host runs it and verifies it, and what a database means stays with + the module that provides one. - **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