Files
mesh-catalog/modules/mesh-self-check/cmd/mesh-self-check/observation.go
T
jschoubben bbd02004b8
mesh/delivery merged: source.main moved to novox/mesh-catalog@19714898986c
mesh/delivery-group group fix/l4-quick merged: every member is merged
mesh/check pass bbd02004b863 on 3d7830cfdc4f | unit 2729 run, 4570 kept, 16 skipped | integration 2975 run, 4493 kept, 25 skipped | over bound: unit, …
Let the health take-over's compare come out empty (issue 1952)
The self-check's D13 read every reading, good night, indexer module and
manifest one key at a time through the runtime and never finished
within its 30 s; it now keeps those states by watching them and judges
from memory, and reads the setting `probes` once a run. Findings name
their key's last part `ref`, since the runtime refuses a field named
`token`; the self-check, the healers and the keeper are pinned to the
mesh-sdk that reads it and lets a carried holder say its seat's events.
The controller's own bus.nats.upgrade-failed ends with its lease, as
the operator approved on 2026-10-31, so compare lists it apart.

Quick vs thorough: quick, these fixes for L4's take-over now; the
thorough fix (research 032 / ADR 0240's health view extension) is
decided later.

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-10-31 21:35:59 +01:00

143 lines
5.5 KiB
Go

package main
// What a probe observes, and the finding it is written as (novox/hq ADR 0564 §2). A probe says what it sees as an
// Observation, the controller's observation: its token, its evidence in one line, and whether one look can be wrong
// about it. It is written into the bucket `findings` as mesh-sdk's health.Finding, whose key is the controller's key
// for the same condition, so the condition the keeper raises from it and the one the controller raised share one key.
import (
"encoding/json"
"time"
"git.novox.be/novox/mesh-sdk/go/health"
)
// The severities and scopes, as mesh-sdk's health names them.
const (
Urgent = health.Urgent
Warning = health.Warning
ScopeBus = health.ScopeBus
ScopeCore = health.ScopeCore
ScopeProbe = health.ScopeProbe
ScopeMesh = health.ScopeMesh
)
// Subject is what a finding is about.
type Subject = health.Subject
// Observation is one thing a probe sees wrong now.
type Observation struct {
Kind string
Ref string
Subject Subject
Severity health.Severity
Summary string
Said string
Headline string
Explanation string
Resolved string
Needs string
// Source is the probe that saw it, or the self-check itself for a probe that could not run.
Source string
// Confirm says one look can be wrong about it: written only when two runs in a row see it (issue 277).
Confirm bool
}
// Key is where it is kept: `<scope>.<id>.<token>`, the controller's key for the same condition.
func (o Observation) Key() string {
token := o.Ref
if token == "" {
token = o.Kind
}
return health.Key(o.Subject.Scope, o.Subject.ID, token)
}
// Finding is the observation as written by run at, its evidence the one line it said.
func (o Observation) Finding(run string, at time.Time) health.Finding {
f := health.Finding{Key: o.Key(), Kind: o.Kind, Subject: o.Subject, Severity: o.Severity, Summary: o.Summary,
Headline: o.Headline, Explanation: o.Explanation, Resolved: o.Resolved, Needs: o.Needs, Source: o.Source,
Observer: Module, Run: run, At: at.UTC(), Evidence: []health.Evidence{}}
if o.Ref != "" && o.Ref != o.Kind {
f.Ref = o.Ref
}
said := o.Said
if said == "" {
said = o.Summary
}
f.Evidence = append(f.Evidence, health.Evidence{At: at.UTC(), Said: said})
return f
}
// says is what a finding says, its times and the run that wrote it aside: two findings that say the same are one write
// (ADR 0289 §1).
func says(f health.Finding) string {
f.At, f.Run, f.V = time.Time{}, "", 0
ev := make([]health.Evidence, len(f.Evidence))
for i, e := range f.Evidence {
ev[i] = health.Evidence{Said: e.Said}
}
f.Evidence = ev
raw, _ := json.Marshal(f)
return string(raw)
}
// worded gives a finding the operator's words registered for its kind, where it says none of its own.
func worded(f Observation) Observation {
w, ok := words[f.Kind]
if !ok {
return f
}
if f.Headline == "" {
f.Headline = w.Headline
}
if f.Explanation == "" {
f.Explanation = w.Explanation
}
if f.Resolved == "" {
f.Resolved = w.Resolved
}
if f.Needs == "" {
f.Needs = w.Needs
}
return f
}
type plain struct{ Headline, Explanation, Resolved, Needs string }
// words are the controller's words for each kind the self-check raises (mesh-controller plain_words.go), the
// controller's name for itself replaced by the self-check's where the words spoke of it.
var words = map[string]plain{
"consumer-lost": {Headline: "A listener on the bus is gone",
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"},
"consumer-wrong": {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 defined; the next send sets it up again.",
Resolved: "The listener is set up right again"},
"consumer-behind": {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 " +
"restarts it if it stays.",
Resolved: "The listener caught up"},
"stream-wrong": {Headline: "Part of the bus's storage is wrong",
Needs: "check the machine the bus runs on; the details say what is missing.",
Explanation: "A store the mesh 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"},
"bus-reconnects": {Headline: "A client keeps losing the bus",
Explanation: "One of the mesh's clients lost its connection to the bus again and again in the last hour. While " +
"it reconnects, what it says and what it is asked waits.",
Resolved: "Resolved: the client stays connected"},
"core-unhealthy": {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"},
"watchdogs-silent": {Headline: "The mesh's watchdogs stopped",
Needs: "restart the watchman if this stays, from the mesh MCP server.",
Explanation: "The watchdogs, which notice when something expected does not happen, do not answer, so missed " +
"signals are not noticed.",
Resolved: "The watchdogs run again"},
"probe-failed": {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 for now. It runs again " +
"every few minutes.",
Resolved: "The check runs again"},
}