Say where an answer no notification can give is given, and never offer to silence data loss
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-group group fix/needs-you-from-the-console delivering: 1 of 2 delivered
mesh/delivery superseded: a newer delivery to the same trunk took over its walk
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-group group fix/needs-you-from-the-console delivering: 1 of 2 delivered
mesh/delivery superseded: a newer delivery to the same trunk took over its walk
A "Needs you" with no button left the operator guessing where to act, and a click could silence a condition that says data is gone. The place is named in the glossary's word, the mesh MCP server (hq ADR 0258).
This commit is contained in:
@@ -254,9 +254,10 @@ var plainWordings = map[string]func(conditions.Observation) words{
|
||||
kindDataMissing: worded(func(o conditions.Observation) words {
|
||||
what, _ := dataWords(o)
|
||||
return words{Headline: conditions.Capital(what) + " is gone",
|
||||
Needs: "restore it from a backup, or silence this if you removed it.",
|
||||
// No silence from a click: data loss is never waved away from a notification (ADR 0258).
|
||||
Needs: "restore it from a backup, or silence this " + FromMeshMCPServer,
|
||||
Explanation: fmt.Sprintf("Where %s is kept on %s, nothing exists any more.", what, machineOr(o, "its machine")),
|
||||
Resolved: conditions.Capital(what) + " is back", Actions: []conditions.Action{conditions.SilenceAction(o.Key())}}
|
||||
Resolved: conditions.Capital(what) + " is back"}
|
||||
}),
|
||||
kindDataShrank: worded(func(o conditions.Observation) words {
|
||||
what, _ := dataWords(o)
|
||||
@@ -265,9 +266,10 @@ var plainWordings = map[string]func(conditions.Observation) words{
|
||||
what = "a dataset"
|
||||
}
|
||||
return words{Headline: conditions.Capital(what) + " shrank on " + m,
|
||||
Needs: "if you meant it, silence this; if not, restore the last good copy from a backup.",
|
||||
// No silence from a click: data loss is never waved away from a notification (ADR 0258).
|
||||
Needs: "restore the last good copy from a backup, or silence this " + FromMeshMCPServer,
|
||||
Explanation: "More than half of what it held is gone within a week.",
|
||||
Resolved: "Resolved: the shrinking on " + m + " is explained", Actions: []conditions.Action{conditions.SilenceAction(o.Key())}}
|
||||
Resolved: "Resolved: the shrinking on " + m + " is explained"}
|
||||
}),
|
||||
kindDataQuiet: worded(func(o conditions.Observation) words {
|
||||
what, _ := dataWords(o)
|
||||
@@ -637,7 +639,7 @@ func walkWaitingWords(w waitFacts, in time.Duration, severity conditions.Severit
|
||||
// waitingNeeds is what the operator does about a walk waiting past its urgent bound: nothing before it.
|
||||
func waitingNeeds(severity conditions.Severity) string {
|
||||
if severity == conditions.Urgent {
|
||||
return "start it, or stop it."
|
||||
return "start it, or stop it, " + FromMeshMCPServer
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -656,11 +658,17 @@ func moduleNeeds(node string, rs []inventory.ResourceHealth) string {
|
||||
}
|
||||
}
|
||||
if unit != "" {
|
||||
return fmt.Sprintf("restart its service %s on %s; if it fails again, the details say why.", unit, node)
|
||||
return fmt.Sprintf("restart its service %s on %s %s", unit, node, FromMeshMCPServer)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// FromMeshMCPServer ends what the operator needs when no notification can do it (ADR 0258), naming the mesh MCP
|
||||
// server (the glossary's word; "console" is retired): the answer is not an
|
||||
// acknowledgement, so it is given where the operator is known to be the one asking, until answers are
|
||||
// authorised (to-be 46 phases 5 and 6).
|
||||
const FromMeshMCPServer = "from the mesh MCP server; this notification cannot do it."
|
||||
|
||||
// reloginNeeds is what an account waiting for its groups needs (ADR 0252).
|
||||
func reloginNeeds(node string) string {
|
||||
return fmt.Sprintf("log out of every session on %s and log in again.", node)
|
||||
@@ -684,11 +692,11 @@ func stalledWords(l stalledLine, o conditions.Observation) (headline, explanatio
|
||||
// performs it (ADR 0258).
|
||||
switch held {
|
||||
case "held":
|
||||
needs = "release it, or stop it."
|
||||
needs = "release it, or stop it, " + FromMeshMCPServer
|
||||
case "ready", "checked":
|
||||
needs = "merge its pull request, or close it."
|
||||
default:
|
||||
needs = "stop it, or read the details to see what it waits for."
|
||||
needs = "stop it " + FromMeshMCPServer
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("Delivery of %s %s %s", name, held, long),
|
||||
|
||||
@@ -68,7 +68,7 @@ func TestADeliveryWaitingNeedsNothingUntilItsBoundThenOffersStartAndStop(t *test
|
||||
f.waits[0].since = now.Add(-5 * time.Hour)
|
||||
got = watchWaits(f)
|
||||
plainExample(t, got[0], "openrazer delivery waiting to start",
|
||||
"Needs you: start it, or stop it. The change to openrazer is merged and built, and mesh-delivery (the "+
|
||||
"Needs you: start it, or stop it, from the mesh MCP server; this notification cannot do it. The change to openrazer is merged and built, and mesh-delivery (the "+
|
||||
"module that decides when a delivery goes out) has not let it start for 5 hours, so mesh-delivery may "+
|
||||
"be stuck.")
|
||||
|
||||
@@ -88,7 +88,7 @@ func TestAModuleUnhealthyAsksForARestartInWords(t *testing.T) {
|
||||
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",
|
||||
"Needs you: restart its service openrazer-daemon on g14; if it fails again, the details say why. "+
|
||||
"Needs you: restart its service openrazer-daemon on g14 from the mesh MCP server; this notification cannot do it. "+
|
||||
"openrazer on g14 is not healthy: its service openrazer-daemon stopped with an error. It clears as soon "+
|
||||
"as it runs again.")
|
||||
// An account waiting for a new login (ADR 0252) asks for the login, held to the plain rule.
|
||||
@@ -149,7 +149,7 @@ func TestADeliveryHeldAsksForReleaseOrStopInWords(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",
|
||||
"Needs you: release it, or stop it. A delivery of hq has been held for 36 hours, past its limit.",
|
||||
"Needs you: release it, or stop it, from the mesh MCP server; this notification cannot do it. A delivery of hq has been held for 36 hours, past its limit.",
|
||||
)
|
||||
}
|
||||
|
||||
@@ -204,3 +204,18 @@ func TestAnOperatorsAnswerIsNoHandRepair(t *testing.T) {
|
||||
t.Fatalf("a silence by hand stopped counting: %+v", got)
|
||||
}
|
||||
}
|
||||
|
||||
// **Data loss is never silenced from a notification** (ADR 0258): data missing or shrunk offers no answer,
|
||||
// and says where it is silenced.
|
||||
func TestDataLossOffersNoSilence(t *testing.T) {
|
||||
for _, kind := range []string{kindDataMissing, kindDataShrank} {
|
||||
w := plainWordings[kind](conditions.Observation{Scope: conditions.ScopeMachine, ID: "ace.immich.library",
|
||||
Machine: "ace", Kind: kind, Severity: conditions.Urgent})
|
||||
if len(w.Actions) != 0 || !strings.HasSuffix(w.Needs, FromMeshMCPServer) {
|
||||
t.Errorf("%s: %+v", kind, w)
|
||||
}
|
||||
if why, ok := conditions.PlainWords(w, "ace"); !ok {
|
||||
t.Errorf("%s: %s", kind, why)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user