status: one machine that cannot be worked out no longer takes the answer from the rest
`status --json` emitted no JSON at all when a single node was unresolvable. A blocked node is not on the private network, and a mesh whose hub is that node has no hub — which came back through the reading as a refusal, so `status` printed nothing and `status --json` put multi-line prose on stderr and not one byte on stdout. A machine-readable interface that stops being machine-readable exactly when something is wrong is one nobody can build an alarm on. Why a machine cannot be worked out is read as data now, per machine, through the same whoResolves the private network is built from — so this and the network agree about who could not be resolved rather than deciding it twice. The private network failing to compute is kept as a note beside it instead of ending the read: it is almost always a consequence of those same refusals, and every question that does not depend on it is still answered. It reaches all three ways of saying it, from the one reading: the text form leads with it because a machine here is in none of the answers below, the JSON carries `unresolved` (always a list, never null) and `network`, and the page has a section of its own. That also closes a silent success. A machine that resolves to nothing has nothing computed for it, so there is nothing to compare it against and nothing it can be behind — it appeared in no answer at all, and `status` reported a mesh where nothing could be sent anywhere as "all doing what they were told". statusAsJSON takes the whole reading now rather than a growing argument list, which is what let an answer be added to the text form and forgotten here. The two are one function's output in two shapes and must not be able to differ about what was asked. Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
This commit is contained in:
@@ -3,9 +3,8 @@ package main
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/novox/mesh-control/internal/inventory"
|
||||
)
|
||||
|
||||
// The same answers, in a shape something other than a person can read.
|
||||
@@ -40,11 +39,30 @@ type meshStatus struct {
|
||||
// whose is older is still working — and Waiting cannot tell those apart, because the sent
|
||||
// digest is recorded at send, not at apply.
|
||||
Reported []machineReported `json:"reported"`
|
||||
// Unresolved is every machine that cannot be worked out at all, with what the mesh said when
|
||||
// it tried. **A machine here is in none of the lists above**: nothing was computed for it, so
|
||||
// there is nothing to compare it against and nothing it can be behind — which is why a
|
||||
// document without this field described a wholly blocked mesh as a well one.
|
||||
//
|
||||
// Per machine, and data. One node failing must never take the document away from a reader
|
||||
// asking about the others.
|
||||
Unresolved []machineUnresolved `json:"unresolved"`
|
||||
// Network is why the private network could not be computed, when it could not; absent when it
|
||||
// could. Almost always a consequence of Unresolved: a node that does not resolve is not on the
|
||||
// network, and a mesh whose hub is that node has no hub.
|
||||
Network string `json:"network,omitempty"`
|
||||
// Machines is how many the mesh knows about, so a reader can tell "none wrong" from
|
||||
// "none at all".
|
||||
Machines int `json:"machines"`
|
||||
}
|
||||
|
||||
type machineUnresolved struct {
|
||||
Node string `json:"node"`
|
||||
// Problem is the mesh's own words, whole — newlines and all. It lists every requirement that
|
||||
// could not be met, and a first line alone would name one of them and hide the rest.
|
||||
Problem string `json:"problem"`
|
||||
}
|
||||
|
||||
type machineDoing struct {
|
||||
Node string `json:"node"`
|
||||
// Outcome is refused or failed. Kept distinct all the way out: they are fixed in different
|
||||
@@ -92,14 +110,32 @@ type moduleBehind struct {
|
||||
On []string `json:"on"`
|
||||
}
|
||||
|
||||
// statusAsJSON answers the same three questions as the text form, from the same calls.
|
||||
func statusAsJSON(wrong []inventory.Doing, nodes []inventory.Node, quiet []inventory.Node,
|
||||
behind map[string][]string, sources map[string]inventory.Source,
|
||||
waiting []inventory.Machine, reported []inventory.Reported) ([]byte, error) {
|
||||
// statusAsJSON answers the same questions as the text form, from the same reading.
|
||||
//
|
||||
// **It takes the whole reading rather than a growing argument list**, which is what let a new
|
||||
// answer be added to the text form and forgotten here — the two are one function's output in two
|
||||
// shapes, and they must not be able to differ about what was asked.
|
||||
//
|
||||
// It never fails on account of the mesh. Every per-machine problem in here is a field, so one
|
||||
// machine that cannot be worked out cannot stop a caller reading about the others: a
|
||||
// machine-readable interface that stops being machine-readable exactly when something is wrong is
|
||||
// one nobody can build an alarm on.
|
||||
func statusAsJSON(asked answers) ([]byte, error) {
|
||||
wrong, nodes, quiet := asked.wrong, asked.nodes, asked.quiet
|
||||
behind, sources := asked.behind, asked.sources
|
||||
waiting, reported := asked.waiting, asked.reported
|
||||
|
||||
out := meshStatus{Machines: len(nodes), Wrong: []machineDoing{},
|
||||
Quiet: []machineQuiet{}, Behind: []moduleBehind{}, Waiting: []machineWaiting{},
|
||||
Reported: []machineReported{}}
|
||||
Reported: []machineReported{}, Unresolved: []machineUnresolved{},
|
||||
Network: asked.network}
|
||||
for name := range asked.refused {
|
||||
out.Unresolved = append(out.Unresolved, machineUnresolved{
|
||||
Node: name, Problem: asked.refused[name]})
|
||||
}
|
||||
sort.Slice(out.Unresolved, func(i, j int) bool {
|
||||
return out.Unresolved[i].Node < out.Unresolved[j].Node
|
||||
})
|
||||
for _, r := range reported {
|
||||
out.Reported = append(out.Reported, machineReported{
|
||||
Node: r.Node, Outcome: r.Outcome, At: r.At, Sent: r.Sent, Current: r.Current})
|
||||
|
||||
Reference in New Issue
Block a user