219 lines
7.5 KiB
Go
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
|
|
}
|