Files
mesh-catalog/modules/mesh-self-check/cmd/mesh-self-check/tools.go
T

219 lines
7.5 KiB
Go

package main
// The seat's verbs and the module's own tools (novox/hq ADR 0564 §6, §12): `doctor` answers the last verdict at once,
// runs now, or gives one probe's verdict whole; `probes` is the registry; and a read of each state it keeps — the
// findings, the last verdict and the week's verdicts — so a person sees all it holds and a recovery is a call.
import (
"context"
"encoding/json"
"fmt"
"sort"
"strings"
"time"
stdio "git.novox.be/novox/mesh-sdk/go"
)
// Verbs are the seat's verbs, as the manifest claims them.
var Verbs = []string{"doctor", "probes"}
// Tool names: the seat's verbs, and the module's own reads.
const (
DoctorTool = Seat + ".doctor"
ProbesTool = Seat + ".probes"
FindingsTool = "self_check_findings"
VerdictsTool = "self_check_verdicts"
)
func obj(props map[string]any) map[string]any {
return map[string]any{"type": "object", "properties": props}
}
// Tools are what the runtime serves.
func Tools(c *Checker) []stdio.Tool {
return []stdio.Tool{
{Name: DoctorTool,
Description: "The mesh's self-check (novox/hq ADR 0564 §6): the last run's verdict at once, each probe passed, " +
"failed with what it found, could not run (never a pass) or deferred with the facts it lacks; run=true runs " +
"every probe now and answers that run; probe=<id> gives one probe's verdict whole, every finding.",
Input: obj(map[string]any{
"run": map[string]any{"type": "boolean", "description": "run every probe now"},
"probe": map[string]any{"type": "string", "description": "one probe's verdict, whole, e.g. D6"}}),
Run: func(a map[string]any) (any, error) {
return c.Doctor(boolArg(a, "run"), strArg(a, "probe"), "asked by "+stdio.CallerOf(a))
}},
{Name: ProbesTool,
Description: "The self-check's registry: each probe's id, what it asserts, where it came from, the condition kinds " +
"it raises, and for one deferred the facts it lacks on the bus.",
Run: func(map[string]any) (any, error) { return c.Probes(), nil }},
{Name: FindingsTool,
Description: "What the self-check finds wrong now, as the condition keeper reads it (its bucket findings): every " +
"finding's key, kind, severity, the probe that found it and its summary; or one finding whole by key.",
Input: obj(map[string]any{"key": map[string]any{"type": "string", "description": "one finding's key"}}),
Run: func(a map[string]any) (any, error) { return c.Findings(strArg(a, "key")) }},
{Name: VerdictsTool,
Description: "The self-check's runs of the last week (its bucket verdicts, each kept a week) newest first, each " +
"with its counts; or one run whole by its id. The last run is the doctor verb's.",
Input: obj(map[string]any{
"run": map[string]any{"type": "string", "description": "one run's id, e.g. doctor-1793284800-12"},
"limit": map[string]any{"type": "number", "description": "at most this many, newest (default 50)"}}),
Run: func(a map[string]any) (any, error) { return c.Verdicts(strArg(a, "run"), intArg(a, "limit", 50)) }},
}
}
// Doctor is the verb: the last verdict, a run now, or one probe of either.
func (c *Checker) Doctor(run bool, probe, why string) (map[string]any, error) {
var r Run
switch {
case run:
r = c.RunOnce(context.Background(), why)
case c.LastRun() != nil:
r = *c.LastRun()
default:
raw, found, err := c.Store.Get(VerdictState, LastKey)
if err != nil {
return nil, fmt.Errorf("the last verdict cannot be read: %w", err)
}
if !found {
return nil, fmt.Errorf("the self-check has not finished its first run yet: it runs %s after it starts, then "+
"every %s — run=true runs it now", FirstAfter, Every)
}
if err := json.Unmarshal(raw, &r); err != nil {
return nil, fmt.Errorf("the last verdict is not readable: %w", err)
}
}
if probe != "" {
one, ok := oneProbe(r, probe)
if !ok {
return nil, fmt.Errorf("the run %s has no probe %q — %s lists them", r.Run, probe, ProbesTool)
}
r = one
} else {
r = Overview(r)
}
return map[string]any{"run": r, "age": c.Now().Sub(r.At).Round(time.Second).String(),
"note": "a probe that could not run is never a pass; each finding is a condition the keeper raises until a run no " +
"longer sees it, and one a single look can be wrong about is written when two runs in a row see it"}, nil
}
func oneProbe(r Run, id string) (Run, bool) {
for _, p := range r.Probes {
if strings.EqualFold(p.ID, id) {
r.Probes = []ProbeVerdict{p}
return r, true
}
}
return Run{}, false
}
// Probes is the registry as the verb answers it.
func (c *Checker) Probes() map[string]any {
var out []map[string]any
for _, p := range c.Registry {
row := map[string]any{"id": p.ID, "asserts": p.Asserts, "from": p.From, "kind": p.Kind}
if len(p.Raises) > 0 {
row["raises"] = p.Raises
}
if !c.On(p.ID) {
row["off"] = "off by setting"
} else if p.Run == nil {
row["deferred"] = p.Deferred
}
out = append(out, row)
}
return map[string]any{"probes": out, "every": Every.String(), "each within": Within.String()}
}
// Findings are the findings written now, or one whole.
func (c *Checker) Findings(key string) (any, error) {
if key != "" {
raw, found, err := c.Store.Get(FindingsState, key)
if err != nil {
return nil, err
}
if !found {
return nil, fmt.Errorf("no finding is written under %s now", key)
}
return json.RawMessage(raw), nil
}
written, err := c.written()
if err != nil {
return nil, fmt.Errorf("the findings cannot be read: %w", err)
}
rows := []map[string]any{}
for k, f := range written {
rows = append(rows, map[string]any{"key": k, "kind": f.Kind, "severity": f.Severity, "probe": f.Source,
"summary": f.Summary})
}
sort.Slice(rows, func(i, j int) bool { return rows[i]["key"].(string) < rows[j]["key"].(string) })
return map[string]any{"findings": rows, "count": len(rows)}, nil
}
// Verdicts are the week's runs newest first, or one whole.
func (c *Checker) Verdicts(run string, limit int) (any, error) {
if run != "" {
raw, found, err := c.Store.Get(VerdictsState, run)
if err != nil {
return nil, err
}
if !found {
return nil, fmt.Errorf("no run %s is kept: runs are kept a week", run)
}
return json.RawMessage(raw), nil
}
keys, err := c.Store.Keys(VerdictsState)
if err != nil {
return nil, fmt.Errorf("the week's verdicts cannot be read: %w", err)
}
sort.Slice(keys, func(i, j int) bool { return runOrder(keys[i]) > runOrder(keys[j]) })
total := len(keys)
if limit > 0 && len(keys) > limit {
keys = keys[:limit]
}
rows := []map[string]any{}
for _, k := range keys {
raw, found, err := c.Store.Get(VerdictsState, k)
if err != nil || !found {
continue
}
var r Run
if json.Unmarshal(raw, &r) == nil {
rows = append(rows, map[string]any{"run": r.Run, "at": r.At, "why": r.Why, "counts": r.Counts})
}
}
return map[string]any{"verdicts": rows, "kept": total, "kept-for": "a week"}, nil
}
// runOrder orders run ids `doctor-<unix>-<n>` by time, then number.
func runOrder(id string) string {
parts := strings.Split(id, "-")
if len(parts) != 3 {
return id
}
return fmt.Sprintf("%020s-%020s", parts[1], parts[2])
}
func strArg(a map[string]any, k string) string {
s, _ := a[k].(string)
return strings.TrimSpace(s)
}
// boolArg reads a flag as a boolean or as the seat's "true".
func boolArg(a map[string]any, k string) bool {
switch v := a[k].(type) {
case bool:
return v
case string:
return strings.EqualFold(strings.TrimSpace(v), "true")
}
return false
}
func intArg(a map[string]any, k string, otherwise int) int {
if n, ok := a[k].(float64); ok && n > 0 {
return int(n)
}
return otherwise
}