list, show and orphans answer meshbroker.ListAnswer, Held and OrphansAnswer; check answers CheckAnswer with each differing entry's settings asked and held by stream and whether the difference only grows; show takes the contract's local-name. Built against mesh-contracts' seats/meshbroker module in the workspace; the group pin adds its require. Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
302 lines
11 KiB
Go
302 lines
11 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"
|
|
|
|
"git.novox.be/novox/mesh-contracts/go/seats/meshbroker"
|
|
|
|
"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
|
|
}
|
|
|
|
// heldOf is one entry as the seat's list, show and orphans answer it (mesh-contracts meshbroker.Held): its bus name, its
|
|
// template, its options or configuration as the manifest asked them, the settings the bus holds of each of its objects
|
|
// by stream, and its fill summed over them against their caps.
|
|
func heldOf(module, local string, m keeper.Made, held map[string]busStream) meshbroker.Held {
|
|
h := meshbroker.Held{BusName: keeper.BusName(module, local), Module: module, LocalName: local, StorageType: m.Plan.Storage,
|
|
Template: m.Plan.Template, Options: anyOf(m.Plan.Entry.Options), Configuration: anyOf(m.Plan.Entry.Configuration)}
|
|
settings := map[string]any{}
|
|
var fill meshbroker.Fill
|
|
for _, o := range m.Plan.Objects {
|
|
s, ok := held[o.Stream()]
|
|
if !ok {
|
|
continue
|
|
}
|
|
settings[o.Stream()] = keeper.SettingsOf(o.Kind, s.Config)
|
|
fill.Bytes += int64(s.State.Bytes)
|
|
fill.Count += int64(s.State.Messages)
|
|
fill.Cap += s.Config.MaxBytes
|
|
}
|
|
if len(settings) > 0 {
|
|
h.Settings, h.Fill = settings, &fill
|
|
}
|
|
return h
|
|
}
|
|
|
|
// differencesOf are an entry's objects the bus holds otherwise than asked, or not at all, as the seat's check answers
|
|
// them: each object's settings asked and held by stream, and whether the difference only grows (apply may act on it).
|
|
func differencesOf(module, local string, m keeper.Made, held map[string]busStream) (meshbroker.CheckAnswerDifferencesItem, bool) {
|
|
d := meshbroker.CheckAnswerDifferencesItem{Module: module, LocalName: local, Asked: map[string]any{}, Held: map[string]any{}, Grows: true}
|
|
differs := false
|
|
for _, o := range m.Plan.Objects {
|
|
s, ok := held[o.Stream()]
|
|
if !ok {
|
|
d.Asked[o.Stream()], d.Grows, differs = o.Settings, false, true
|
|
continue
|
|
}
|
|
h := keeper.SettingsOf(o.Kind, s.Config)
|
|
if keeper.Differs(o.Settings, h) == "" {
|
|
continue
|
|
}
|
|
d.Asked[o.Stream()], d.Held[o.Stream()], differs = o.Settings, h, true
|
|
d.Grows = d.Grows && keeper.Grows(o.Settings, h)
|
|
}
|
|
return d, differs
|
|
}
|
|
|
|
// anyOf is a manifest's options or configuration as the contract carries them.
|
|
func anyOf(m map[string]json.RawMessage) map[string]any {
|
|
if len(m) == 0 {
|
|
return nil
|
|
}
|
|
out := map[string]any{}
|
|
for k, v := range m {
|
|
var x any
|
|
if json.Unmarshal(v, &x) == nil {
|
|
out[k] = x
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
// 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) []meshbroker.Held {
|
|
out := []meshbroker.Held{}
|
|
for _, module := range sortedNames(entries) {
|
|
for _, local := range sortedNames(entries[module].Made) {
|
|
if m := entries[module].Made[local]; keep(m) {
|
|
out = append(out, heldOf(module, local, m, held))
|
|
}
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
entryArgs := map[string]any{"module": str("the module, e.g. mesh-knowledge"), "local-name": 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 bus name, template and options as asked, the settings the bus holds of each of its objects, 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]}
|
|
}
|
|
return meshbroker.ListAnswer{Entries: each(entries, held, func(keeper.Made) bool { return true })}, nil
|
|
}},
|
|
{Name: "mesh-broker.show",
|
|
Description: "One entry of a module's kept data on the bus: its bus name, template and options as asked, the settings the bus holds of each of its objects, and its fill against its cap. Whether it is refused, waiting, drifted, full or orphaned is what nats_history says of it.",
|
|
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-name")
|
|
m, ok := entries[module].Made[local]
|
|
if !ok {
|
|
return nil, fmt.Errorf("the bus module made no entry %q of %q", local, module)
|
|
}
|
|
return heldOf(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, with both. 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]}
|
|
}
|
|
out := meshbroker.CheckAnswer{Differences: []meshbroker.CheckAnswerDifferencesItem{}}
|
|
for _, module := range sortedNames(entries) {
|
|
for _, local := range sortedNames(entries[module].Made) {
|
|
m := entries[module].Made[local]
|
|
if !m.Created || m.Orphaned {
|
|
continue
|
|
}
|
|
if d, differs := differencesOf(module, local, m, held); differs {
|
|
out.Differences = append(out.Differences, d)
|
|
}
|
|
}
|
|
}
|
|
return out, 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
|
|
}
|
|
return meshbroker.OrphansAnswer{Entries: each(entries, held, func(m keeper.Made) bool { return m.Orphaned })}, 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
|
|
}
|