The approval is the checkpoint, and what a declaration is #10
@@ -0,0 +1,113 @@
|
||||
---
|
||||
status: accepted
|
||||
date: 2026-08-26
|
||||
deciders: jochen
|
||||
reconstructed: false
|
||||
extends: 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`](../03-DESIGN/01-to-be/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](0039-the-link-is-the-security-boundary.md)).
|
||||
- **The host applies; it does not decide** ([ADR 0037](0037-the-host-applies-it-does-not-decide.md)).
|
||||
- **The host depends on nothing** ([ADR 0041](0041-the-host-depends-on-nothing.md)).
|
||||
|
||||
## 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](0037-the-host-applies-it-does-not-decide.md) 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](../04-ISSUES/003-firewall-scope-is-read-by-no-code/00-report.md) 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](0018-the-mesh-creates-no-symlinks.md) 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.
|
||||
|
||||
## 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 in
|
||||
[`02-scenario-declaration.md`](../03-DESIGN/01-to-be/02-scenario-declaration.md).
|
||||
|
||||
## References
|
||||
|
||||
- [ADR 0037](0037-the-host-applies-it-does-not-decide.md) — why ordering is not the host's.
|
||||
- [ADR 0039](0039-the-link-is-the-security-boundary.md) — bounded by form.
|
||||
- [ADR 0041](0041-the-host-depends-on-nothing.md) — why JSON.
|
||||
- [ADR 0008](0008-a-failed-step-fails-the-job.md) — why a refusal is whole.
|
||||
@@ -11,6 +11,7 @@ decisions:
|
||||
- 02-DECISIONS/0039-the-link-is-the-security-boundary.md
|
||||
- 02-DECISIONS/0008-a-failed-step-fails-the-job.md
|
||||
- 02-DECISIONS/0041-the-host-depends-on-nothing.md
|
||||
- 02-DECISIONS/0043-a-declaration-is-an-ordered-list-of-owned-resources.md
|
||||
---
|
||||
|
||||
# The node host
|
||||
@@ -132,15 +133,23 @@ the mesh, and the full peer set arrives derived.
|
||||
|
||||
## What a declaration is
|
||||
|
||||
**Open, and the first thing to settle in build.** The shape is constrained but not chosen:
|
||||
Settled by [ADR 0043](../../02-DECISIONS/0043-a-declaration-is-an-ordered-list-of-owned-resources.md).
|
||||
|
||||
- It is data, not instructions — the host's vocabulary is finite, versioned and auditable, and
|
||||
anything outside it is refused rather than best-effort interpreted.
|
||||
- It is per-node and complete: what this machine should be, not a delta against what it was.
|
||||
A delta requires the sender to know what the receiver holds, which is the coupling the store
|
||||
exists to remove.
|
||||
- Every addition to the vocabulary widens what a compromised control plane can express, so it
|
||||
is a security artefact and additions are reviewed as such.
|
||||
**JSON**, because the host has no dependencies to spend and the standard library carries no
|
||||
YAML. **An ordered list of typed resources**, each with a stable identity — the order is stated
|
||||
rather than derived, because deriving it would be the host deciding the thing most likely to
|
||||
differ from what the control plane intended.
|
||||
|
||||
**Unknown is refused, never skipped.** An unknown version, type or field refuses the whole
|
||||
declaration. A host that skipped what it did not understand would apply most of it and report
|
||||
success.
|
||||
|
||||
**Complete for what the host owns, and only that.** It removes what it previously applied and
|
||||
is no longer declared — a fact it holds, from the store, rather than an inference — and never
|
||||
removes anything it did not create.
|
||||
|
||||
**Addressed.** A host with an identity refuses a declaration addressed elsewhere; a host
|
||||
without one, applying the bundle it carries, has nothing to check against.
|
||||
|
||||
## Build order
|
||||
|
||||
@@ -154,6 +163,12 @@ what it is. No control plane, no declarations, no network. Verifiable immediatel
|
||||
the first node's path, and it is the claim the skeleton's Move 1 rests on and has never proved:
|
||||
that one host can raise the substrate alone.
|
||||
|
||||
The first vocabulary is bounded by something the lab makes unavoidable: **a scenario is a
|
||||
closed address space**, so a resource that must be fetched cannot be applied there at all. So
|
||||
stage 2 begins with what needs no network — files, directories, service state — and the types
|
||||
that need artifacts wait on where those come from, which is open in
|
||||
[`02-scenario-declaration.md`](02-scenario-declaration.md).
|
||||
|
||||
**3 — link and store.** The node connects, receives declarations, and holds what it applied.
|
||||
|
||||
**4 — enrolment.** The one genuinely new mechanism in
|
||||
@@ -182,7 +197,6 @@ Each decision above owes a test:
|
||||
|
||||
## Open
|
||||
|
||||
- **What a declaration is.** Above; the first thing to settle.
|
||||
- **Whether one host can raise the substrate alone.** Move 1 assumes it. Stage 2 tests it, and
|
||||
if it is false the tier boundary moves.
|
||||
- **What the host carries versus what it finds.** It manages `wg`, `nft`, `pacman`, `docker`;
|
||||
|
||||
Reference in New Issue
Block a user