150 lines
5.2 KiB
Go
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
|
|
}
|