Open every explanation with what the operator needs to do, and offer the answers
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).
This commit is contained in:
jochen
2026-10-08 13:58:53 +02:00
parent 7df72edd2b
commit 1a4305213d
10 changed files with 430 additions and 207 deletions
+205 -131
View File
@@ -63,71 +63,80 @@ var plainWordings = map[string]func(conditions.Observation) words{
// Machines and their node-engine.
"silent": worded(func(o conditions.Observation) words {
m := machineOr(o, "a machine")
return words{Headline: m + " is not answering",
w := words{Headline: m + " is not answering",
Explanation: fmt.Sprintf("The mesh has not heard from %s for several minutes. A machine asleep or away is "+
"normal; if %s should be on, check that it is powered and online.", m, m),
"normal.", m),
Resolved: m + " answers again"}
if o.Severity == conditions.Urgent {
w.Needs = fmt.Sprintf("check that %s is on and online.", m)
w.Explanation = fmt.Sprintf("The mesh has not heard from %s for half an hour, and the mesh is run from it: "+
"nothing changes anywhere until it is back.", m)
}
return w
}),
"sent-not-reported": worded(func(o conditions.Observation) words {
m := machineOr(o, "a machine")
return words{Headline: m + " has not confirmed a change",
Explanation: fmt.Sprintf("The mesh sent %s new instructions and %s has not said it applied them. It "+
"usually catches up by itself; if it stays, %s may be stuck.", m, m, m),
Explanation: fmt.Sprintf("The mesh sent %s new instructions and %s has not said it applied them yet. "+
"It usually catches up by itself.", m, m),
Resolved: m + " applied the change"}
}),
"tools-silent": worded(func(o conditions.Observation) words {
m := machineOr(o, "a machine")
return words{Headline: "Tools on " + m + " are not answering",
Explanation: fmt.Sprintf("The tool runner on %s has not checked in, so nothing can be done on %s through "+
"the mesh. A machine asleep is normal.", m, m),
"the mesh until it does. A machine asleep is normal.", m, m),
Resolved: "Tools on " + m + " answer again"}
}),
kindAwaitingPush: worded(func(o conditions.Observation) words {
m := machineOr(o, "a machine")
return words{Headline: m + " waits for new instructions",
Explanation: fmt.Sprintf("%s's instructions only work once the next push is made, and none has been made "+
"for a while. Nothing is broken; an agent can push to %s.", conditions.Capital(m), m),
Explanation: fmt.Sprintf("%s's new instructions go out with the next push. Nothing is broken meanwhile.",
conditions.Capital(m)),
Resolved: m + " got its new instructions"}
}),
"declaration-refused": worded(func(o conditions.Observation) words {
if o.Scope == conditions.ScopeMesh {
return words{Headline: "No machine can get new instructions",
Needs: "decide whether to undo the last catalogue change; the details say what fails.",
Explanation: "The mesh cannot work out its private network, so no machine's instructions can be " +
"made. Machines keep running what they have. It needs a look now.",
"made. Machines keep running what they have.",
Resolved: "Machines can get new instructions again"}
}
m := machineOr(o, "a machine")
return words{Headline: m + " cannot get new instructions",
Explanation: fmt.Sprintf("What the mesh would send %s is wrong and would be refused, so nothing new reaches "+
"%s. It keeps running what it has. It needs a fix in the catalogue.", m, m),
Needs: "decide whether to undo the last catalogue change; the details say what fails.",
Explanation: fmt.Sprintf("What the mesh would send %s is wrong and would be refused, so nothing new "+
"reaches it. It keeps running what it has.", m),
Resolved: m + " can get new instructions again"}
}),
"own-address-banned": worded(func(o conditions.Observation) words {
m := machineOr(o, "a machine")
return words{Headline: m + " has banned the mesh",
Needs: fmt.Sprintf("lift the ban on %s, or wait for it to expire.", m),
Explanation: fmt.Sprintf("The intrusion protection on %s banned one of the mesh's own addresses, so the "+
"mesh is locked out of %s. It needs the ban lifted.", m, m),
"mesh is locked out of it.", m),
Resolved: m + " no longer bans the mesh"}
}),
"core-behind": worded(func(o conditions.Observation) words {
m := machineOr(o, "a machine")
return words{Headline: m + " runs an old node-engine",
Explanation: fmt.Sprintf("%s runs an older build of the mesh's own software than the mesh holds, and no "+
"delivery is bringing it up to date. It works, but misses the newest fixes.", conditions.Capital(m)),
Explanation: fmt.Sprintf("%s runs an older build of the mesh's own software than the mesh holds. It "+
"works, and gets the newer one with a later delivery.", conditions.Capital(m)),
Resolved: m + " runs the current node-engine"}
}),
kindMachineUnits: worded(func(o conditions.Observation) words {
m := machineOr(o, "a machine")
return words{Headline: "Failed services on " + m,
Explanation: fmt.Sprintf("Services on %s that no module manages have failed. They are %s's own, so the "+
"mesh does not repair them: mend or remove them there.", m, m),
Resolved: "No failed services on " + m + " any more"}
Needs: fmt.Sprintf("mend or remove them on %s, or silence this if they do not matter.", m),
Explanation: "No module manages them, so the mesh does not repair them.",
Resolved: "No failed services on " + m + " any more", Actions: []conditions.Action{conditions.SilenceAction(o.Key())}}
}),
kindMachineNetwork: worded(func(o conditions.Observation) words {
m := machineOr(o, "a machine")
return words{Headline: m + " has a network problem",
Explanation: fmt.Sprintf("%s's own network check failed: its name lookups, tunnel, bus connection or "+
"route. Other machines may not reach it, or it them.", conditions.Capital(m)),
"route. It often passes again once a network change settles.", conditions.Capital(m)),
Resolved: m + "'s network is fine again"}
}),
kindNetworkRewritten: worded(func(o conditions.Observation) words {
@@ -141,38 +150,38 @@ var plainWordings = map[string]func(conditions.Observation) words{
m := machineOr(o, "a machine")
return words{Headline: m + " cannot be reached",
Explanation: fmt.Sprintf("Other machines cannot reach %s over the mesh's network. A machine away or "+
"asleep is normal; otherwise its tunnel needs a look.", m),
"asleep is normal.", m),
Resolved: m + " can be reached again"}
}),
kindBindingKept: worded(func(o conditions.Observation) words {
m := machineOr(o, "a machine")
return words{Headline: "A module's data source is held on " + m,
Needs: "decide where its data lives; the details name both places.",
Explanation: fmt.Sprintf("A module on %s should switch to another provider of its data, and the mesh "+
"kept the old one because switching would leave the data behind. It needs you to decide.", m),
"kept the old one because switching would leave the data behind.", m),
Resolved: "Resolved: the data source on " + m + " is decided"}
}),
kindBindingMoving: worded(func(o conditions.Observation) words {
m := machineOr(o, "a machine")
return words{Headline: "A module's data source moves on " + m,
Explanation: fmt.Sprintf("At the next push a module on %s switches to another provider, and its data is "+
"still with the old one. Move the data first.", m),
Resolved: "Resolved: the data source move on " + m + " is settled"}
Needs: "move its data to the new provider before the next push.",
Explanation: fmt.Sprintf("At the next push a module on %s switches to another provider of its data.", m),
Resolved: "Resolved: the data source move on " + m + " is settled"}
}),
kindBindingMoved: worded(func(o conditions.Observation) words {
m := machineOr(o, "a machine")
return words{Headline: "A module's data source moved on " + m,
Explanation: fmt.Sprintf("A module on %s switched to another provider of its data, and its data may "+
"still be with the old one. Check it found its data.", m),
Resolved: "Resolved: the data source move on " + m + " is settled"}
Needs: "check that it found its data.",
Explanation: fmt.Sprintf("A module on %s switched to another provider, and its data may still be with the old one.", m),
Resolved: "Resolved: the data source move on " + m + " is settled"}
}),
// Modules and providers.
kindModuleUnhealthy: worded(func(o conditions.Observation) words {
thing := conditions.ThingWords(o)
return words{Headline: conditions.Capital(thing) + " is not working",
Explanation: fmt.Sprintf("%s is not healthy. It clears as soon as it runs again; if it stays, "+
"look at why it fails.", conditions.Capital(thing)),
Resolved: conditions.Capital(thing) + " works again"}
Explanation: fmt.Sprintf("%s is not healthy. It clears as soon as it runs again.", conditions.Capital(thing)),
Resolved: conditions.Capital(thing) + " works again"}
}),
kindProviderFailing: worded(func(o conditions.Observation) words {
thing, consumer := conditions.ThingWords(o), idPart(o, 2)
@@ -181,29 +190,29 @@ var plainWordings = map[string]func(conditions.Observation) words{
}
return words{Headline: conditions.Capital(thing) + " keeps failing " + consumer,
Explanation: fmt.Sprintf("%s provides something %s needs, such as a database, and keeps failing to set "+
"it up. %s may not work until it does.", conditions.Capital(thing), consumer, conditions.Capital(consumer)),
"it up. It tries again by itself.", conditions.Capital(thing), consumer),
Resolved: conditions.Capital(thing) + " serves " + consumer + " again"}
}),
kindProviderSilent: worded(func(o conditions.Observation) words {
thing := conditions.ThingWords(o)
return words{Headline: conditions.Capital(thing) + " went quiet",
Explanation: fmt.Sprintf("%s said it keeps failing a module that depends on it, and then stopped "+
"reporting. Whether it still fails is not known.", conditions.Capital(thing)),
"reporting.", conditions.Capital(thing)),
Resolved: conditions.Capital(thing) + " reports again"}
}),
kindRetireWaiting: worded(func(o conditions.Observation) words {
thing := conditions.ThingWords(o)
return words{Headline: "Retiring data on " + thing + " waits for you",
Explanation: fmt.Sprintf("%s keeps data the mesh no longer asks for, too much to retire on its own. It "+
"needs you to approve or reject retiring it.", conditions.Capital(thing)),
Needs: "approve or reject retiring it; the details list what would go.",
Explanation: fmt.Sprintf("%s keeps data the mesh no longer asks for, too much to retire on its own.",
conditions.Capital(thing)),
Resolved: "Resolved: the retirement on " + thing + " is decided"}
}),
kindRetireRejected: worded(func(o conditions.Observation) words {
thing := conditions.ThingWords(o)
return words{Headline: "Unused data kept on " + thing,
Explanation: fmt.Sprintf("You chose to keep data on %s that the mesh no longer asks for. Nothing to do; "+
"this is a reminder that it is still there.", thing),
Resolved: "Resolved: the unused data on " + thing + " is gone"}
Explanation: fmt.Sprintf("You chose to keep data on %s that the mesh no longer asks for; it is still there.", thing),
Resolved: "Resolved: the unused data on " + thing + " is gone"}
}),
kindCleanupWaiting: worded(func(o conditions.Observation) words {
var where string
@@ -214,9 +223,9 @@ var plainWordings = map[string]func(conditions.Observation) words{
where = what + " on " + machineOr(o, "a machine")
}
return words{Headline: "Retired data waits for cleanup",
Explanation: fmt.Sprintf("Retired data of %s has been kept for over thirty days. It needs you to delete "+
"it or keep it.", where),
Resolved: "Resolved: the retired data is dealt with"}
Needs: "delete it or keep it; nothing is deleted without your word.",
Explanation: fmt.Sprintf("Retired data of %s has been kept for over thirty days.", where),
Resolved: "Resolved: the retired data is dealt with"}
}),
// Data.
@@ -224,22 +233,23 @@ var plainWordings = map[string]func(conditions.Observation) words{
m := machineOr(o, "a machine")
return words{Headline: "Data on " + m + " could not be measured",
Explanation: fmt.Sprintf("The backup holder on %s did not say what it measured, so nothing is known about "+
"the data there: not that it is safe either.", m),
"the data there. The next measurement may settle it.", m),
Resolved: "Data on " + m + " is measured again"}
}),
kindProtectionMissing: worded(func(o conditions.Observation) words {
what, _ := dataWords(o)
return words{Headline: conditions.Capital(what) + " is unprotected",
Needs: "check the storage it is on.",
Explanation: fmt.Sprintf("%s on %s is said to be protected by redundant storage, and it is not on any "+
"the mesh can see. It needs you to check its storage.", conditions.Capital(what), machineOr(o, "its machine")),
"the mesh can see.", conditions.Capital(what), machineOr(o, "its machine")),
Resolved: conditions.Capital(what) + " is protected again"}
}),
kindDataMissing: worded(func(o conditions.Observation) words {
what, _ := dataWords(o)
return words{Headline: conditions.Capital(what) + " is gone",
Explanation: fmt.Sprintf("Where %s is kept on %s, nothing exists any more. If that was not meant, it needs "+
"restoring from a backup.", what, machineOr(o, "its machine")),
Resolved: conditions.Capital(what) + " is back"}
Needs: "restore it from a backup, or silence this if you removed it.",
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())}}
}),
kindDataShrank: worded(func(o conditions.Observation) words {
what, _ := dataWords(o)
@@ -248,30 +258,33 @@ var plainWordings = map[string]func(conditions.Observation) words{
what = "a dataset"
}
return words{Headline: conditions.Capital(what) + " shrank on " + m,
Explanation: "More than half of what it held is gone within a week. If you meant that, silence it; if " +
"not, restore the last good copy from a backup.",
Resolved: "Resolved: the shrinking on " + m + " is explained"}
Needs: "if you meant it, silence this; if not, restore the last good copy from a backup.",
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())}}
}),
kindDataQuiet: worded(func(o conditions.Observation) words {
what, _ := dataWords(o)
return words{Headline: conditions.Capital(what) + " stopped changing",
Needs: "check that what writes it still runs.",
Explanation: fmt.Sprintf("%s on %s is normally written all the time and has not been for longer than "+
"usual: whatever writes it has stopped.", conditions.Capital(what), machineOr(o, "its machine")),
"usual.", conditions.Capital(what), machineOr(o, "its machine")),
Resolved: conditions.Capital(what) + " is written again"}
}),
kindBackupStale: worded(func(o conditions.Observation) words {
what, _ := dataWords(o)
m := machineOr(o, "its machine")
return words{Headline: "No recent backup of " + what,
Needs: fmt.Sprintf("check that backups run on %s.", m),
Explanation: fmt.Sprintf("%s on %s has no good backup recent enough. If the machine failed now, what "+
"changed since would be lost.", conditions.Capital(what), machineOr(o, "its machine")),
"changed since would be lost.", conditions.Capital(what), m),
Resolved: conditions.Capital(what) + " is backed up again"}
}),
kindArrayDegraded: worded(func(o conditions.Observation) words {
m := machineOr(o, "a machine")
return words{Headline: "Storage on " + m + " is degraded",
Explanation: fmt.Sprintf("The redundant storage on %s lost a disk or is rebuilding. The data on it is "+
"less protected until it is whole again.", m),
Resolved: "Storage on " + m + " is whole again"}
Needs: fmt.Sprintf("check the disks of %s.", m),
Explanation: "Its redundant storage lost a disk or is rebuilding; the data on it is less protected until it is whole.",
Resolved: "Storage on " + m + " is whole again"}
}),
kindEmptyReplacement: worded(func(o conditions.Observation) words {
module := idPart(o, 1)
@@ -282,8 +295,9 @@ var plainWordings = map[string]func(conditions.Observation) words{
module = "a module"
}
return words{Headline: conditions.Capital(module) + " runs on empty data",
Needs: "decide where its data lives: move the data, or move it back.",
Explanation: fmt.Sprintf("%s on %s is using an empty copy of its data while the full copy is kept "+
"elsewhere. It needs you: move the data, or move it back.", conditions.Capital(module), machineOr(o, "its machine")),
"elsewhere.", conditions.Capital(module), machineOr(o, "its machine")),
Resolved: conditions.Capital(module) + " has its data again"}
}),
kindDataHeldTwice: worded(func(o conditions.Observation) words {
@@ -292,40 +306,38 @@ var plainWordings = map[string]func(conditions.Observation) words{
consumer = "a module"
}
return words{Headline: conditions.Capital(consumer) + "'s data is on several machines",
Explanation: fmt.Sprintf("%s has live data on more than one machine and uses only one. It needs you to "+
"decide which to keep.", conditions.Capital(consumer)),
Resolved: conditions.Capital(consumer) + "'s data is in one place again"}
Needs: "decide which copy to keep.",
Explanation: fmt.Sprintf("%s has live data on more than one machine and uses only one.", conditions.Capital(consumer)),
Resolved: conditions.Capital(consumer) + "'s data is in one place again"}
}),
// Deliveries and walks.
"stalled": worded(func(o conditions.Observation) words {
if o.Scope == conditions.ScopeDelivery {
return words{Headline: "A delivery is held too long",
Explanation: "A delivery has been held past its time limit. " + conditions.ResolverWords(o),
Resolved: "Resolved: the delivery moves again"}
Explanation: "A delivery has been held past its time limit.", Needs: conditions.ResolverNeeds(o),
Resolved: "Resolved: the delivery moves again"}
}
return words{Headline: "A delivery is stuck halfway",
Explanation: "Its walk across the machines has not moved for longer than usual. Nothing is lost; if it " +
"stays, an agent can look at why.",
Resolved: "Resolved: the delivery moves again"}
Explanation: "Its walk across the machines has not moved for longer than usual. Nothing is lost.",
Resolved: "Resolved: the delivery moves again"}
}),
kindWalkWaiting: worded(func(o conditions.Observation) words {
return words{Headline: "A delivery is waiting to start",
Explanation: "A merged change is built, and mesh-delivery (the module that decides when a delivery goes " +
"out) has not said it may start. Nothing to do yet; an agent can start it by hand.",
"out) has not let it start yet.",
Resolved: "Resolved: the delivery is no longer waiting"}
}),
"merge-not-acted": worded(func(o conditions.Observation) words {
return words{Headline: "A merge was never picked up",
Explanation: "A pull request was merged, and the controller never heard of it, so nothing was built or " +
"sent. An agent can hand it over again.",
"sent. The mesh catches up on missed merges by itself.",
Resolved: "Resolved: the merge was picked up"}
}),
"ask-lost": worded(func(o conditions.Observation) words {
return words{Headline: "A build has no result",
Explanation: "A build was asked for and never answered. What it was for is not delivered until it is " +
"built again.",
Resolved: "Resolved: the build has its result"}
Explanation: "A build was asked for and never answered. It is asked again with the next delivery.",
Resolved: "Resolved: the build has its result"}
}),
kindRolledBack: worded(func(o conditions.Observation) words {
m := machineOr(o, "a machine")
@@ -336,68 +348,73 @@ var plainWordings = map[string]func(conditions.Observation) words{
if o.Scope == conditions.ScopeCore {
return words{Headline: "A core update was put back on " + m,
Explanation: fmt.Sprintf("A new build of the mesh's own %s did not become healthy on %s, so the build "+
"before was put back. It needs a look now.", module, m),
"before was put back and runs.", module, m),
Resolved: "Resolved: the core on " + m + " is healthy"}
}
return words{Headline: conditions.Capital(module) + " update put back on " + m,
Explanation: fmt.Sprintf("The new build of %s did not become healthy on %s, so the mesh put back the one "+
"before. The change is not delivered; it needs a fix in its code.", module, m),
"before, which runs. The change is not delivered until it is fixed.", module, m),
Resolved: "Resolved: " + module + " on " + m + " is settled"}
}),
kindRollbackFailed: worded(func(o conditions.Observation) words {
m := machineOr(o, "a machine")
return words{Headline: "Putting back an update failed on " + m,
Explanation: fmt.Sprintf("A build that failed on %s could not be put back to the one before. What runs "+
"there may be broken; it needs you now.", m),
Resolved: "Resolved: " + m + " runs a good build again"}
Needs: fmt.Sprintf("check what runs on %s; the details say which build.", m),
Explanation: fmt.Sprintf("A build that failed on %s could not be put back to the one before.", m),
Resolved: "Resolved: " + m + " runs a good build again"}
}),
"release-held": worded(func(o conditions.Observation) words {
return words{Headline: "Updates wait for your release",
Explanation: "Some module updates wait for a person to release them. They are not delivered until you do.",
Needs: "release them, or leave them held.",
Explanation: "Some module updates wait for a person to release them, and are not delivered until then.",
Resolved: "Resolved: the held updates are released"}
}),
"facts-stale": worded(func(o conditions.Observation) words {
return words{Headline: "Merge checks use outdated facts",
Explanation: "The facts a merge check judges a change against are out of date, so a check may pass or " +
"fail for the wrong reason.",
Explanation: "The facts a merge check judges a change against are out of date; they are gathered again " +
"by themselves.",
Resolved: "Merge checks use current facts again"}
}),
// The controller and the core.
"controller-deaf": worded(func(o conditions.Observation) words {
return words{Headline: "The controller stopped listening",
Needs: "restart the controller if this stays.",
Explanation: "The controller, which coordinates the mesh, has taken no messages for minutes while some " +
"wait. Changes and repairs do not happen until it recovers.",
Resolved: "The controller listens again"}
}),
"self-check-silent": worded(func(o conditions.Observation) words {
return words{Headline: "The mesh's self-check stopped",
Needs: "restart the controller if this stays.",
Explanation: "The self-check, which looks over the whole mesh every few minutes, has not finished a run. " +
"Problems may go unnoticed until it runs again.",
Resolved: "The self-check runs again"}
}),
"watchdogs-silent": worded(func(o conditions.Observation) words {
return words{Headline: "The mesh's watchdogs stopped",
Explanation: "The watchdogs, which notice when something expected does not happen, have not run. Missed " +
"signals are not being noticed.",
Needs: "restart the controller if this stays.",
Explanation: "The watchdogs, which notice when something expected does not happen, have not run, so " +
"missed signals are not noticed.",
Resolved: "The watchdogs run again"}
}),
"lease-lost": worded(func(o conditions.Observation) words {
return words{Headline: "The controller lost its lease",
Needs: "make sure only one controller runs; the details say which machines claim it.",
Explanation: "The controller holds a lease so that only one copy of it acts at a time. It lost or could " +
"not renew it, so two could act at once, or none. It needs a look now.",
"not renew it, so two could act at once, or none.",
Resolved: "The controller holds its lease again"}
}),
"lease-split": worded(func(o conditions.Observation) words {
return words{Headline: "Two controllers may be acting",
Explanation: "The lease that lets one controller act at a time does not agree with its record, so two " +
"could act at once. It needs a look now.",
Resolved: "One controller acts again"}
Needs: "make sure only one controller runs; the details say which machines claim it.",
Explanation: "The lease that lets one controller act at a time does not agree with its record.",
Resolved: "One controller acts again"}
}),
"stale-writer": worded(func(o conditions.Observation) words {
return words{Headline: "Outdated instructions are being sent",
Explanation: "Something kept sending instructions older than what the machines already hold, and they " +
"were refused. It is harmless if it stops.",
"were refused, which is harmless.",
Resolved: "Resolved: no more outdated instructions"}
}),
"status-slow": worded(func(o conditions.Observation) words {
@@ -408,32 +425,32 @@ var plainWordings = map[string]func(conditions.Observation) words{
kindCoreUnhealthy: worded(func(o conditions.Observation) words {
if o.Scope == conditions.ScopeBus {
return words{Headline: "The bus is not healthy",
Needs: "check the machine the bus runs on; the details say what fails.",
Explanation: "The bus, which carries every message in the mesh, is not healthy. Instructions, builds " +
"and tool calls may fail until it recovers.",
Resolved: "The bus is healthy again"}
}
m := machineOr(o, "its machine")
return words{Headline: "The mesh's own software fails on " + m,
Explanation: fmt.Sprintf("A part of the mesh's own software on %s is not healthy. What it does there may "+
"not happen until it recovers.", m),
Resolved: "The mesh's own software on " + m + " is healthy again"}
Explanation: fmt.Sprintf("A part of the mesh's own software on %s is not healthy; it is restarted by itself.", m),
Resolved: "The mesh's own software on " + m + " is healthy again"}
}),
"call-hung": worded(func(o conditions.Observation) words {
return words{Headline: "A tool call is hanging",
Explanation: "A call to one of the mesh's tools has run far past its time limit. Whatever waits on it is " +
"stuck.",
Resolved: "Resolved: the tool call ended"}
Explanation: "A call to one of the mesh's tools has run far past its time limit; it is ended by itself.",
Resolved: "Resolved: the tool call ended"}
}),
"probe-failed": worded(func(o conditions.Observation) words {
return words{Headline: "A check of the mesh could not run",
Explanation: "One of the mesh's own checks could not run, so what it watches is unknown, which is not " +
"the same as fine.",
Explanation: "One of the mesh's own checks could not run, so what it watches is unknown for now. It runs " +
"again every few minutes.",
Resolved: "The check runs again"}
}),
kindHealersBraked: worded(func(o conditions.Observation) words {
return words{Headline: "All automatic repairs stopped",
Needs: "read the details and decide whether the repairs may run again.",
Explanation: "The healers (the mesh's automatic repairs) acted more often in an hour than allowed, which " +
"looks like a loop, so all of them stopped. Nothing repairs itself until this is looked at.",
"looks like a loop, so all of them stopped.",
Resolved: "Automatic repairs run again"}
}),
"healer-wanted": worded(func(o conditions.Observation) words {
@@ -442,8 +459,8 @@ var plainWordings = map[string]func(conditions.Observation) words{
cause = "something"
}
return words{Headline: conditions.Capital(strings.ReplaceAll(cause, ".", " ")) + " keeps being fixed by hand",
Explanation: "A person keeps repairing this by hand. A healer (an automatic repair) is wanted for it; " +
"nothing is broken now.",
Explanation: "A person keeps repairing this by hand, and an automatic repair is wanted for it. Nothing is " +
"broken now.",
Resolved: "Resolved: no more hand repairs of " + strings.ReplaceAll(cause, ".", " ")}
}),
@@ -451,8 +468,9 @@ var plainWordings = map[string]func(conditions.Observation) words{
"resolver-wrong": worded(func(o conditions.Observation) words {
m := machineOr(o, "a machine")
return words{Headline: "Name lookups fail on " + m,
Explanation: fmt.Sprintf("The mesh's name resolver on %s does not answer machine names correctly. "+
"Machines that rely on it may not find each other.", m),
Needs: fmt.Sprintf("check that %s is on and online.", m),
Explanation: fmt.Sprintf("The mesh's name resolver on %s does not answer machine names correctly, so "+
"machines that rely on it may not find each other.", m),
Resolved: "Name lookups work on " + m + " again"}
}),
"holder-silent": worded(func(o conditions.Observation) words {
@@ -468,50 +486,47 @@ var plainWordings = map[string]func(conditions.Observation) words{
}),
kindBusObjectsUnasserted: worded(func(o conditions.Observation) words {
return words{Headline: "The bus could not be fully set up",
Explanation: "When instructions were sent, the bus's queues could not all be checked, so a module may " +
"not get its messages.",
Explanation: "When instructions were sent, the bus's queues could not all be checked; they are checked " +
"again with the next send.",
Resolved: "The bus is fully set up again"}
}),
kindBusMaintenance: worded(func(o conditions.Observation) words {
return words{Headline: "The bus is being upgraded",
Explanation: "The mesh's message system is being upgraded; some things pause until it is done. Nothing " +
"to do.",
Resolved: "The bus upgrade is done"}
Explanation: "The mesh's message system is being upgraded; some things pause until it is done.",
Resolved: "The bus upgrade is done"}
}),
kindBusUpgradeFailed: worded(func(o conditions.Observation) words {
return words{Headline: "The bus upgrade failed",
Explanation: "The upgrade of the mesh's message system did not end healthy in time. It needs you: it can " +
"be put back to the version before.",
Resolved: "Resolved: the bus is healthy after its upgrade"}
Needs: "decide whether to put the bus back to the version before; the details say how.",
Explanation: "The upgrade of the mesh's message system did not end healthy in time.",
Resolved: "Resolved: the bus is healthy after its upgrade"}
}),
kindConsumerBehind: worded(func(o conditions.Observation) words {
return words{Headline: "Messages pile up for a listener",
Explanation: "One of the mesh's listeners on the bus is far behind, so what it handles happens late. A " +
"healer may restart it.",
"healer restarts it if it stays.",
Resolved: "The listener caught up"}
}),
"consumer-wrong": worded(func(o conditions.Observation) words {
return words{Headline: "A listener on the bus is set up wrong",
Explanation: "One of the mesh's listeners on the bus is missing or not as the controller defines it, so " +
"some messages may reach nobody.",
Explanation: "One of the mesh's listeners on the bus is missing or not as defined; the next send sets " +
"it up again.",
Resolved: "The listener is set up right again"}
}),
"consumer-lost": worded(func(o conditions.Observation) words {
return words{Headline: "A listener on the bus is gone",
Explanation: "One of the mesh's listeners is no longer on the bus, so what it should receive reaches " +
"nobody.",
Resolved: "The listener is back"}
Explanation: "One of the mesh's listeners is no longer on the bus; the next send sets it up again.",
Resolved: "The listener is back"}
}),
"slow-consumer": worded(func(o conditions.Observation) words {
return words{Headline: "A listener on the bus is too slow",
Explanation: "The bus reports a listener too slow to keep up, so messages to it are dropped or late.",
Explanation: "The bus reports a listener too slow to keep up, so messages to it are late.",
Resolved: "The listener keeps up again"}
}),
"max-deliveries": worded(func(o conditions.Observation) words {
return words{Headline: "A message could not be handled",
Explanation: "The bus gave up on a message after trying to hand it over too many times. What it carried " +
"did not happen.",
Resolved: "Resolved: messages are handled again"}
Explanation: "The bus gave up on a message after trying to hand it over too many times.",
Resolved: "Resolved: messages are handled again"}
}),
"refused": worded(func(o conditions.Observation) words {
return words{Headline: "The bus refuses some messages",
@@ -520,21 +535,19 @@ var plainWordings = map[string]func(conditions.Observation) words{
}),
"stream-wrong": worded(func(o conditions.Observation) words {
return words{Headline: "Part of the bus's storage is wrong",
Explanation: "A store the controller keeps on the bus is missing or not as defined, so what it keeps " +
"there may be lost.",
Resolved: "The bus's storage is right again"}
Needs: "check the machine the bus runs on; the details say what is missing.",
Explanation: "A store the controller keeps on the bus is missing or not as defined, so what it keeps there may be lost.",
Resolved: "The bus's storage is right again"}
}),
"archives-unheld": worded(func(o conditions.Observation) words {
return words{Headline: "Kept builds are about to be cleaned up",
Explanation: "Some kept builds are not claimed by any module, so the store's cleanup would delete them. " +
"Nothing to do unless one is still needed.",
Resolved: "Resolved: every kept build is claimed"}
Explanation: "Some kept builds are not claimed by any module, so the store's cleanup would delete them.",
Resolved: "Resolved: every kept build is claimed"}
}),
"archives-missing": worded(func(o conditions.Observation) words {
return words{Headline: "Kept builds are missing",
Explanation: "Some builds the mesh relies on are no longer in the store. A machine that needs one cannot " +
"get it until it is built again.",
Resolved: "Resolved: the kept builds are back"}
Explanation: "Some builds the mesh relies on are no longer in the store; they are built again when needed.",
Resolved: "Resolved: the kept builds are back"}
}),
}
@@ -602,18 +615,68 @@ func deliveryName(modules []string, repository string) string {
return repoName(repository) + " delivery"
}
// waitingAct is what the operator does about a walk waiting for its delivery's word.
func waitingAct(severity conditions.Severity) string {
// walkWaitingWords explains a walk waiting for its delivery's word.
func walkWaitingWords(w waitFacts, in time.Duration, severity conditions.Severity) string {
what := deliveryWhat(w.modules, w.repository)
if severity == conditions.Urgent {
return "Mesh-delivery may be broken. It needs you: have an agent look at mesh-delivery, or start the " +
"delivery by hand."
return fmt.Sprintf("The change to %s is merged and built, and mesh-delivery (the module that decides when a "+
"delivery goes out) has not let it start for %s, so mesh-delivery may be stuck.", what, humanDuration(in))
}
return fmt.Sprintf("Nothing to do yet; it becomes urgent after %s. To start it now, have an agent start it by hand.",
humanDuration(waitUrgentAfter))
return fmt.Sprintf("The change to %s is merged and built, and has waited %s for mesh-delivery (the module that "+
"decides when a delivery goes out) to let it start. It becomes a question for you if it still waits after %s.",
what, humanDuration(in), humanDuration(waitUrgentAfter))
}
// 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 ""
}
// waitingActions are the answers to a walk waiting past its urgent bound: the controller's own verb, since
// the module that should have said go is the one not answering.
func waitingActions(w waitFacts, severity conditions.Severity) []conditions.Action {
if severity != conditions.Urgent {
return nil
}
return []conditions.Action{
{Label: "Start", Verb: "mesh-controller.plans", Arguments: map[string]string{"go": w.id, "why": ""}},
{Label: "Stop", Verb: "mesh-controller.plans", Arguments: map[string]string{"stop": w.id, "why": ""}},
}
}
// moduleNeeds is what the operator can do about a module unhealthy on a machine: log in again where its
// account's groups wait for it (ADR 0252), restart a failed service, or nothing where the mesh restarts it.
func moduleNeeds(node string, rs []inventory.ResourceHealth) (string, []conditions.Action) {
var actions []conditions.Action
for _, r := range rs {
if strings.Contains(r.Reason, "relogin needed") {
return fmt.Sprintf("log out of every session on %s and log in again.", node), nil
}
if r.Kind == link.KindUnit && len(actions) < 2 {
scope := "system"
if strings.Contains(r.Reason, "account's own") {
scope = "user"
}
label := "Restart"
if len(actions) > 0 {
label = "Restart " + unitPlainWords(r.Target)
}
actions = append(actions, conditions.Action{Label: label, Verb: "node-service-manager.restart",
Machine: node, Arguments: map[string]string{"unit": r.Target, "scope": scope}})
}
}
if len(actions) > 0 {
return "restart it; if it fails again, the details say why.", actions
}
return "", nil
}
// stalledWords are the plain words of a delivery held past its bound, as mesh-delivery says it.
func stalledWords(l stalledLine, o conditions.Observation) (string, string, string) {
func stalledWords(l stalledLine, o conditions.Observation) (headline, explanation, resolved, needs string,
actions []conditions.Action) {
repository, _, _ := strings.Cut(l.ID, "@")
name := repoName(repository)
held := l.State
@@ -624,13 +687,24 @@ func stalledWords(l stalledLine, o conditions.Observation) (string, string, stri
if d, err := time.ParseDuration(l.For); err == nil {
long = "for " + humanDuration(d)
}
act := conditions.ResolverWords(o)
if o.Resolver == conditions.ResolverOperator {
act = "It waits for you: release it, or stop it."
switch held {
case "held":
needs = "release it, or stop it."
actions = []conditions.Action{
{Label: "Release", Verb: "mesh-delivery.release", Arguments: map[string]string{"id": l.ID, "why": ""}},
{Label: "Stop", Verb: "mesh-delivery.stop", Arguments: map[string]string{"id": l.ID, "why": ""}},
}
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."
actions = []conditions.Action{{Label: "Stop", Verb: "mesh-delivery.stop", Arguments: map[string]string{"id": l.ID, "why": ""}}}
}
}
return fmt.Sprintf("Delivery of %s %s %s", name, held, long),
fmt.Sprintf("A delivery of %s has been %s %s, past its limit. %s", name, held, long, act),
fmt.Sprintf("Delivery of %s is no longer %s", name, held)
fmt.Sprintf("A delivery of %s has been %s %s, past its limit.", name, held, long),
fmt.Sprintf("Delivery of %s is no longer %s", name, held), needs, actions
}
// causeWords is a hand-act's cause as a person says it.