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 pass: its merge-check.sh passed
mesh/delivery delivered
mesh/delivery-group group feat/plain-notifications delivered: every member is delivered
The operator could not tell from a notification whether to act, and was told to have an agent do it. Each condition now says "Nothing for you to do." or "Needs you:" with one thing they can do themselves, and carries the actions the operator channel performs when chosen (hq ADR 0253).
164 lines
7.6 KiB
Go
164 lines
7.6 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":"Nothing for you to do. 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.HasPrefix(c.Explanation, NothingToDo) {
|
|
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)
|
|
}
|
|
}
|
|
|
|
// **An explanation opens with its verdict, and never sends the operator to an agent.**
|
|
func TestEveryExplanationOpensWithItsVerdict(t *testing.T) {
|
|
if got := Verdict("", "It may be asleep."); got != "Nothing for you to do. It may be asleep." {
|
|
t.Errorf("%q", got)
|
|
}
|
|
if got := Verdict("release it, or stop it.", "It is held."); got != "Needs you: release it, or stop it. It is held." {
|
|
t.Errorf("%q", got)
|
|
}
|
|
for _, w := range []Words{
|
|
{Headline: "x waits", Explanation: "Have an agent start it.", Resolved: "x started"},
|
|
{Headline: "x waits", Explanation: "It waits.", Needs: "start it by hand.", Resolved: "x started"},
|
|
{Headline: "x waits", Explanation: "It waits.", Needs: "open a session and start it.", Resolved: "x started"},
|
|
{Headline: "x waits", Explanation: "It waits.", Needs: "start it", Resolved: "x started"},
|
|
{Headline: "x waits", Explanation: "It waits.", Resolved: "x started", Actions: []Action{{Label: "Start"}}},
|
|
} {
|
|
if _, ok := PlainWords(w); ok {
|
|
t.Errorf("passed: %+v", w)
|
|
}
|
|
}
|
|
if why, ok := PlainWords(Words{Headline: "push keeps being fixed by hand", Explanation: "A person repaired " +
|
|
"push by hand 35 times.", Resolved: "Resolved", Needs: "release it, or stop it.",
|
|
Actions: []Action{{Label: "Release", Verb: "mesh-delivery.release"}}}); !ok {
|
|
t.Errorf("refused: %s", why)
|
|
}
|
|
}
|
|
|
|
// **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: "It may be asleep."}
|
|
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 want := "Needs you: " + Escalated + " It may be asleep."; c.Explanation != want || c.Needs != Escalated {
|
|
t.Fatalf("escalated: %q, want %q", c.Explanation, want)
|
|
}
|
|
c, _ = k.Observe(ctx, o)
|
|
if strings.Count(c.Explanation, Escalated) != 1 || strings.Contains(c.Explanation, NothingToDo) {
|
|
t.Errorf("seen again after escalation: %q", c.Explanation)
|
|
}
|
|
}
|