The mesh knows which host runs a machine, and says who is behind another

novox/hq 04-ISSUES/087. A host refuses a declaration carrying a field it
does not know, and refuses it WHOLE — deliberately, because that keeps a
half-understood declaration off a machine. It makes every new declaration
field a flag day: hosts first, then the controller. The mesh had no record
of which host any machine ran, so that order was kept by somebody
remembering it, and a machine that refused for this reason reported a
failure with nothing saying why.

The machine has reported its host version since ADR 0141. The
controller's own copy of the report did not have the field, so it was
unmarshalled into nothing and thrown away on arrival. It has it now,
records it, and shows it in `node show` — "not reported" rather than
blank, because a machine that has not said is not a machine running
nothing.

Status says which machines run an older host than another machine does,
and which is newest. Deliberately disagreement rather than staleness:
nothing delivers a host version yet (ADR 0141, accepted and not built), so
the mesh holds no canonical current version and cannot honestly say a
machine is behind THE host. What it can say is that the oldest host in the
mesh is what the mesh may send.

A machine that has reported nothing is left out rather than called
behind. Versions compare as strings, which suits the timestamps and
commits this mesh uses and is wrong for a scheme where "10" sorts before
"9" — said in the code, at the place that would have to learn.
This commit is contained in:
2026-09-30 08:53:59 +02:00
parent bfd983e3f8
commit 7683ba8b5b
7 changed files with 227 additions and 2 deletions
+56
View File
@@ -171,6 +171,25 @@ func statusCommand(ctx context.Context, args []string) error {
fmt.Printf("\n `push --behind` sends them\n\n")
}
if older, newest := hostsBehind(nodes); len(older) > 0 {
// **Before a declaration gains a field, every machine has to understand it** (novox/hq
// 04-ISSUES/087). A host refuses a declaration carrying a field it does not know, and refuses
// it whole, so every new field is a flag day: hosts first, then the controller. The mesh had
// no record of which host any machine ran, so that order was kept by somebody remembering it,
// and a machine that refused for this reason reported a failure with nothing saying why.
//
// Said as disagreement rather than as "out of date", because nothing delivers a host version
// yet (ADR 0141, not built) and so the mesh has no canonical current one. What it can say
// truthfully is that these machines do not all run the same host, and which is newest of the
// ones it has been told about.
fmt.Printf("%d machine(s) run an older host than another machine does:\n", len(older))
for _, n := range older {
fmt.Printf(" %-12s %s\n", n.Name, orNotReported(n.HostVersion))
}
fmt.Printf("\n the newest any machine reports is %s. A host refuses a declaration carrying a\n"+
" field it does not know, whole — so a new field reaches these machines last\n\n", newest)
}
if len(asked.untaken) > 0 {
// **Before the adopted line, and it breaks "all well".** An adopted machine is a state
// somebody chose and can leave alone; a module assigned to one and never taken is work
@@ -369,3 +388,40 @@ func (a answers) well() bool {
return len(a.wrong) == 0 && len(a.quiet) == 0 && len(a.behind) == 0 &&
len(a.waiting) == 0 && len(a.refused) == 0 && a.network == "" && len(a.untaken) == 0
}
// hostsBehind is every machine reporting an older host than the newest any machine reports, and that
// newest version.
//
// **Disagreement, not staleness.** Nothing delivers a host version yet
// ([ADR 0141](../../02-DECISIONS/0141-the-host-delivers-its-own-successor.md) is accepted and not
// built), so the mesh holds no canonical current version and cannot say a machine is behind THE host.
// It can say these machines are behind ANOTHER MACHINE's, which is the fact that matters before a
// declaration gains a field: the oldest host in the mesh is what the mesh may send
// (novox/hq 04-ISSUES/087).
//
// A machine that has not reported a version is left out rather than called behind. It may be running
// anything, and guessing in either direction is worse than saying it has not said — which `node show`
// does say, per machine.
//
// Versions are compared as strings, which is enough for the timestamps and commits this mesh uses and
// is wrong for a scheme where "10" sorts before "9". Saying so here rather than pretending: when a
// version becomes something ordered, this is the place that has to learn how.
func hostsBehind(nodes []inventory.Node) ([]inventory.Node, string) {
newest := ""
for _, n := range nodes {
if n.HostVersion > newest {
newest = n.HostVersion
}
}
if newest == "" {
return nil, "" // nothing has reported one; there is no disagreement to state
}
var older []inventory.Node
for _, n := range nodes {
if n.HostVersion != "" && n.HostVersion != newest {
older = append(older, n)
}
}
sort.Slice(older, func(i, j int) bool { return older[i].Name < older[j].Name })
return older, newest
}