From 97cc9b1ccfeef0925571be4be74d366f9bdf083a Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 5 Nov 2026 15:09:00 +0100 Subject: [PATCH] =?UTF-8?q?mesh-self-check:=20a=20run=20is=20the=20seat's?= =?UTF-8?q?=20checked=20event=20and=20a=20finding=20the=20seat=20mesh-cond?= =?UTF-8?q?itions'=20value=20(B5d;=20ADR=200634=20=C2=A71,=20=C2=A74;=20is?= =?UTF-8?q?sue=201966)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i --- .../cmd/mesh-self-check/observation.go | 37 ++++++++-------- .../cmd/mesh-self-check/probes_data.go | 5 +-- .../cmd/mesh-self-check/probes_mesh.go | 4 +- .../cmd/mesh-self-check/probes_test.go | 3 +- .../cmd/mesh-self-check/selfcheck.go | 43 ++++++++++--------- .../cmd/mesh-self-check/selfcheck_test.go | 12 ++++-- .../mesh-self-check/whole_integration_test.go | 3 +- modules/mesh-self-check/go.mod | 2 + 8 files changed, 58 insertions(+), 51 deletions(-) diff --git a/modules/mesh-self-check/cmd/mesh-self-check/observation.go b/modules/mesh-self-check/cmd/mesh-self-check/observation.go index 782d6bd01..de29cd4af 100644 --- a/modules/mesh-self-check/cmd/mesh-self-check/observation.go +++ b/modules/mesh-self-check/cmd/mesh-self-check/observation.go @@ -2,35 +2,36 @@ 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 +// about it. It is written into the bucket `findings` as the seat mesh-conditions' 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" + "git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshconditionsclient" + meshconditions "git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshconditions" ) // 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 + Urgent = meshconditionsclient.Urgent + Warning = meshconditionsclient.Warning + ScopeBus = meshconditionsclient.ScopeBus + ScopeCore = meshconditionsclient.ScopeCore + ScopeProbe = meshconditionsclient.ScopeProbe + ScopeMesh = meshconditionsclient.ScopeMesh ) // Subject is what a finding is about. -type Subject = health.Subject +type Subject = meshconditions.Subject // Observation is one thing a probe sees wrong now. type Observation struct { Kind string Ref string Subject Subject - Severity health.Severity + Severity string Summary string Said string Headline string @@ -49,14 +50,14 @@ func (o Observation) Key() string { if token == "" { token = o.Kind } - return health.Key(o.Subject.Scope, o.Subject.ID, token) + return meshconditionsclient.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, +func (o Observation) Finding(run string, at time.Time) meshconditions.FindingsByKeyValue { + f := meshconditions.FindingsByKeyValue{V: meshconditionsclient.FindingVersion, 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{}} + Observer: Module, Run: run, At: at.UTC(), Evidence: []meshconditions.Evidence{}} if o.Ref != "" && o.Ref != o.Kind { f.Ref = o.Ref } @@ -64,17 +65,17 @@ func (o Observation) Finding(run string, at time.Time) health.Finding { if said == "" { said = o.Summary } - f.Evidence = append(f.Evidence, health.Evidence{At: at.UTC(), Said: said}) + f.Evidence = append(f.Evidence, meshconditions.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 { +func says(f meshconditions.FindingsByKeyValue) string { f.At, f.Run, f.V = time.Time{}, "", 0 - ev := make([]health.Evidence, len(f.Evidence)) + ev := make([]meshconditions.Evidence, len(f.Evidence)) for i, e := range f.Evidence { - ev[i] = health.Evidence{Said: e.Said} + ev[i] = meshconditions.Evidence{Said: e.Said} } f.Evidence = ev raw, _ := json.Marshal(f) diff --git a/modules/mesh-self-check/cmd/mesh-self-check/probes_data.go b/modules/mesh-self-check/cmd/mesh-self-check/probes_data.go index 594299aa4..a73b0edda 100644 --- a/modules/mesh-self-check/cmd/mesh-self-check/probes_data.go +++ b/modules/mesh-self-check/cmd/mesh-self-check/probes_data.go @@ -23,7 +23,6 @@ import ( "git.novox.be/novox/mesh-catalog/modules/restic/backupgood" "git.novox.be/novox/mesh-sdk/go/catalogue" "git.novox.be/novox/mesh-sdk/go/delivery" - "git.novox.be/novox/mesh-sdk/go/health" ) // The states D13 reads (declared under `reads`). @@ -201,7 +200,7 @@ func probeData(ctx context.Context, c *Checker) ([]Observation, error) { return dataFindings(f), nil } -func severityOf(class string) health.Severity { +func severityOf(class string) string { if class == catalogue.ClassIrreplaceable { return Urgent } @@ -264,7 +263,7 @@ func firstSeen(r diskreadings.Reading, now time.Time) time.Time { return now } -func obs(kind string, id, machine string, sev health.Severity, summary, said string) Observation { +func obs(kind string, id, machine string, sev string, summary, said string) Observation { return Observation{Kind: kind, Ref: kind, Severity: sev, Summary: summary, Said: said, Subject: Subject{Scope: ScopeMachine, ID: id, Machine: machine}} } diff --git a/modules/mesh-self-check/cmd/mesh-self-check/probes_mesh.go b/modules/mesh-self-check/cmd/mesh-self-check/probes_mesh.go index 0970c7517..878e053d2 100644 --- a/modules/mesh-self-check/cmd/mesh-self-check/probes_mesh.go +++ b/modules/mesh-self-check/cmd/mesh-self-check/probes_mesh.go @@ -17,8 +17,8 @@ import ( "strings" "sync" + "git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshconditionsclient" "git.novox.be/novox/mesh-sdk/go/delivery" - "git.novox.be/novox/mesh-sdk/go/health" ) // The states the machine probes read (declared under `reads`), and the verb D8 asks each node. @@ -28,7 +28,7 @@ const ( PresenceRead = delivery.WatchmanModule + ".presence" BannedSeat = "node-intrusion-prevention" BannedVerb = "seat:" + BannedSeat + ".banned" - ScopeMachine = health.ScopeMachine + ScopeMachine = meshconditionsclient.ScopeMachine ) // placements is every node's placement, by node, from the state's keys in either form: a node key and its module keys, diff --git a/modules/mesh-self-check/cmd/mesh-self-check/probes_test.go b/modules/mesh-self-check/cmd/mesh-self-check/probes_test.go index 0e665dde9..2165a844a 100644 --- a/modules/mesh-self-check/cmd/mesh-self-check/probes_test.go +++ b/modules/mesh-self-check/cmd/mesh-self-check/probes_test.go @@ -14,7 +14,6 @@ import ( "git.novox.be/novox/mesh-sdk/go/buscompose" "git.novox.be/novox/mesh-sdk/go/catalogue" "git.novox.be/novox/mesh-sdk/go/delivery" - "git.novox.be/novox/mesh-sdk/go/health" ) // bus is the bus module's answers as a probe asks them: every mesh stream and consumer as defined, unless changed. @@ -302,7 +301,7 @@ func filling(stream string) *memStore { s := newStore() o := Observation{Kind: "bucket-or-log-filling", Ref: "filling", Severity: Warning, Subject: Subject{Scope: ScopeBus, ID: stream}, Summary: "filling", Source: "D-fill"} - raw, _ := health.EncodeFinding(o.Finding("run", time.Now())) + raw, _ := json.Marshal(o.Finding("run", time.Now())) _ = s.Put(FindingsState, o.Key(), json.RawMessage(raw)) return s } diff --git a/modules/mesh-self-check/cmd/mesh-self-check/selfcheck.go b/modules/mesh-self-check/cmd/mesh-self-check/selfcheck.go index 12e77dfb0..fad166b4c 100644 --- a/modules/mesh-self-check/cmd/mesh-self-check/selfcheck.go +++ b/modules/mesh-self-check/cmd/mesh-self-check/selfcheck.go @@ -17,7 +17,8 @@ import ( "sync" "time" - "git.novox.be/novox/mesh-sdk/go/health" + meshconditions "git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshconditions" + meshselfcheck "git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshselfcheck" ) // Module and Seat are this module and the seat it holds once for the mesh. @@ -28,11 +29,11 @@ const ( // The states it keeps, and the event that ends every run. const ( - FindingsState = health.FindingsState + FindingsState = meshconditions.StateFindings VerdictState = "verdict" VerdictsState = "verdicts" LastKey = "last" - CheckedEvent = health.EventChecked + CheckedEvent = meshselfcheck.EventChecked // CheckedSubject is where the seat says it: a seat's events are on mesh.seat..event., which its // consumers hear (catalogue.SeatSays). CheckedSubject = "mesh.seat." + Seat + ".event." + CheckedEvent @@ -45,12 +46,12 @@ var ( Within = 30 * time.Second ) -// The verdicts a probe can have, as mesh-sdk's health names them. +// The verdicts a probe can have, as the seat's contract names them. const ( - VerdictPass = health.VerdictPassed - VerdictFail = health.VerdictFailed - VerdictFailedToRun = health.VerdictFailedToRun - VerdictDeferred = health.VerdictDeferred + VerdictPass = meshselfcheck.ProbeVerdictVerdictPassed + VerdictFail = meshselfcheck.ProbeVerdictVerdictFailed + VerdictFailedToRun = meshselfcheck.ProbeVerdictVerdictFailedToRun + VerdictDeferred = meshselfcheck.ProbeVerdictVerdictDeferred ) // sourceSelf is what writes a probe's failure to run: the self-check itself. @@ -96,12 +97,12 @@ type Probe struct { Run func(ctx context.Context, c *Checker) ([]Observation, error) } -// ProbeVerdict, Counts and Run are a run as mesh-sdk's health holds it: the body of `checked`, the controller's +// ProbeVerdict, Counts and Run are a run as the seat mesh-self-check's contract holds it: the body of `checked`, the controller's // doctor-heartbeat body unchanged (ADR 0564 §6), Controller naming the machine that ran it. type ( - ProbeVerdict = health.ProbeVerdict - Counts = health.CheckedCounts - Run = health.Checked + ProbeVerdict = meshselfcheck.ProbeVerdict + Counts = meshselfcheck.CheckedEventCounts + Run = meshselfcheck.CheckedEvent ) // Checker is the self-check: the registry and what its probes reach. @@ -157,8 +158,8 @@ func (c *Checker) RunOnce(ctx context.Context, why string) Run { n := c.n c.mu.Unlock() started := c.Now() - run := Run{Run: fmt.Sprintf("doctor-%d-%d", started.Unix(), n), Started: started.UTC(), - IntervalSeconds: int(Every / time.Second), Controller: c.Host, Why: why} + run := Run{Run: fmt.Sprintf("doctor-%d-%d", started.Unix(), n), Started: &started, Counts: &Counts{}, + IntervalSeconds: int64(Every / time.Second), Controller: c.Host, Why: why} on := c.listed() results := make([]result, len(c.Registry)) @@ -253,7 +254,7 @@ func (c *Checker) RunOnce(ctx context.Context, why string) Run { run.At = c.Now().UTC() run.Took = run.At.Sub(started).Round(time.Millisecond).String() c.mu.Lock() - run.Unsaid = c.unsaid + run.Unsaid = int64(c.unsaid) c.mu.Unlock() c.keep(run) return run @@ -290,7 +291,7 @@ func (c *Checker) seenBefore(key string) bool { } // writtenFindings are the findings in the bucket, by key. -type writtenFindings map[string]health.Finding +type writtenFindings map[string]meshconditions.FindingsByKeyValue func (w writtenFindings) has(key string) bool { _, ok := w[key]; return ok } @@ -309,8 +310,8 @@ func (c *Checker) written() (writtenFindings, error) { if !found { continue } - f, err := health.DecodeFinding(raw) - if err != nil { + var f meshconditions.FindingsByKeyValue + if err := json.Unmarshal(raw, &f); err != nil { return out, fmt.Errorf("the finding %s is not readable: %w", k, err) } out[k] = f @@ -333,7 +334,7 @@ func (c *Checker) write(written writtenFindings, want map[string]Observation, ra if was, ok := written[k]; ok && says(was) == says(f) { continue } - raw, err := health.EncodeFinding(f) + raw, err := json.Marshal(f) if err == nil { err = c.Store.Put(FindingsState, k, json.RawMessage(raw)) } @@ -388,7 +389,7 @@ func Overview(run Run) Run { for i, p := range run.Probes { total := len(p.Found) + len(p.Unconfirmed) if total > overviewFindings { - p.FoundCount = len(p.Found) + p.FoundCount = int64(len(p.Found)) if len(p.Found) > overviewFindings { p.Found = p.Found[:overviewFindings:overviewFindings] } @@ -396,7 +397,7 @@ func Overview(run Run) Run { p.Unconfirmed = p.Unconfirmed[:room:room] } p.More = fmt.Sprintf("%d found and %d unconfirmed, %d of them shown — `%s.doctor probe=%s` gives them all", - p.FoundCount, total-p.FoundCount, len(p.Found)+len(p.Unconfirmed), Seat, p.ID) + p.FoundCount, int64(total)-p.FoundCount, len(p.Found)+len(p.Unconfirmed), Seat, p.ID) } probes[i] = p } diff --git a/modules/mesh-self-check/cmd/mesh-self-check/selfcheck_test.go b/modules/mesh-self-check/cmd/mesh-self-check/selfcheck_test.go index ec294bb0e..ce46ad85e 100644 --- a/modules/mesh-self-check/cmd/mesh-self-check/selfcheck_test.go +++ b/modules/mesh-self-check/cmd/mesh-self-check/selfcheck_test.go @@ -5,14 +5,13 @@ import ( "encoding/json" "errors" "fmt" + meshconditions "git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshconditions" "slices" "sort" "strings" "sync" "testing" "time" - - "git.novox.be/novox/mesh-sdk/go/health" ) // memStore is the bus's buckets in memory, counting the writes and the reads one key at a time, and handing every @@ -191,7 +190,7 @@ func TestARunWritesFindingsAsTheyChange(t *testing.T) { found, fail = []Observation{missing}, nil c.RunOnce(context.Background(), "the test") raw, _, _ = store.Get(FindingsState, "bus.EVENTS.stream-missing") - f, err := health.DecodeFinding(raw) + f, err := decodeFinding(raw) if err != nil || f.Kind != "stream-wrong" || f.Source != "P1" || f.Observer != Module || f.Headline == "" || f.Explanation == "" || f.Ref != "stream-missing" || len(f.Evidence) != 1 || f.Run == "" { t.Errorf("the written finding is %+v", f) @@ -319,3 +318,10 @@ func TestOnlyTheProbesTheSettingListsRun(t *testing.T) { t.Fatal("no setting runs every probe, and the default list is D13 and D-fill") } } + +// decodeFinding reads a finding as the seat mesh-conditions lays it out. +func decodeFinding(raw []byte) (meshconditions.FindingsByKeyValue, error) { + var f meshconditions.FindingsByKeyValue + err := json.Unmarshal(raw, &f) + return f, err +} diff --git a/modules/mesh-self-check/cmd/mesh-self-check/whole_integration_test.go b/modules/mesh-self-check/cmd/mesh-self-check/whole_integration_test.go index a8d74293a..d932f93fc 100644 --- a/modules/mesh-self-check/cmd/mesh-self-check/whole_integration_test.go +++ b/modules/mesh-self-check/cmd/mesh-self-check/whole_integration_test.go @@ -26,7 +26,6 @@ import ( "github.com/nats-io/nats.go" "github.com/nats-io/nats.go/jetstream" - "git.novox.be/novox/mesh-sdk/go/health" "git.novox.be/novox/mesh-sdk/go/testbus" ) @@ -295,7 +294,7 @@ func TestTheSelfCheckWholeRunsOnTheBus(t *testing.T) { if err != nil { t.Fatalf("the missing stream is not a finding on the bus: %v", err) } - f, err := health.DecodeFinding(e.Value()) + f, err := decodeFinding(e.Value()) if err != nil || f.Kind != "stream-wrong" || f.Severity != Urgent || f.Source != "D7" { t.Errorf("the finding is %s", e.Value()) } diff --git a/modules/mesh-self-check/go.mod b/modules/mesh-self-check/go.mod index f1f3b359a..999679c50 100644 --- a/modules/mesh-self-check/go.mod +++ b/modules/mesh-self-check/go.mod @@ -31,3 +31,5 @@ replace git.novox.be/novox/mesh-catalog/modules/disk-load/diskreadings => ../dis require git.novox.be/novox/mesh-catalog/modules/restic/backupgood v0.0.0-00010101000000-000000000000 replace git.novox.be/novox/mesh-catalog/modules/restic/backupgood => ../restic/backupgood +require git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshconditions v0.0.0-00010101000000-000000000000 +require git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshselfcheck v0.0.0-00010101000000-000000000000