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/0039-the-link-is-the-security-boundary.md
|
||||||
- 02-DECISIONS/0008-a-failed-step-fails-the-job.md
|
- 02-DECISIONS/0008-a-failed-step-fails-the-job.md
|
||||||
- 02-DECISIONS/0041-the-host-depends-on-nothing.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
|
# The node host
|
||||||
@@ -132,15 +133,23 @@ the mesh, and the full peer set arrives derived.
|
|||||||
|
|
||||||
## What a declaration is
|
## 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
|
**JSON**, because the host has no dependencies to spend and the standard library carries no
|
||||||
anything outside it is refused rather than best-effort interpreted.
|
YAML. **An ordered list of typed resources**, each with a stable identity — the order is stated
|
||||||
- It is per-node and complete: what this machine should be, not a delta against what it was.
|
rather than derived, because deriving it would be the host deciding the thing most likely to
|
||||||
A delta requires the sender to know what the receiver holds, which is the coupling the store
|
differ from what the control plane intended.
|
||||||
exists to remove.
|
|
||||||
- Every addition to the vocabulary widens what a compromised control plane can express, so it
|
**Unknown is refused, never skipped.** An unknown version, type or field refuses the whole
|
||||||
is a security artefact and additions are reviewed as such.
|
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
|
## 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:
|
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.
|
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.
|
**3 — link and store.** The node connects, receives declarations, and holds what it applied.
|
||||||
|
|
||||||
**4 — enrolment.** The one genuinely new mechanism in
|
**4 — enrolment.** The one genuinely new mechanism in
|
||||||
@@ -182,7 +197,6 @@ Each decision above owes a test:
|
|||||||
|
|
||||||
## Open
|
## 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
|
- **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.
|
if it is false the tier boundary moves.
|
||||||
- **What the host carries versus what it finds.** It manages `wg`, `nft`, `pacman`, `docker`;
|
- **What the host carries versus what it finds.** It manages `wg`, `nft`, `pacman`, `docker`;
|
||||||
|
|||||||
Reference in New Issue
Block a user