0042 becomes the operator's own rule and stops there: every merge into the main branch is notified and approved. Notified means proposed and said out loud, not performed and mentioned; approved means a person says yes to THAT merge. Who performs it is not the thing worth constraining, which is what makes an agent merging its own work unremarkable — the checkpoint already happened. 0043 answers the question it did not: where the ordered list comes from. By hand today, in substrate.lock, because the first node has no control plane to derive anything from. Afterwards the control plane derives it from module assignments, resolved configuration, and what each module declares it needs — ordered by the dependency graph, which is research 011. So the record is complete on the consumer side and deliberately silent on the producer side, and that is a legitimate order to settle them in: the host must refuse what it does not understand whoever wrote it. One consequence that only appeared when the question was asked: if the graph turns out not to determine a total order, that is 011's problem and not the host's. The host is still handed a list and still applies it as given. Recorded because it is the seam where a future difficulty would otherwise try to migrate into tier 0. Both were marked accepted before they had been read. Approved now, so the field is true — which it was not when it was written.
7.3 KiB
status, date, deciders, reconstructed, extends
| status | date | deciders | reconstructed | extends |
|---|---|---|---|---|
| accepted | 2026-08-26 | jochen | false | 0037-the-host-applies-it-does-not-decide.md |
43. A declaration is an ordered list of resources the host owns
Context
05-the-node-host.md leaves what a declaration
is open and calls it the first thing to settle in build. Stage 2 — applying with no mesh
present — cannot start without it.
Three constraints already bind it, and between them they decide most of the shape:
- Data, not instructions, with a finite, versioned vocabulary and anything outside it refused rather than interpreted (ADR 0039).
- The host applies; it does not decide (ADR 0037).
- The host depends on nothing (ADR 0041).
Decision
JSON, because the host has no dependencies to spend
Go's standard library carries encoding/json and no YAML. A YAML declaration would put a
third-party parser inside the one binary whose entire argument is that it needs nothing — to
gain authoring comfort in a document that is, in the ordinary case, generated by a machine and
read by a machine.
The mesh's authoring formats stay YAML. What crosses the link is JSON.
An ordered list, because ordering is a decision
A declaration states the order its resources are applied in. The host does not sort, does not resolve dependencies, and does not decide what must come before what.
This follows from ADR 0037 more strictly than it first appears. A host that derived ordering from declared dependencies would be deciding, and it would be deciding the thing most likely to differ between what the control plane intended and what the machine does. The control plane knows what depends on what; it says so by saying when.
Consequence accepted: the control plane must order correctly, and a mis-ordered declaration fails at the step that needed something not yet there — which is at least the right failure, naming the resource rather than a mystery.
Every resource has a stable identity
Not a position, not a hash of its content: a name the control plane keeps stable across declarations. It is what lets the store say this is the same resource I applied last time, which is what makes convergence possible at all.
Unknown is refused, never skipped
An unknown declaration version, an unknown resource type, or an unknown field is a refusal of the whole declaration. Not a warning, not a skip, not best-effort.
A host that skipped what it did not understand would apply most of a declaration and report success — a node that looks configured and is not, which is 04-ISSUES/003 with the declaration on the other side of the wire. Refusing whole also means an older host cannot be handed a newer vocabulary and quietly do half of it.
Complete for what the host owns, and only that
Desired state invites the question of removal, and the honest answer needs a boundary.
The host removes what it previously applied and is no longer declared. It knows what it
applied because it recorded it (store), so this is a fact it holds rather than an inference.
The host never removes anything it did not create. A machine has things on it that the mesh did not put there, and a converger that treats not declared as must not exist deletes them. The rule that prevents production data loss elsewhere in this repository is the same one: ADR 0018 exists because a tool did something to a path it did not own.
So: authoritative over its own footprint, inert everywhere else.
Addressed, and checked when it can be
A declaration names who it is for. A host that has an identity refuses one addressed elsewhere. A host that has no identity yet — the first node, applying the bundle it carries — has nothing to check against and applies it.
Where the list comes from
This record specifies what the host accepts. What produces a declaration is deliberately not settled here, and the reason is worth stating rather than leaving as an omission.
Today, and at stage 2: by hand. substrate.lock is authored and pinned — a person writes
the resources and writes the order. That is the first node's path, where there is no control
plane to derive anything from.
Afterwards: the control plane derives it, from three things it already holds — which modules are assigned to this node, what those modules' configuration resolves to, and what each module declares it needs.
And the order comes from the graph. Each module expands to resources; the modules are
ordered by their declared dependencies on one another. That is
research 011 — requires, provides,
excludes — and a declaration is the graph's output, flattened for one node.
So this record is complete on the consumer side and silent on the producer side, because the producer does not exist and its shape is what 011 is investigating. The consumer can be settled first because the host must refuse what it does not understand whoever wrote it.
What this means for ordering. ADR 0037 puts the ordering decision in the control plane; 011 decides how the control plane makes it. If the graph turns out not to determine a total order, that is 011's problem to solve and not the host's — the host will still be handed a list, and will still apply it as given.
Consequences
- Ordering is now a control-plane responsibility, and getting it wrong is a class of bug that will appear. It is the correct place for it: the alternative puts a dependency solver in tier 0 and a decision in the wrong tier.
- The vocabulary is a security artefact. Every type added widens what a compromised control plane can express, so additions are reviewed as such rather than as features.
- Removal is bounded but not free. A resource dropped from a declaration is deleted on the next apply, so removing a line is an act with an effect — which is the point, and is worth saying out loud because it does not look like one.
- The store becomes load-bearing at stage 2, earlier than the build order suggests. Nothing can be removed without knowing what was applied, so the record of applied resources arrives with the first apply rather than with the link.
- A closed address space bounds what the first types can be. A scenario has no route to a
package repository, so a declaration whose resources must be fetched cannot be applied in the
lab at all. The first vocabulary is therefore what needs no network — files, directories,
service state — and packages and containers wait on where
place:gets its artifacts from, which is open in02-scenario-declaration.md.