A commit has no order, so the mesh says who runs what and claims no newer

novox/hq 04-ISSUES/087. The version I shipped this morning said "N
machine(s) run an older host than another machine does" and worked it out
by comparing versions as strings. A host reports its version as a commit.
Commits have no order.

On the live mesh it named the three machines running the NEWER host as the
ones behind: `ced54d4` sorts above `04a27ca` and means nothing. An
arbitrary lexicographic result, presented as a fact, about the one thing
this was built to make trustworthy.

It now reports the split — which machines run which version — and claims
no ordering:

  4 machine(s) do not all run the same host:
    04a27ca      g14, novox, shanks
    ced54d4      ace

    a host refuses a declaration carrying a field it does not know, whole
    — so the mesh may send only what every one of these understands. Which
    of them is newer is not readable from a commit; that needs a version
    the host reports as ordered

More useful as well as more honest: the reader sees who is on which side
of the split, which is what decides whether a field can be sent.

A report that confidently says the opposite of the truth is worse than one
that says less — which is the subject of 04-ISSUES/145, arriving by my own
door within an hour of my closing it.
This commit is contained in:
2026-09-30 09:28:56 +02:00
parent 2542aa67b0
commit 1d9c102889
2 changed files with 90 additions and 77 deletions
+40 -40
View File
@@ -189,23 +189,30 @@ func printStatus(asked answers) error {
fmt.Printf("\n `push --behind` sends them\n\n")
}
if older, newest := hostsBehind(nodes); len(older) > 0 {
if split := hostSplit(nodes); len(split) > 1 {
// **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.
// no record of which host any machine ran, so that order was kept by somebody remembering it.
//
// 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))
// **Disagreement, and deliberately not "behind".** A host reports its version as a commit, and
// commits have no order — the first version of this said "N machines run an older host" and
// named the three that were newer, because it compared two hashes as strings. What the mesh
// can say truthfully is that the machines do not all run the same host, and which machines
// hold which. Ordering needs a version that is ordered, and that is the host's to report.
versions := make([]string, 0, len(split))
for v := range split {
versions = append(versions, v)
}
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)
sort.Strings(versions)
fmt.Printf("%d machine(s) do not all run the same host:\n", len(nodes))
for _, v := range versions {
sort.Strings(split[v])
fmt.Printf(" %-12s %s\n", v, strings.Join(split[v], ", "))
}
fmt.Printf("\n a host refuses a declaration carrying a field it does not know, whole — so the\n" +
" mesh may send only what every one of these understands. Which of them is newer is\n" +
" not readable from a commit; that needs a version the host reports as ordered\n\n")
}
if len(asked.untaken) > 0 {
@@ -419,39 +426,32 @@ func (a answers) well() bool {
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.
// hostSplit is which machines report which host version, for every version more than one machine
// could disagree about.
//
// **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).
// **It does not say which is newer, because it cannot.** A host reports its version as a commit, and
// commits have no order. The first version of this returned "the machines behind the newest" by
// comparing versions as strings, and on the live mesh it named the three machines running the NEWER
// host as the ones behind — an arbitrary lexicographic result presented as a fact
// (novox/hq 04-ISSUES/087). A report that confidently says the opposite of the truth is worse than one
// that says less, which is the whole subject of 04-ISSUES/145.
//
// 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.
// So this answers what is checkable: who runs what. The reader sees the split and the mesh claims no
// ordering. Ordering wants an ordered version, and that is the host's to report rather than this
// function's to infer.
//
// 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 := ""
// Machines that have not reported a version are left out entirely: they are not a version, and
// counting them as one would invent a disagreement. `node show` says per machine that it has not said.
func hostSplit(nodes []inventory.Node) map[string][]string {
out := map[string][]string{}
for _, n := range nodes {
if n.HostVersion > newest {
newest = n.HostVersion
if n.HostVersion == "" {
continue
}
out[n.HostVersion] = append(out[n.HostVersion], n.Name)
}
if newest == "" {
return nil, "" // nothing has reported one; there is no disagreement to state
if len(out) < 2 {
return nil // one version, or none reported: nothing to disagree about
}
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
return out
}