--- 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.