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).
138 lines
6.2 KiB
Go
138 lines
6.2 KiB
Go
package conditions
|
|
|
|
import (
|
|
"encoding/json"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// **The plain rule** (novox/hq ADR 0253): what the operator reads names no identifier, no code and no time
|
|
// of its own; the words conditions are made of pass.
|
|
func TestThePlainRuleRefusesIdentifiersCodeAndTimes(t *testing.T) {
|
|
refused := map[string]string{
|
|
"the walk of novox/mesh-catalog a6385479 waits": "a hash",
|
|
"plan plan-1791454185265004861 is waiting": "a hash",
|
|
"see `mesh-delivery.show` for why": "markup",
|
|
"call mesh-delivery.show for why": "a dotted name",
|
|
"key plan.x.waiting": "a dotted name",
|
|
"waited 56m0s": "a duration in code",
|
|
"since 10:40": "a clock time",
|
|
"since 2026-10-08": "a clock time",
|
|
"raised at noon UTC": "a clock time",
|
|
"start it with plans go --why": "a command's flag",
|
|
"one line\nand another": "a line break",
|
|
"the resolver at 10.77.0.1 is silent": "",
|
|
"": "nothing",
|
|
"**bold**": "markup",
|
|
"storage-media.mount failed": "a dotted name",
|
|
"the store at /var/lib/mesh is full": "",
|
|
"a commit 0123abc landed": "a hash",
|
|
}
|
|
for text, want := range refused {
|
|
why, ok := Plain(text, "g14")
|
|
if ok {
|
|
t.Errorf("%q passed", text)
|
|
continue
|
|
}
|
|
if want != "" && !strings.HasPrefix(why, want) {
|
|
t.Errorf("%q: refused for %q, want %q", text, why, want)
|
|
}
|
|
}
|
|
for _, text := range []string{
|
|
"openrazer delivery waiting to start",
|
|
"The change to openrazer is merged and built, and has waited 56 minutes for mesh-delivery to let it start.",
|
|
"3 failed services on shanks",
|
|
"On shanks, mnt-recalbox (a mount), storage-media (a mount) and greenclip failed.",
|
|
"Name lookups fail on g14",
|
|
"Retired data of postgres on anchor waits for app_db",
|
|
"Push keeps being fixed by hand",
|
|
} {
|
|
if why, ok := Plain(text, "g14", "shanks", "anchor"); !ok {
|
|
t.Errorf("%q refused: %s", text, why)
|
|
}
|
|
}
|
|
}
|
|
|
|
// **A condition carries its plain words**: a producer's own; else its kind's; else its scope's — and the
|
|
// event says them beside the summary, for every channel.
|
|
func TestAConditionCarriesItsPlainWords(t *testing.T) {
|
|
k, _, told, _ := keeper(t)
|
|
before := Unworded
|
|
var unworded []string
|
|
Unworded = func(o Observation, why string) { unworded = append(unworded, why) }
|
|
t.Cleanup(func() { Unworded = before })
|
|
|
|
Wording("test-silent", func(o Observation) Words {
|
|
return Words{Headline: o.Machine + " is not answering", Explanation: "The mesh has not heard from it.",
|
|
Resolved: o.Machine + " answers again"}
|
|
})
|
|
c, err := k.Observe(t.Context(), Observation{Scope: ScopeMachine, ID: "ace", Kind: "test-silent", Machine: "ace",
|
|
Severity: Warning, Source: "S1", Summary: "ace has not been heard from since 12:00 UTC (bound 3m0s)"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if c.Headline != "ace is not answering" || c.Resolved != "ace answers again" || c.Explanation == "" {
|
|
t.Fatalf("the kind's words were not kept: %+v", c)
|
|
}
|
|
if c.Summary != "ace has not been heard from since 12:00 UTC (bound 3m0s)" {
|
|
t.Errorf("the summary was changed: %q", c.Summary)
|
|
}
|
|
said := settled(t, told, 1)
|
|
body, _ := json.Marshal(said[0])
|
|
for _, field := range []string{`"headline":"ace is not answering"`, `"explanation":"The mesh has not heard from it."`,
|
|
`"resolved":"ace answers again"`} {
|
|
if !strings.Contains(string(body), field) {
|
|
t.Errorf("the event does not carry %s: %s", field, body)
|
|
}
|
|
}
|
|
|
|
// A producer's own words win; a resolved line it leaves out is made from its headline.
|
|
c, _ = k.Observe(t.Context(), Observation{Scope: ScopeModule, ID: "openrazer.g14", Kind: "test-silent",
|
|
Machine: "g14", Severity: Warning, Source: "health", Summary: "openrazer on g14 is not healthy",
|
|
Headline: "openrazer not working on g14", Explanation: "Its service stopped with an error."})
|
|
if c.Headline != "openrazer not working on g14" || c.Resolved != "Resolved: openrazer not working on g14" {
|
|
t.Errorf("the producer's words: %+v", c)
|
|
}
|
|
if len(unworded) != 0 {
|
|
t.Errorf("words that were plain were reported: %v", unworded)
|
|
}
|
|
|
|
// Words that are not plain, and a kind with none, are said from the scope — and reported.
|
|
c, _ = k.Observe(t.Context(), Observation{Scope: ScopeModule, ID: "openrazer.g14", Token: "x", Kind: "test-unworded",
|
|
Machine: "g14", Severity: Warning, Source: "health", Summary: "openrazer on g14 is not healthy"})
|
|
if c.Headline != "Openrazer on g14 needs a look" || !strings.Contains(c.Explanation, "Nothing to do yet") {
|
|
t.Errorf("the scope's words: %+v", c)
|
|
}
|
|
c, _ = k.Observe(t.Context(), Observation{Scope: ScopePlan, ID: "plan-1791454185265004861", Kind: "test-silent",
|
|
Severity: Warning, Source: "S16", Summary: "the walk waits", Headline: "plan-1791454185265004861 waits"})
|
|
if strings.Contains(c.Headline, "plan-") || c.Headline != "A walk needs a look" {
|
|
t.Errorf("an id reached the headline: %+v", c)
|
|
}
|
|
if len(unworded) != 2 || !strings.Contains(unworded[0], "no plain words") || !strings.Contains(unworded[1], "not plain") {
|
|
t.Errorf("what was said in borrowed words was not reported: %v", unworded)
|
|
}
|
|
}
|
|
|
|
// **A healer that gave up says so in the explanation**: the words of the kind said "nothing to do"; once the
|
|
// budget is spent, the operator is needed.
|
|
func TestAnEscalatedConditionSaysItNeedsTheOperator(t *testing.T) {
|
|
k, _, _, _ := keeper(t)
|
|
ctx := t.Context()
|
|
o := Observation{Scope: ScopeMachine, ID: "ace", Kind: "silent", Machine: "ace", Severity: Warning, Source: "S1",
|
|
Summary: "ace is silent", Headline: "ace is not answering", Explanation: "Nothing to do yet."}
|
|
if _, err := k.Observe(ctx, o); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
c, _, err := k.Escalate(ctx, o.Key(), Attempt{What: "budget spent", Outcome: "escalated", By: "healer H1"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !strings.HasSuffix(c.Explanation, Escalated) {
|
|
t.Fatalf("escalated: %q", c.Explanation)
|
|
}
|
|
c, _ = k.Observe(ctx, o)
|
|
if strings.Count(c.Explanation, Escalated) != 1 {
|
|
t.Errorf("seen again after escalation: %q", c.Explanation)
|
|
}
|
|
}
|