Give every condition a headline, an explanation and a resolved line in plain words
mesh/merge-gate pass: builds build-agent, mesh-controller, route-proxy → ace, g14, novox, shanks; no bus step; every machine composes with the change as it…
mesh/repo-check fail: its merge-check.sh failed: --- FAIL: TestTheForgesOwnAddressFollowsThePortTheNodeGaveIt (0.00s)
mesh/delivery-group group feat/plain-notifications rejected: a member's own check failed
mesh/delivery superseded: a newer head of the same pull request
mesh/merge-gate pass: builds build-agent, mesh-controller, route-proxy → ace, g14, novox, shanks; no bus step; every machine composes with the change as it…
mesh/repo-check fail: its merge-check.sh failed: --- FAIL: TestTheForgesOwnAddressFollowsThePortTheNodeGaveIt (0.00s)
mesh/delivery-group group feat/plain-notifications rejected: a member's own check failed
mesh/delivery superseded: a newer head of the same pull request
The operator could not read the desktop notifications: they carried plan ids, commits, keys and verb syntax. The words the operator reads now travel with the condition, so every channel says them (hq ADR 0253).
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/novox/mesh-controller/internal/conditions"
|
||||
"github.com/novox/mesh-controller/internal/inventory"
|
||||
"github.com/novox/mesh-controller/internal/link"
|
||||
)
|
||||
|
||||
// The conditions the operator read on 2026-10-08, raised again from the same facts (novox/hq ADR 0253):
|
||||
// each keeps its summary for whoever looks closer, and now carries a headline, an explanation and a
|
||||
// resolved line the operator reads at a glance — no plan id, commit, key, verb or clock time in them.
|
||||
|
||||
// plainExample checks one finding's words: plain, within their bounds, and saying what the operator needs.
|
||||
func plainExample(t *testing.T, o conditions.Observation, headline string, says ...string) {
|
||||
t.Helper()
|
||||
machines := append([]string{o.Machine}, o.Also...)
|
||||
w := conditions.Words{Headline: o.Headline, Explanation: o.Explanation, Resolved: o.Resolved}
|
||||
if why, ok := conditions.PlainWords(w, machines...); !ok {
|
||||
t.Errorf("%s: not plain: %s\n %+v", o.Key(), why, w)
|
||||
}
|
||||
if o.Headline != headline {
|
||||
t.Errorf("%s: headline %q, want %q", o.Key(), o.Headline, headline)
|
||||
}
|
||||
for _, s := range says {
|
||||
if !strings.Contains(o.Explanation+" | "+o.Resolved, s) {
|
||||
t.Errorf("%s: the words do not say %q:\n %s | %s", o.Key(), s, o.Explanation, o.Resolved)
|
||||
}
|
||||
}
|
||||
t.Logf("\nBEFORE: %s\nAFTER: %s\n %s\nCLEARS: %s", o.Summary, o.Headline, o.Explanation, o.Resolved)
|
||||
}
|
||||
|
||||
// **A delivery waiting**: the popup read "the walk of novox/mesh-catalog a6385479 has waited 56m0s for
|
||||
// mesh-delivery's word to start: `mesh-delivery.show` for the delivery that landed as a6385479 says why;
|
||||
// `plans go plan-1791454185265004861 --why …` starts it by hand".
|
||||
func TestADeliveryWaitingIsSaidByItsModulesAndWhatToDo(t *testing.T) {
|
||||
now := time.Date(2026, 10, 8, 11, 36, 0, 0, time.UTC)
|
||||
f := calm(now)
|
||||
f.waits = []waitFacts{{id: "plan-1791454185265004861", repository: "novox/mesh-catalog", commit: "a6385479c0ffee",
|
||||
awaits: "mesh-delivery", since: now.Add(-56 * time.Minute), modules: []string{"openrazer"}}}
|
||||
got := watchWaits(f)
|
||||
if len(got) != 1 {
|
||||
t.Fatalf("%+v", got)
|
||||
}
|
||||
plainExample(t, got[0], "openrazer delivery waiting to start", "56 minutes", "mesh-delivery (the module that decides",
|
||||
"Nothing to do yet", "openrazer delivery no longer waiting")
|
||||
if !strings.Contains(got[0].Summary, "plans go plan-1791454185265004861") {
|
||||
t.Errorf("the summary lost the way on for whoever looks closer: %q", got[0].Summary)
|
||||
}
|
||||
|
||||
// Past four hours it is urgent, and says it needs the operator.
|
||||
f.waits[0].since = now.Add(-5 * time.Hour)
|
||||
got = watchWaits(f)
|
||||
plainExample(t, got[0], "openrazer delivery waiting to start", "It needs you")
|
||||
|
||||
// Many modules are counted, not listed in the headline.
|
||||
f.waits[0].modules = []string{"a", "b", "c", "d"}
|
||||
got = watchWaits(f)
|
||||
plainExample(t, got[0], "mesh-catalog delivery waiting to start", "4 modules of mesh-catalog")
|
||||
}
|
||||
|
||||
// **A module unhealthy**: "openrazer on g14 is not healthy: its unit openrazer-daemon.service failed in the
|
||||
// account's own service manager (exit-code)".
|
||||
func TestAModuleUnhealthyNamesItsServiceInWords(t *testing.T) {
|
||||
o := moduleUnhealthyObservation("openrazer", "g14", []inventory.ResourceHealth{{Kind: link.KindUnit,
|
||||
Resource: "openrazer-daemon", Target: "openrazer-daemon.service",
|
||||
Reason: "failed in the account's own service manager (exit-code)", Since: time.Now()}})
|
||||
plainExample(t, o, "openrazer not working on g14", "is not healthy: its service openrazer-daemon stopped with an error",
|
||||
"openrazer works again on g14")
|
||||
}
|
||||
|
||||
// **Failed units on a machine**: "shanks's service manager is degraded: 3 failed unit(s) no module places —
|
||||
// mnt-recalbox.mount, storage-media.mount, greenclip.service (the account's own manager). Each is the
|
||||
// machine's own: mend or remove it there, or have a module place it".
|
||||
func TestFailedUnitsOnAMachineAreNamedWithoutTheirSuffixes(t *testing.T) {
|
||||
o, raise := machineUnitsObservation("shanks", &link.UnitsHealth{State: "degraded", Failed: []link.FailedUnit{
|
||||
{Unit: "mnt-recalbox.mount", Result: "exit-code"}, {Unit: "storage-media.mount", Result: "exit-code"},
|
||||
{Unit: "greenclip.service", Scope: "user", Result: "exit-code"}}})
|
||||
if !raise {
|
||||
t.Fatal("not raised")
|
||||
}
|
||||
plainExample(t, o, "3 failed services on shanks",
|
||||
"mnt-recalbox (a mount), storage-media (a mount) and greenclip failed", "mend or remove them on shanks")
|
||||
}
|
||||
|
||||
// **A healer wanted**: "\"push\" was repaired by hand 35 times in 14 days, the last by g14.node-tools,
|
||||
// through the mesh-controller seat: a healer is wanted for it".
|
||||
func TestAHealerWantedSaysNothingIsBroken(t *testing.T) {
|
||||
now := time.Date(2026, 10, 8, 12, 0, 0, 0, time.UTC)
|
||||
f := calm(now)
|
||||
for i := 0; i < 35; i++ {
|
||||
a := actByHand(now.Add(-time.Duration(i+1)*time.Hour), "push")
|
||||
a.Verb, a.By = "push", "g14.node-tools, through the mesh-controller seat"
|
||||
f.handActs = append(f.handActs, a)
|
||||
}
|
||||
got := watchHandActs(f)
|
||||
if len(got) != 1 {
|
||||
t.Fatalf("%+v", got)
|
||||
}
|
||||
plainExample(t, got[0], "Push keeps being fixed by hand", "35 times in 14 days", "nothing is broken now")
|
||||
}
|
||||
|
||||
// **A delivery held past its bound**, as mesh-delivery says it: "the delivery novox/hq@055550802096 has been
|
||||
// held for 36h2m6s, past its bound of 24h0m0s (it waits for the operator): healer H2 may none: …".
|
||||
func TestADeliveryHeldSaysItWaitsForTheOperator(t *testing.T) {
|
||||
got := stalledObservations([]stalledLine{{ID: "novox/hq@055550802096", State: "held", For: "36h2m6s",
|
||||
Bound: "24h0m0s", H2: "none: the state is the operator's", Says: "it waits for the operator"}})
|
||||
plainExample(t, got[0], "Delivery of hq held for 36 hours", "It waits for you: release it, or stop it.")
|
||||
}
|
||||
|
||||
// **Every kind the controller raises has plain words**, and its words are plain for a subject of every
|
||||
// shape it is raised with.
|
||||
func TestEveryWordingIsPlain(t *testing.T) {
|
||||
subjects := []conditions.Observation{
|
||||
{Scope: conditions.ScopeMachine, ID: "ace", Machine: "ace"},
|
||||
{Scope: conditions.ScopeMachine, ID: "ace.immich.library", Machine: "ace"},
|
||||
{Scope: conditions.ScopeModule, ID: "openrazer.g14", Machine: "g14"},
|
||||
{Scope: conditions.ScopeProvider, ID: "postgres.anchor.app_db", Machine: "anchor"},
|
||||
{Scope: conditions.ScopePlan, ID: "plan-1791454185265004861"},
|
||||
{Scope: conditions.ScopeDelivery, ID: "novox/hq@055550802096"},
|
||||
{Scope: conditions.ScopeCore, ID: "controller.anchor", Machine: "anchor"},
|
||||
{Scope: conditions.ScopeBus, ID: "mesh"},
|
||||
{Scope: conditions.ScopeSeat, ID: "node-backup.ace", Machine: "ace"},
|
||||
{Scope: conditions.ScopeMesh, ID: "hand-acts.disk-load"},
|
||||
{Scope: conditions.ScopeBuild, ID: "openrazer.g14", Machine: "g14"},
|
||||
}
|
||||
for kind, fn := range plainWordings {
|
||||
if !conditions.Worded(kind) {
|
||||
t.Errorf("%s is not registered", kind)
|
||||
}
|
||||
for _, s := range subjects {
|
||||
s.Kind = kind
|
||||
w := fn(s)
|
||||
if why, ok := conditions.PlainWords(w, s.Machine); !ok {
|
||||
t.Errorf("%s about %s %s: %s\n %+v", kind, s.Scope, s.ID, why, w)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user