Files
mesh-catalog/modules/nats/cmd/nats-tools/catalogview.go
T
jschoubben 9e005317dd Merge branch 'feat/47-G-fix11-c3c' into feat/47-G-join11
# Conflicts:
#	modules/nats/cmd/nats-tools/keep.go
#	modules/nats/go.mod
2026-11-12 10:26:43 +01:00

150 lines
5.2 KiB
Go

package main
import (
"cmp"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"maps"
"slices"
catalog "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshcatalog"
"git.novox.be/novox/mesh-sdk/go/catalogue"
"nats-tools/internal/keeper"
)
// The keeper reads a module's asks from the module's entry in the catalog half of each watched branch, not from the
// indexer's event (design 47 G §344, §433, §532).
// Asks is what one catalog graph entry says of a module for the keeper: what its manifest asks of the bus as an accepted
// verdict, the seats it claims, whether it declares an event of its own (the facts keeper.Sends reads), whether it is a
// foundation part (an old `objects` section `sent-objects`, §533), and the caps of its old sections in bytes (§547, §591).
type Asks struct {
Accepted keeper.Accepted
Claims []string
EmitsOwn bool
Foundation bool
OldBytes int64
}
// AsksOf reads one entry. An entry whose manifest is not accepted asks nothing of the bus now, as an accepted verdict of
// no entries does; the digest it carries is of the entries it asks, so an entry changed in nothing the bus module reads
// is not applied again.
func AsksOf(e catalog.CatalogEntry) (Asks, error) {
if e.Module == "" {
return Asks{}, fmt.Errorf("a catalog entry names no module")
}
raw, err := json.Marshal(e.Manifest)
if err != nil {
return Asks{}, fmt.Errorf("the manifest of %s is not readable: %w", e.Module, err)
}
var m catalogue.Manifest
if err := json.Unmarshal(raw, &m); err != nil {
return Asks{}, fmt.Errorf("the manifest of %s is not readable: %w", e.Module, err)
}
state := m.BusState
if e.Verdict != "" && e.Verdict != "accepted" {
state = nil
}
sum := sha256.Sum256(mustJSON(state))
out := Asks{Accepted: keeper.Accepted{Module: e.Module, Digest: hex.EncodeToString(sum[:]), BusState: state},
EmitsOwn: len(m.Emits) > 0 || len(e.Streams) > 0}
for _, c := range m.Claims {
out.Claims = append(out.Claims, c.Name)
}
// The old sections' caps (design 47 G §547, §591): an objects section its max-mib, a log or a stream its max-mib or
// the catalogue's default.
for _, o := range m.Objects {
out.Foundation = out.Foundation || o.Name == keeper.SentLocal
out.OldBytes += int64(o.MaxMiB) * keeper.MiB
}
for _, l := range m.Logs {
out.OldBytes += int64(cmp.Or(l.MaxMiB, catalogue.LogDefaultMiB)) * keeper.MiB
}
for _, st := range m.Streams {
out.OldBytes += int64(cmp.Or(st.MaxMiB, catalogue.StreamDefaultMiB)) * keeper.MiB
}
return out, nil
}
// Branch is one watched branch's newest revision of the catalog half: its name, its revision and its entries by module.
type Branch struct {
Name string
Revision int64
Entries map[string]catalog.CatalogEntry
}
// ViewOf is the catalog half as the keeper plans from it (design 47 G §532): every module some branch's newest revision
// lists, its kept data planned from each such branch, each local name once, from the first of the module's watched
// branches in its registration's order (order: the registration's branches; a branch it does not name comes after, in
// the order given) whose entry asks it; another branch asking that local name with other settings is a drift naming
// the branch, and plans nothing. The module sends when a branch's entry says so; it is a foundation part, and its old
// sections' caps are read, from the first branch.
func ViewOf(branches []Branch, order func(module string) []string) (keeper.CatalogView, error) {
modules := map[string]bool{}
for _, b := range branches {
for m := range b.Entries {
modules[m] = true
}
}
var view keeper.CatalogView
for _, module := range slices.Sorted(maps.Keys(modules)) {
var listing []Branch
var named []string
if order != nil {
named = order(module)
}
for _, name := range named {
for _, b := range branches {
if _, ok := b.Entries[module]; ok && b.Name == name {
listing = append(listing, b)
}
}
}
for _, b := range branches {
if _, ok := b.Entries[module]; ok && !slices.Contains(named, b.Name) {
listing = append(listing, b)
}
}
var cm keeper.CatalogModule
from := map[string]string{}
asked := map[string]string{}
for i, b := range listing {
a, err := AsksOf(b.Entries[module])
if err != nil {
return keeper.CatalogView{}, fmt.Errorf("the branch %s: %w", b.Name, err)
}
if i == 0 {
cm = keeper.CatalogModule{Accepted: keeper.Accepted{Module: module, Digest: b.Entries[module].ManifestDigest, Revision: uint64(max(b.Revision, 0))},
Foundation: a.Foundation, OldBytes: a.OldBytes}
}
cm.EmitsOwn = cm.EmitsOwn || a.EmitsOwn
for _, c := range a.Claims {
if !slices.Contains(cm.Claims, c) {
cm.Claims = append(cm.Claims, c)
}
}
for _, e := range a.Accepted.BusState {
settings := string(mustJSON(e))
if f, ok := from[e.Name]; ok {
if asked[e.Name] != settings {
view.Drifts = append(view.Drifts, keeper.Drift{Module: module, Local: e.Name, Branch: b.Name, From: f})
}
continue
}
from[e.Name], asked[e.Name] = b.Name, settings
cm.BusState = append(cm.BusState, e)
}
}
view.Modules = append(view.Modules, cm)
}
return view, nil
}
func mustJSON(v any) []byte {
b, _ := json.Marshal(v)
return b
}