diff --git a/modules/mesh-self-check/cmd/mesh-self-check/main.go b/modules/mesh-self-check/cmd/mesh-self-check/main.go index 4225eead..6d3d53f8 100644 --- a/modules/mesh-self-check/cmd/mesh-self-check/main.go +++ b/modules/mesh-self-check/cmd/mesh-self-check/main.go @@ -40,6 +40,7 @@ func (runtimeStore) Keys(state string) ([]string, error) { return stdio.State(st // NewChecker is the self-check over the runtime. func NewChecker() *Checker { return &Checker{Store: runtimeStore{}, Registry: Registry(), Now: time.Now, Logf: logf, Host: os.Getenv("MESH_NODE"), + Enabled: enabledProbes, Ask: func(key string, body any) (json.RawMessage, error) { return stdio.Ask(key, body) }, Publish: stdio.SeatPublish} } @@ -55,3 +56,19 @@ func main() { os.Exit(1) } } + +// enabledProbes is the setting `probes`, read at each run so a change applies at the next; with it unreadable, the +// default list. +func enabledProbes() []string { + raw, err := stdio.Config("probes") + if err != nil { + logf("the setting probes cannot be read (%v); the default list runs", err) + return DefaultProbes + } + var list []string + if json.Unmarshal(raw, &list) != nil { + logf("the setting probes is not a list of probe ids (%s); the default list runs", raw) + return DefaultProbes + } + return list +} 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 fad756df..174ff884 100644 --- a/modules/mesh-self-check/cmd/mesh-self-check/selfcheck.go +++ b/modules/mesh-self-check/cmd/mesh-self-check/selfcheck.go @@ -11,6 +11,7 @@ import ( "context" "encoding/json" "fmt" + "slices" "sort" "strings" "sync" @@ -63,6 +64,17 @@ type Store interface { Keys(state string) ([]string, error) } +// On says whether the setting lists the probe. +func (c *Checker) On(id string) bool { + if c.Enabled == nil { + return true + } + return slices.Contains(c.Enabled(), id) +} + +// DefaultProbes are the probes that run unless the setting says otherwise. +var DefaultProbes = []string{"D13", "D-fill"} + // Probe is one live invariant: its id, what it asserts, the kind it raises, and the run; or why it waits. type Probe struct { ID string @@ -87,7 +99,10 @@ type ( // Checker is the self-check: the registry and what its probes reach. type Checker struct { Store Store - Ask func(key string, body any) (json.RawMessage, error) + // Enabled is the setting `probes`: the ids of the probes that run. Nil runs every probe in the registry; one not + // listed runs nothing and raises nothing (the operator, 2026-10-30). + Enabled func() []string + Ask func(key string, body any) (json.RawMessage, error) // Publish says a seat event on its subject, de-duplicated by id (stdio.SeatPublish). Publish func(subject string, body any, id string) error Now func() time.Time @@ -137,7 +152,7 @@ func (c *Checker) RunOnce(ctx context.Context, why string) Run { results := make([]result, len(c.Registry)) var wg sync.WaitGroup for i, p := range c.Registry { - if p.Run == nil { + if p.Run == nil || !c.On(p.ID) { continue } wg.Add(1) @@ -154,6 +169,12 @@ func (c *Checker) RunOnce(ctx context.Context, why string) Run { ran := map[string]bool{} // the sources whose findings this run judged whole var blind []Observation for i, p := range c.Registry { + if !c.On(p.ID) { + // Off by the setting: it ran nothing and sees nothing, so what it raised before is cleared, and it has no + // verdict in the run. + ran[p.ID] = true + continue + } v := ProbeVerdict{ID: p.ID} r := results[i] switch { 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 e79bb78f..50557340 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 @@ -253,3 +253,43 @@ func TestTheVerbsAndReads(t *testing.T) { t.Errorf("probes answered %v", p) } } + +// Only the probes the setting `probes` lists run: one not listed runs nothing, has no verdict, clears what it raised before, +// and is reported by the verb as off by setting; the default list is D13 and D-fill (the operator, 2026-10-30). +func TestOnlyTheProbesTheSettingListsRun(t *testing.T) { + ran := map[string]int{} + finds := Observation{Token: "stream-missing", Severity: Urgent, Subject: Subject{Scope: ScopeBus, ID: "EVENTS"}, Summary: "gone"} + probe := func(id string) Probe { + return Probe{ID: id, Kind: "stream-wrong", Run: func(context.Context, *Checker) ([]Observation, error) { + ran[id]++ + return []Observation{finds}, nil + }} + } + store := newStore() + c, _ := checker(store, probe("D7"), probe("D13")) + c.Enabled = func() []string { return []string{"D7", "D13"} } + run := c.RunOnce(context.Background(), "test") + if ran["D7"] != 1 || ran["D13"] != 1 || len(run.Probes) != 2 || len(store.findingKeys()) != 1 { + t.Fatalf("both listed: ran %v, %d verdicts, findings %v", ran, len(run.Probes), store.findingKeys()) + } + c.Enabled = func() []string { return []string{"D13"} } + run = c.RunOnce(context.Background(), "test") + if ran["D7"] != 1 || ran["D13"] != 2 || len(run.Probes) != 1 || run.Probes[0].ID != "D13" { + t.Fatalf("D7 unlisted: ran %v, verdicts %+v", ran, run.Probes) + } + // What the unlisted probe raised stays only as long as another listed probe sees the same key (D13 raises the same + // key here); an unlisted probe alone clears its findings. + c.Enabled = func() []string { return nil } + c.RunOnce(context.Background(), "test") + if len(store.findingKeys()) != 0 { + t.Fatalf("with nothing listed, findings are %v", store.findingKeys()) + } + rows, _ := c.Probes()["probes"].([]map[string]any) + if len(rows) != 2 || rows[0]["off"] != "off by setting" || rows[1]["off"] != "off by setting" { + t.Fatalf("the verb says %v", rows) + } + c.Enabled = nil + if !c.On("anything") || !slices.Equal(DefaultProbes, []string{"D13", "D-fill"}) { + t.Fatal("no setting runs every probe, and the default list is D13 and D-fill") + } +} diff --git a/modules/mesh-self-check/cmd/mesh-self-check/tools.go b/modules/mesh-self-check/cmd/mesh-self-check/tools.go index 1c1df753..a4377b73 100644 --- a/modules/mesh-self-check/cmd/mesh-self-check/tools.go +++ b/modules/mesh-self-check/cmd/mesh-self-check/tools.go @@ -115,7 +115,9 @@ func (c *Checker) Probes() map[string]any { if len(p.Raises) > 0 { row["raises"] = p.Raises } - if p.Run == nil { + if !c.On(p.ID) { + row["off"] = "off by setting" + } else if p.Run == nil { row["deferred"] = p.Deferred } out = append(out, row) diff --git a/modules/mesh-self-check/module.json b/modules/mesh-self-check/module.json index a1a892ea..4c06ef33 100644 --- a/modules/mesh-self-check/module.json +++ b/modules/mesh-self-check/module.json @@ -71,5 +71,16 @@ ] } ] + }, + "config": { + "probes": { + "type": "list", + "description": "The ids of the probes that run, each judging one thing of the mesh (mesh-self-check.probes lists them with what each asserts). A probe not listed runs nothing and raises nothing, and what it raised before is cleared. D8 and DA are in the code and off by default.", + "trusted": true, + "default": [ + "D13", + "D-fill" + ] + } } }