Hosts first, then the controller — a build and a push each, now that the mesh delivers the host. The host refuses a lower sequence than it kept and drains a batch by sequence rather than arrival; the controller numbers each send under the node's hold, inside the signed bytes. Measured: two pushes, sequence 2 in the kept declaration, counters in the store agree, no machine reads as behind. That last one is the subtlety: the mesh compares the digest of what it would send against what it did, and a number changes the bytes, so the read-only comparison composes with the last number sent rather than a fresh one.
61 lines
3.1 KiB
Markdown
61 lines
3.1 KiB
Markdown
# 107 — resolved: a declaration carries its order
|
|
|
|
*2026-09-30. Measured on the mesh.*
|
|
|
|
## What was done
|
|
|
|
**Hosts first, then the controller** — the order [issue 087](../087-the-controller-cannot-tell-a-host-is-too-old/00-report.md)
|
|
says a new declaration field needs, and now a build and a push rather than an expedition
|
|
([issue 142](../142-the-host-is-the-one-thing-the-mesh-does-not-deliver/01-progress.md)).
|
|
|
|
The host understands a `sequence` on a declaration and tolerates its absence: absent reads as "no
|
|
order claimed", not "first", so a controller that sends none is still understood and a host that kept
|
|
a declaration before it understood the field compares nothing. It refuses a declaration with a lower
|
|
sequence than the one it kept, whole, and says why; and the drain that picks one declaration from a
|
|
batch keeps the highest sequence rather than the last to arrive — which is the case the report
|
|
constructed, a backlog drained out of order.
|
|
|
|
The controller numbers each send: the next number for that node, taken under the node's hold, before
|
|
the body exists, so the number is inside what the mesh signs and a replayed older declaration cannot
|
|
borrow a newer one's.
|
|
|
|
## Measured
|
|
|
|
```
|
|
push shanks; push shanks
|
|
sequence in kept declaration: 2
|
|
node sequence
|
|
novox 2
|
|
shanks 2
|
|
ace (none — not sent since numbering)
|
|
g14 (none)
|
|
status: nobody "not running what the mesh would send them"
|
|
```
|
|
|
|
Both applies went through; neither was refused; the machine holding the earlier one accepted the later.
|
|
|
|
## The subtlety, which would have read every machine as behind for ever
|
|
|
|
The mesh decides a machine is behind by comparing the digest of what it **would** send against what it
|
|
**did** send. A number changes the bytes. So the read-only comparison composes with the number the
|
|
machine was *last* sent — not a fresh one — and is byte for byte what was sent when nothing else
|
|
changed. Without that, numbering would have made `status` name all four machines as out of date on
|
|
every reading, permanently.
|
|
|
|
## The open questions
|
|
|
|
- *A per-node `sequence` under the controller's node hold?* Yes, as described. **`supersedes` — the
|
|
previous digest — is not added.** A strictly-greater sequence gives the ordering; a chain of digests
|
|
would give continuity, which nothing here needs yet and which every re-composition would break.
|
|
- *Genesis signing its bundle as sequence zero?* Zero is "no order claimed", which is what the bundle
|
|
carries by carrying nothing. Same rule, no genesis branch.
|
|
- *A marker for a mode change?* Not needed for the incident it guards: a replayed converged declaration
|
|
reaching a node returned to adopted is already refused **by mode**, before this check runs.
|
|
|
|
## How it is checked
|
|
|
|
Host: an older sequence is refused, a newer or equal one is not, and no order claimed on either side
|
|
compares nothing; the drain keeps the highest sequence, and falls back to arrival when none is claimed.
|
|
Controller: a send carries its number inside the signed bytes, an unnumbered send is byte for byte what
|
|
it was before, and each node's counter is one higher per send and readable for the comparison.
|