From 671c2f3881df076ae5a35c2bfef788dfecbee5c6 Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 23 Sep 2026 23:27:08 +0200 Subject: [PATCH] Issue 107: a declaration carries no order; rescue on an enrolled node is reconcile, not apply FILE Both from the review of the issue-104 fix: a hand-applied file on an enrolled node is recorded as carried and would remove the foundation, and nothing on the wire orders one declaration against another. --- 03-DESIGN/01-to-be/09-the-node-lifecycle.md | 12 +++++- .../00-report.md | 42 +++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 04-ISSUES/107-a-declaration-carries-no-order/00-report.md diff --git a/03-DESIGN/01-to-be/09-the-node-lifecycle.md b/03-DESIGN/01-to-be/09-the-node-lifecycle.md index 61db9c4..a7f3810 100644 --- a/03-DESIGN/01-to-be/09-the-node-lifecycle.md +++ b/03-DESIGN/01-to-be/09-the-node-lifecycle.md @@ -8,7 +8,7 @@ code: - mesh-host packaging/nox-mesh-host-network.sh - mesh-controller internal/token - mesh-controller internal/inventory/nodes.go -updated: 2026-09-22 +updated: 2026-09-23 decisions: - 02-DECISIONS/0100-a-node-in-use-is-adopted-before-it-is-converged.md - 02-DECISIONS/0004-a-node-and-how-it-joins.md @@ -436,6 +436,16 @@ root can already do anything it can. The bound in [ADR 0005](../../02-DECISIONS/0005-the-node-host.md) is on what a **remote** party may push, not on what a person at the machine may do. +**But not on a node the mesh has spoken to.** Amended 2026-09-23, after +[issue 104](../../04-ISSUES/104-reconcile-applies-a-stale-declaration-and-refuses-nothing/00-report.md): +once a controller declaration has been kept on the node, `apply FILE` is refused, whatever the +file — a hand-applied file is recorded as *carried*, which the mesh can never remove and reports +as the machine's own, and a declaration carries no order, so the host cannot tell a newer file +from an older one ([issue 107](../../04-ISSUES/107-a-declaration-carries-no-order/00-report.md)). +Rescue on an enrolled node is `reconcile`, which re-applies what the mesh last said, previewed; +`apply FILE` is for a machine before enrolment. A `--rescue` that applies a hand-written file to an +enrolled node under a confirmation is open, not decided. + This replaces the three hand-run scripts that exist today — first node, joining, rescue — with one binary that has always been the same binary. diff --git a/04-ISSUES/107-a-declaration-carries-no-order/00-report.md b/04-ISSUES/107-a-declaration-carries-no-order/00-report.md new file mode 100644 index 0000000..01d3f6f --- /dev/null +++ b/04-ISSUES/107-a-declaration-carries-no-order/00-report.md @@ -0,0 +1,42 @@ +--- +status: located +opened: 2026-09-23 +located-in: [mesh-controller internal/link, mesh-host internal/link] +fixed-by: +amended-design: +--- + +# 107 — A declaration carries no order, so a host cannot tell an older one from a newer + +## What was observed + +Reviewing the fix for [issue 104](../104-reconcile-applies-a-stale-declaration-and-refuses-nothing/00-report.md), +2026-09-23. A signed declaration carries a vocabulary version, the node it is for, its mode and +its resources — and nothing that orders it against another. Its only identity is the digest of +its bytes. So a host asked to apply a file can say "this is not the one the mesh last sent"; it +cannot say "this is older". + +The same absence reaches the link. The host drains a backlog of declarations and applies the +newest it received, but "newest" is decided by arrival within a batch of sixteen and a 750 ms +window: a backlog of more than sixteen pushes queued across a `converge`/`adopt` pair, or a slow +broker splitting one, applies a declaration the controller had already superseded. Not observed; +constructed from the code, and narrow — but a converged declaration applied to a node that has +since been returned to adopted is the incident of issue 104 by another door. + +## Why it matters beyond this instance + +Ordering is the one property a declaration needs that its signature does not give it. Every +refusal the host can make about staleness today is "not the last", which is both too strict (a +legitimately newer file is refused too) and too weak (a replay within a batch is not caught). The +controller already holds a per-node lock while it composes and records each send; the order +exists there and is thrown away at the wire. + +## Open questions + +- Should the signed declaration carry a per-node `sequence`, assigned under the controller's node + hold and persisted with the node, and `supersedes` — the digest of the previous send — so a host + refuses anything not strictly newer, on the link and from a file alike? +- Should genesis sign its rewritten bundle as sequence zero, so one rule covers the bundle and no + separate genesis-digest branch is needed on the host? +- Is a sequence enough, or does a mode change deserve its own marker, so a replayed converged + declaration is refused by mode as well as by order? -- 2.54.0