From 187fb63439620d80d92746a4e03110c6ec571c63 Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 29 Oct 2026 21:35:16 +0100 Subject: [PATCH] Add the four health seats, mesh-conditions, mesh-watchdogs, mesh-self-check and mesh-healers, so the controller's health has homes its lease does not hold (issue 1952 S1, ADR 0564 clause 1) --- go/catalogue/health_seats.go | 133 +++++++++++++++++++++++++++++++++++ go/catalogue/seats.go | 7 ++ go/catalogue/seats_test.go | 21 +++++- 3 files changed, 158 insertions(+), 3 deletions(-) create mode 100644 go/catalogue/health_seats.go diff --git a/go/catalogue/health_seats.go b/go/catalogue/health_seats.go new file mode 100644 index 0000000..79ae2c6 --- /dev/null +++ b/go/catalogue/health_seats.go @@ -0,0 +1,133 @@ +package catalogue + +// **The mesh's health, out of the controller** (novox/hq ADR 0564 clause 1, issue 1952): four seats at mesh scope, each +// held once, each holder a process on its own bus credential that writes only its own state. +// +// - mesh-conditions, held by the module mesh-conditions (the condition keeper): the one writer of the open conditions, +// raised from the observers' findings and cleared when they go (clauses 2, 3, 9, 13); +// - mesh-watchdogs, held by the watchman (mesh-delivery-watchman): the signals as keys that expire, its stuck keys and +// presence as findings, and the dead letters from the lease write (clauses 4, 5); +// - mesh-self-check, held by the module mesh-self-check: the probes, every five minutes (clause 6); +// - mesh-healers, held by the module mesh-healers: the healers, acting on the keeper's events (clauses 7, 8). +// +// Their events keep the controller's names and bodies (go/health), so a reader changes only the seat it hears. + +// The four health seats. +const ( + ConditionsSeat = "mesh-conditions" + WatchdogsSeat = "mesh-watchdogs" + SelfCheckSeat = "mesh-self-check" + HealersSeat = "mesh-healers" +) + +// healthDecision is the record that made the four seats. +const healthDecision = "novox/hq ADR 0564" + +// conditionsVerbs are the condition keeper's verbs (ADR 0564 clauses 3, 9, 12). +func conditionsVerbs() []Verb { + return []Verb{ + {Name: "conditions", Description: "What is wrong with the mesh now: every open condition, urgent first, then oldest, " + + "raised from the observers' findings (the watchman, the self-check, the healers, the assigner) and cleared when " + + "the finding goes, never by hand. Given a key, that one whole with its evidence; with history, every transition " + + "lately, the controller's before the take-over included.", + Input: schema(map[string]string{ + "key": "a condition's key: that one whole; with history, only its transitions", + "scope": "only this scope: machine, plan, call, build, merge, provider, seat, bus, core, probe, mesh, delivery or module", + "severity": "only urgent, or only warning", + "machine": "only those about this machine", + "history": "\"true\": every raising, change, silence and clearing lately, oldest first", + "days": "with history: how many days back (default 7, at most 90)", + }, nil, "history")}, + {Name: "silence", Description: "Stop one condition's messages for a while, a hand act with why: it stays open and in " + + "status, and the silence is kept as its transition in history.", + Input: schema(map[string]string{ + "key": "the condition's key", + "for": "how long: 30m, 4h, 2d; at most 7d", + "why": "why, required and recorded", + "cause": "the cause in a word (the condition's kind when absent)", + }, []string{"key", "for", "why"})}, + {Name: "compare", Description: "The keeper's open conditions against the controller's: every key open in one and not " + + "the other, or open in both with another severity, each with its reason; the kinds that end with the controller " + + "and the keys held while their module is delivered listed apart and not counted. The take-over waits for it empty.", + Input: schema(map[string]string{}, nil)}, + {Name: "take-over", Description: "Ask the operator's Approve to switch who the router, the grants part and " + + "mesh-watcher hear from the controller to the keeper; refused while compare shows a difference. On the warrant " + + "the keeper writes its take-over key.", + Input: schema(map[string]string{"why": "why now, shown to the operator"}, []string{"why"})}, + {Name: "hand-back", Description: "Switch the readers back to the controller's conditions, until the controller's " + + "lease is handed away; refused after it.", + Input: schema(map[string]string{"why": "why, kept with the switch"}, []string{"why"})}, + {Name: "resync", Description: "Read every observer's findings again and raise and clear what differs from the open " + + "conditions: the recovery of a keeper that missed a change.", + Input: schema(map[string]string{}, nil)}, + } +} + +// watchdogsVerbs are the watchman's verbs (ADR 0564 clauses 4, 5, 12). +func watchdogsVerbs() []Verb { + return []Verb{ + {Name: "signals", Description: "Every watchdog of the signals table the watchman keeps, each a key that expires unless " + + "its signal renews it: its row, what renews it, its bound, the age of its newest signal, and the finding its " + + "expiry wrote.", + Input: schema(map[string]string{"row": "one row, e.g. S1"}, nil)}, + {Name: "stuck", Description: "Every want, build or step the watchman says stopped moving past its bound, with since " + + "when and the words of its finding.", + Input: schema(map[string]string{"machine": "only those on this machine"}, nil)}, + {Name: "presence", Description: "Every machine and tool runner the watchman sees connected, with its last word, and " + + "those it says silent; a machine that said it sleeps is shown as sleeping, not silent.", + Input: schema(map[string]string{"machine": "one machine"}, nil)}, + {Name: "dead-letters", Description: "Every message a consumer on the bus gave up on, kept in DEAD_LETTERS: whose " + + "consumer, the subject, how often it was handed over and when it was given up, newest first. With id: that one " + + "whole. With deliver or drop, a hand act with why, taken only once the controller's lease is handed away; " + + "before, refused naming the controller's verb.", + Input: schema(map[string]string{ + "id": "a dead letter's id, as the list gives it: that one whole, with its message", + "consumer": "a consumer's name, or .: only what that one gave up on", + "limit": "how many to list, newest first (default 50); only when listing", + "deliver": "a dead letter's id: deliver it again to the consumer that gave it up (needs why)", + "drop": "a dead letter's id: drop it for good (needs why)", + "why": "with deliver or drop: why, required and recorded", + "cause": "with deliver or drop: the cause in a word (dead-letter when absent)", + }, nil)}, + {Name: "resync", Description: "Rebuild presence from the bus's connections, and the pulses and findings from the keys " + + "the watchman watches, and take every dead-letter notice waiting: the recovery of a watchman that missed a change.", + Input: schema(map[string]string{}, nil)}, + } +} + +// selfCheckVerbs are the self-check's verbs (ADR 0564 clause 6). +func selfCheckVerbs() []Verb { + return []Verb{ + {Name: "doctor", Description: "The self-check: the last run's verdict at once, each probe passed, failed, deferred " + + "(its facts not on the bus yet, named) or could not run, and how long ago. With run, a run now; with probe, one " + + "probe's verdict whole. Runs every five minutes on its own; each failure is a finding.", + Input: schema(map[string]string{ + "run": "\"true\": run every probe now and answer the verdict", + "probe": "one probe's id (D6): its verdict alone, with every finding", + }, nil, "run")}, + {Name: "probes", Description: "The registry: what each probe asserts, where it reads its facts, and the finding it writes.", + Input: schema(map[string]string{}, nil)}, + } +} + +// healersVerbs are the healers' verbs (ADR 0564 clauses 7, 8). +func healersVerbs() []Verb { + return []Verb{ + {Name: "healers", Description: "The healers: each registered response to one kind of condition, its repair, its " + + "budget and what happens when it is spent; every act taken lately with its outcome, the acts it would have " + + "taken while the controller's lease names a controller, and the mesh-wide brake.", + Input: schema(map[string]string{"days": "how many days of acts back (default 7)"}, nil)}, + {Name: "heal", Description: "Take one healer's act for one open condition now, within its budget and the brake.", + Input: schema(map[string]string{ + "key": "the condition's key", + "why": "why now, kept with the act", + }, []string{"key", "why"})}, + {Name: "drill", Description: "Record a drill: something broken on purpose to see the mesh raise and clear it, with " + + "what it tests. A drill is never a repair: no healer is wanted for it however often it is run.", + Input: schema(map[string]string{ + "what": "what was done on purpose, in a line", + "why": "what the drill tests", + "condition": "the key of the condition the drill is meant to raise, if any (optional)", + }, []string{"what", "why"})}, + } +} diff --git a/go/catalogue/seats.go b/go/catalogue/seats.go index 56637b8..3082ce8 100644 --- a/go/catalogue/seats.go +++ b/go/catalogue/seats.go @@ -280,6 +280,13 @@ var defaultSeats = append([]Seat{ {Name: PlacementSeat, Scope: ScopeMesh, Serves: placementVerbs(), Decision: "novox/hq ADR 0567"}, // The delivery's checker (novox/hq ADR 0547 §10, issue 774): the try is its verb, not the controller's. {Name: CheckerSeat, Scope: ScopeMesh, Serves: checkerVerbs(), Decision: "novox/hq ADR 0547"}, + // The mesh's health, out of the controller (novox/hq ADR 0564 clause 1, health_seats.go): the condition keeper, the + // watchman's watchdogs, the self-check and the healers. + {Name: ConditionsSeat, Scope: ScopeMesh, Decision: healthDecision, + Emits: []string{"condition-raised", "condition-changed", "condition-cleared"}, Serves: conditionsVerbs()}, + {Name: WatchdogsSeat, Scope: ScopeMesh, Decision: healthDecision, Serves: watchdogsVerbs()}, + {Name: SelfCheckSeat, Scope: ScopeMesh, Decision: healthDecision, Emits: []string{"checked"}, Serves: selfCheckVerbs()}, + {Name: HealersSeat, Scope: ScopeMesh, Decision: healthDecision, Emits: []string{"healer-acted"}, Serves: healersVerbs()}, // Deferred renames (novox/hq ADR 0121): these deliver a provision, so renaming them is a // delivering-seat migration with a mesh-wide cascade if a holder stops resolving mid-flight. // They keep their names until that migration is done deliberately, apart from the node-* pass. diff --git a/go/catalogue/seats_test.go b/go/catalogue/seats_test.go index 9113bd7..bd22358 100644 --- a/go/catalogue/seats_test.go +++ b/go/catalogue/seats_test.go @@ -64,6 +64,20 @@ func TestTheSeatsAreAClosedSetAndEachNamesItsDecision(t *testing.T) { if strings.Join(checkerVerbs, ",") != "try,try-read,try-end" { t.Errorf("the checker seat serves %v", checkerVerbs) } + // The four health seats (novox/hq ADR 0564 clause 1): at mesh scope, each held once, each naming ADR 0564, with the + // verbs and events the record names; the events keep the controller's names (clause 3). + for _, c := range []struct{ seat, verbs, emits string }{ + {ConditionsSeat, "conditions,silence,compare,take-over,hand-back,resync", "condition-raised,condition-changed,condition-cleared"}, + {WatchdogsSeat, "signals,stuck,presence,dead-letters,resync", ""}, + {SelfCheckSeat, "doctor,probes", "checked"}, + {HealersSeat, "healers,heal,drill", "healer-acted"}, + } { + s, ok := SeatNamed(c.seat) + if !ok || s.Scope != ScopeMesh || s.Replicated || s.Delivers != "" || s.Decision != "novox/hq ADR 0564" || + strings.Join(VerbNames(s.Serves), ",") != c.verbs || strings.Join(s.Emits, ",") != c.emits { + t.Errorf("the health seat %s is %v, %+v", c.seat, ok, s) + } + } // The node seat node-engine is the node's engine's (novox/hq ADR 0563 §1): at node scope, delivering nothing, its six // verbs and three events, held by no assignment, and a module's claim of it refused. if engine, ok := SeatNamed(EngineSeat); !ok || engine.Scope != ScopeNode || engine.Delivers != "" || !engine.HeldByTheEngine { @@ -99,9 +113,10 @@ func TestTheSeatsAreAClosedSetAndEachNamesItsDecision(t *testing.T) { // node-container-runtime (ADR 0207); nineteen with node-environment and node-login-shell (ADR 0203, // ADR 0204); seventeen with node-build-agent (ADR 0190). One fewer once the retired // mesh-build-machine row goes, when no registered manifest claims it. - // Forty-eight with mesh-delivery-checker (novox/hq ADR 0547 §10, issue 774); forty-nine with node-engine (novox/hq ADR 0563 §1). - if len(Seats()) != 49 { - t.Errorf("the mesh defines %d seats rather than 49; the set is closed, so a change here is "+ + // Forty-eight with mesh-delivery-checker (novox/hq ADR 0547 §10, issue 774); forty-nine with node-engine (novox/hq ADR 0563 §1); + // fifty-three with the four health seats (novox/hq ADR 0564 clause 1). + if len(Seats()) != 53 { + t.Errorf("the mesh defines %d seats rather than 53; the set is closed, so a change here is "+ "a decision (novox/hq ADR 0110): %s", len(Seats()), seatNames()) } }