Served by the bus module's bundle from the keeper's own state asked and the bus's monitoring endpoint (design 25 § 12.3): each entry's template, bus names, settings asked and held and fill against its cap; check names each object missing or differing; orphans what no accepted manifest asks for. nats_history reads one entry's events, in order, from the bus module's own stream BROKER_EVENTS on the tools' credential. The manifest test now holds every tool and reading verb the manifest names to what the bundle serves. Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
282 lines
10 KiB
Go
282 lines
10 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"sort"
|
|
"time"
|
|
|
|
stdio "git.novox.be/novox/mesh-sdk/go"
|
|
"github.com/nats-io/nats.go/jetstream"
|
|
|
|
"nats-tools/internal/keeper"
|
|
)
|
|
|
|
// The seat mesh-broker's reading verbs (design 25 § 12.3), served by this bundle: every entry the keeper made, one
|
|
// entry, what the bus holds against what was asked, and the orphans; each read from the keeper's own state asked and
|
|
// the bus's monitoring endpoint, never from another part's state. And nats_history: every event the bus module said of
|
|
// one entry, in order.
|
|
|
|
// heldEntries reads the keeper's own state asked: every module it holds.
|
|
type heldEntries func() (map[string]keeper.Asked, error)
|
|
|
|
// HeldInState reads asked through the runtime that launched this bundle.
|
|
func HeldInState() (map[string]keeper.Asked, error) {
|
|
s := stdio.State(keeper.StateAsked)
|
|
keys, err := s.Keys()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("the keeper's own %s cannot be read: %w", keeper.StateAsked, err)
|
|
}
|
|
out := map[string]keeper.Asked{}
|
|
for _, key := range keys {
|
|
if key == keeper.KeyStatus {
|
|
continue
|
|
}
|
|
e, err := s.Get(key)
|
|
if err != nil || e == nil {
|
|
continue
|
|
}
|
|
var a keeper.Asked
|
|
if json.Unmarshal(e.Value, &a) == nil {
|
|
out[key] = a
|
|
}
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// busStream is one stream as the monitoring endpoint answers it.
|
|
type busStream struct {
|
|
Config jetstream.StreamConfig `json:"config"`
|
|
State struct {
|
|
Messages uint64 `json:"messages"`
|
|
Bytes uint64 `json:"bytes"`
|
|
} `json:"state"`
|
|
}
|
|
|
|
// streamsHeld reads every stream the bus holds from the monitoring endpoint's jsz, by name.
|
|
func streamsHeld(jsz []byte) (map[string]busStream, error) {
|
|
var doc struct {
|
|
Accounts []struct {
|
|
Streams []struct {
|
|
Name string `json:"name"`
|
|
busStream
|
|
} `json:"stream_detail"`
|
|
} `json:"account_details"`
|
|
}
|
|
if err := json.Unmarshal(jsz, &doc); err != nil {
|
|
return nil, fmt.Errorf("the bus's streams cannot be read: %w", err)
|
|
}
|
|
out := map[string]busStream{}
|
|
for _, a := range doc.Accounts {
|
|
for _, s := range a.Streams {
|
|
out[s.Name] = s.busStream
|
|
}
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// EntryView is one entry as list and show answer it: its template, its binding, each object's settings asked and held,
|
|
// its fill against its cap, and what stands of it.
|
|
type EntryView struct {
|
|
Module string `json:"module"`
|
|
Local string `json:"local"`
|
|
Storage string `json:"storage"`
|
|
Template string `json:"template,omitempty"`
|
|
Binding any `json:"binding"`
|
|
Objects []ObjectView `json:"objects"`
|
|
State keeper.CheckLine `json:"state"`
|
|
}
|
|
|
|
// ObjectView is one bus object of an entry.
|
|
type ObjectView struct {
|
|
Stream string `json:"stream"`
|
|
Asked keeper.Settings `json:"asked"`
|
|
Held *keeper.Settings `json:"held,omitempty"`
|
|
Bytes uint64 `json:"bytes"`
|
|
Count uint64 `json:"count"`
|
|
MaxBytes int64 `json:"max_bytes"`
|
|
Differs string `json:"differs,omitempty"`
|
|
Missing bool `json:"missing,omitempty"`
|
|
}
|
|
|
|
func viewOf(module, local string, m keeper.Made, held map[string]busStream) EntryView {
|
|
v := EntryView{Module: module, Local: local, Storage: m.Plan.Storage, Template: m.Plan.Template, Binding: m.Plan.Binding,
|
|
State: keeper.CheckLine{Module: module, Local: local, Bus: keeper.BusName(module, local), Created: m.Created,
|
|
Refused: m.Refused, Waiting: m.Waiting, Drift: m.Drift, Orphaned: m.Orphaned, Full: m.Full, Fill: m.Fill}}
|
|
for _, o := range m.Plan.Objects {
|
|
ov := ObjectView{Stream: o.Stream(), Asked: o.Settings, MaxBytes: o.Settings.MaxBytes}
|
|
if s, ok := held[o.Stream()]; ok {
|
|
h := keeper.SettingsOf(o.Kind, s.Config)
|
|
ov.Held, ov.Bytes, ov.Count, ov.MaxBytes = &h, s.State.Bytes, s.State.Messages, s.Config.MaxBytes
|
|
ov.Differs = keeper.Differs(o.Settings, h)
|
|
} else {
|
|
ov.Missing = true
|
|
}
|
|
v.Objects = append(v.Objects, ov)
|
|
}
|
|
return v
|
|
}
|
|
|
|
// brokerTools are the four reading verbs and nats_history.
|
|
func brokerTools(read heldEntries, jsz func() ([]byte, error), history func(module, local string, limit int) ([]keeper.Event, error)) []stdio.Tool {
|
|
both := func() (map[string]keeper.Asked, map[string]busStream, error) {
|
|
entries, err := read()
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
doc, err := jsz()
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("the bus's monitoring endpoint cannot be read: %w", err)
|
|
}
|
|
held, err := streamsHeld(doc)
|
|
return entries, held, err
|
|
}
|
|
each := func(entries map[string]keeper.Asked, held map[string]busStream, keep func(keeper.Made) bool) []EntryView {
|
|
var out []EntryView
|
|
for _, module := range sortedNames(entries) {
|
|
for _, local := range sortedNames(entries[module].Made) {
|
|
if m := entries[module].Made[local]; keep(m) {
|
|
out = append(out, viewOf(module, local, m, held))
|
|
}
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
entryArgs := map[string]any{"module": str("the module, e.g. mesh-knowledge"), "local": str("its local name, e.g. records")}
|
|
return []stdio.Tool{
|
|
{Name: "mesh-broker.list",
|
|
Description: "Every module's kept data on the bus that the bus module created (design 25 § 12.3): each entry's template, its bus names, its settings asked and held, and its fill against its cap. Narrowed by a module.",
|
|
Input: map[string]any{"module": str("one module")},
|
|
Run: func(a map[string]any) (any, error) {
|
|
entries, held, err := both()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if m := arg(a, "module"); m != "" {
|
|
entries = map[string]keeper.Asked{m: entries[m]}
|
|
}
|
|
out := each(entries, held, func(keeper.Made) bool { return true })
|
|
return map[string]any{"entries": out, "count": len(out)}, nil
|
|
}},
|
|
{Name: "mesh-broker.show",
|
|
Description: "One entry of a module's kept data on the bus: its template, its bus names, its settings asked and held, its fill against its cap, and whether it is refused, waiting, drifted, orphaned or full.",
|
|
Input: entryArgs,
|
|
Run: func(a map[string]any) (any, error) {
|
|
entries, held, err := both()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
module, local := arg(a, "module"), arg(a, "local")
|
|
m, ok := entries[module].Made[local]
|
|
if !ok {
|
|
return nil, fmt.Errorf("the bus module made no entry %q of %q", local, module)
|
|
}
|
|
return viewOf(module, local, m, held), nil
|
|
}},
|
|
{Name: "mesh-broker.check",
|
|
Description: "What the bus holds against what was asked, entry by entry: each object missing, or differing from its manifest's settings and how. A difference that only grows is applied by the bus module itself; any other is a drift, raised to the operator.",
|
|
Input: map[string]any{"module": str("one module")},
|
|
Run: func(a map[string]any) (any, error) {
|
|
entries, held, err := both()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if m := arg(a, "module"); m != "" {
|
|
entries = map[string]keeper.Asked{m: entries[m]}
|
|
}
|
|
var differing []EntryView
|
|
all := each(entries, held, func(m keeper.Made) bool { return m.Created && !m.Orphaned })
|
|
for _, v := range all {
|
|
for _, o := range v.Objects {
|
|
if o.Missing || o.Differs != "" {
|
|
differing = append(differing, v)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
return map[string]any{"checked": len(all), "differing": differing}, nil
|
|
}},
|
|
{Name: "mesh-broker.orphans",
|
|
Description: "Kept data on the bus that no accepted manifest asks for any more: each goes only by the bus module's drop, on the operator's warrant (nats_drop). Never an entry of a refused or set-aside manifest, nor a foundation part's old section.",
|
|
Run: func(map[string]any) (any, error) {
|
|
entries, held, err := both()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out := each(entries, held, func(m keeper.Made) bool { return m.Orphaned })
|
|
return map[string]any{"orphans": out, "count": len(out)}, nil
|
|
}},
|
|
{Name: "nats_history",
|
|
Description: "Every event the bus module said of one entry of a module's kept data, in order (created, refused, drifted, drift-cleared, full, full-cleared, orphaned, recreated, dropped), from its own stream, kept 30 days.",
|
|
Input: map[string]any{"module": str("the module"), "local": str("its local name"), "limit": num("at most this many, the newest (default 100)")},
|
|
Run: func(a map[string]any) (any, error) {
|
|
module, local := arg(a, "module"), arg(a, "local")
|
|
if module == "" || local == "" {
|
|
return nil, fmt.Errorf("history names one entry: a module and its local name")
|
|
}
|
|
events, err := history(module, local, limitOf(a, 100))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return map[string]any{"module": module, "local": local, "events": events}, nil
|
|
}},
|
|
}
|
|
}
|
|
|
|
// StreamHistory reads the bus module's events of one entry from its own stream, on the tools' own credential.
|
|
func StreamHistory(bus Bus) func(module, local string, limit int) ([]keeper.Event, error) {
|
|
return func(module, local string, limit int) ([]keeper.Event, error) {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
defer cancel()
|
|
js, done, err := bus(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer done()
|
|
cons, err := js.OrderedConsumer(ctx, BrokerEventsStream, jetstream.OrderedConsumerConfig{})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("the bus module's stream %s cannot be read: %w", BrokerEventsStream, err)
|
|
}
|
|
info, err := cons.Info(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var out []keeper.Event
|
|
for left := info.NumPending; left > 0; {
|
|
batch, err := cons.Fetch(int(min(left, 256)), jetstream.FetchMaxWait(5*time.Second))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
n := uint64(0)
|
|
for m := range batch.Messages() {
|
|
n++
|
|
var e keeper.Event
|
|
if json.Unmarshal(m.Data(), &e) == nil && e.Module == module && e.Local == local {
|
|
out = append(out, e)
|
|
}
|
|
}
|
|
if n == 0 {
|
|
break
|
|
}
|
|
left -= min(n, left)
|
|
}
|
|
if len(out) > limit {
|
|
out = out[len(out)-limit:]
|
|
}
|
|
return out, nil
|
|
}
|
|
}
|
|
|
|
// BrokerEventsStream is the bus module's own stream on the bus (catalogue.StreamBusName("broker-events")).
|
|
const BrokerEventsStream = "BROKER_EVENTS"
|
|
|
|
func sortedNames[V any](m map[string]V) []string {
|
|
out := make([]string, 0, len(m))
|
|
for k := range m {
|
|
out = append(out, k)
|
|
}
|
|
sort.Strings(out)
|
|
return out
|
|
}
|