The machine has reported its host version since ADR 0141, whose own comment says why it must: without it nothing can say a machine is behind. The controller's copy of the report did not have the field, so it unmarshalled into nothing and was thrown away on arrival. Two structs describe one message and only the sending side had it. node show names it per machine, "not reported" where the mesh has not been told. status names every machine running an older host than another does, and which is newest. Disagreement rather than staleness, deliberately: nothing delivers a host version yet, so the mesh holds no canonical current one and "behind" has no fixed point. What it can say is that the oldest host in the mesh is what the mesh may send. Two refusals to guess: a machine that reported nothing is not called behind, and versions compare as strings — right for the timestamps this mesh uses, wrong for a scheme where 10 sorts before 9, said at the place that would have to learn. 107 gains the note that this is what makes its new field safe to consider, and that one machine of four is behind today, so it is not free yet.
56 lines
3.0 KiB
Markdown
56 lines
3.0 KiB
Markdown
---
|
|
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?
|
|
|
|
## What has since made this safer to do (2026-09-30)
|
|
|
|
Adding a `sequence` to a declaration is adding a field, and a host refuses a declaration carrying a
|
|
field it does not know — whole. That was
|
|
[issue 087](../087-the-controller-cannot-tell-a-host-is-too-old/00-report.md), and it is resolved: the
|
|
mesh now records which host each machine reports and `status` names every machine running an older one
|
|
than another does.
|
|
|
|
So the flag day is visible before it is walked into, which it was not when this was filed. It does not
|
|
make the field free: **the oldest host in the mesh is still what the mesh may send**, and one machine of
|
|
four is behind today. A sequence that an old host refuses takes that machine out of the mesh's reach
|
|
entirely — worse than the replay it prevents, which has never been observed.
|