Files
mesh-sdk/go/catalogue/seat_invokes.go
T
jschoubben 7115b7bf64 Refuse a catalogue where a module invokes a seat verb nothing answers (issue 774)
messenger invoked seat:node-engine.root-free while nothing answered it, and Telegram held every ask that offers an
approval, silently. Nothing judged a seat:<seat>.<verb> invoke beyond its spelling: not that the seat exists, that
it promises the verb, nor that anything holds it.

Quick fix: messenger asks the controller's root-free (mesh-catalog bc271896) and the engine announces its seat as
discovery reads it (mesh-host 7ecf7ba). Thorough fix, chosen because an invoke nothing answers fails only at the
call, silently for whoever waits on it: CatalogueProblems refuses every seat verb a module or one of its processes
invokes whose seat no module declares and the mesh does not define, whose seat does not promise the verb, or whose
seat no module claims (a seat every node's engine holds, ADR 0563 §1, is held). Over some manifests alone a seat
whose holder was not given is a note (IsUndeclaredSeat). Over every manifest of mesh-catalog, mesh-control and
mesh-delivery today it refuses nothing.
2026-10-30 18:11:19 +01:00

90 lines
3.2 KiB
Go

package catalogue
import (
"fmt"
"slices"
"strings"
)
// A seat verb a module invokes is one some holder serves (issue 774, sweep item 1). messenger invoked
// `seat:node-engine.root-free`, which nothing in the mesh answered, so every ask that offers an approval was held on
// Telegram, silently. A manifest names what it calls (novox/hq ADR 0152); that a seat by that name exists, promises
// that verb, and has a holder is only known over the whole catalogue, so it is judged here, beside the seats' other
// rules (CatalogueProblems).
// UnheldSeat ends the problem of a `seat:<seat>.<verb>` whose seat no module given claims: over the whole catalogue a
// refusal, over some manifests alone a seat whose holder was not given (IsUndeclaredSeat says both).
const UnheldSeat = "which no module claims, so nothing answers it"
// SeatInvoke splits an invoke `seat:<seat>.<verb>` into its seat and verb; ok is false for anything else (a module's
// tool, `*`).
func SeatInvoke(invoke string) (seat, verb string, ok bool) {
rest, isSeat := strings.CutPrefix(invoke, "seat:")
if !isSeat {
return "", "", false
}
seat, verb, ok = strings.Cut(rest, ".")
return seat, verb, ok && seat != "" && verb != ""
}
// seatInvokeProblems are the `seat:<seat>.<verb>` a module or one of its processes invokes that nothing answers: a seat
// no module declares and the mesh does not define, a verb the seat does not promise, or a seat with no holder (a
// mesh seat no module claims, unless its node's engine holds it, ADR 0563 §1).
func seatInvokeProblems(shelf Shelf, declared map[string]SeatDeclaration) []string {
claimed := map[string]bool{}
for _, m := range shelf {
for _, c := range m.Claims {
claimed[c.Name] = true
if s, ok := SeatNamed(c.Name); ok {
claimed[s.Name] = true
}
}
}
var problems []string
for _, module := range shelfOrder(shelf) {
m := shelf[module]
for _, invoke := range invokesOf(m) {
seat, verb, ok := SeatInvoke(invoke)
if !ok {
continue
}
var verbs []Verb
held := claimed[seat]
if s, isMesh := SeatNamed(seat); isMesh {
verbs, held = s.Serves, held || claimed[s.Name] || s.HeldByTheEngine
} else if d, isModule := declared[seat]; isModule {
verbs = d.Serves
} else {
problems = append(problems, fmt.Sprintf("%s invokes %s, a verb of the seat %q, "+UndeclaredSeat, module, invoke, seat))
continue
}
if !slices.ContainsFunc(verbs, func(v Verb) bool { return v.Name == verb }) {
problems = append(problems, fmt.Sprintf("%s invokes %s, and the seat %s serves no verb %q", module, invoke, seat, verb))
continue
}
if !held {
problems = append(problems, fmt.Sprintf("%s invokes %s, a verb of the seat %q, "+UnheldSeat, module, invoke, seat))
}
}
}
return problems
}
// invokesOf is everything a module says it invokes: its own and each process's bus, once each.
func invokesOf(m Manifest) []string {
out := append([]string(nil), m.Invokes...)
for _, r := range m.Resources {
if fmt.Sprint(r["type"]) != "process" {
continue
}
if _, present := r["bus"]; !present {
continue
}
if b, _, err := processBusOf(r); err == nil {
out = append(out, b.Invokes...)
}
}
slices.Sort(out)
return slices.Compact(out)
}