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.
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
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)
|
||||
}
|
||||
@@ -272,7 +272,9 @@ type Shelf map[string]Manifest
|
||||
const UndeclaredSeat = "which no module declares and the mesh does not define"
|
||||
|
||||
// IsUndeclaredSeat says whether a problem CatalogueProblems gave is a seat no manifest given declares.
|
||||
func IsUndeclaredSeat(problem string) bool { return strings.HasSuffix(problem, UndeclaredSeat) }
|
||||
func IsUndeclaredSeat(problem string) bool {
|
||||
return strings.HasSuffix(problem, UndeclaredSeat) || strings.HasSuffix(problem, UnheldSeat)
|
||||
}
|
||||
|
||||
func CatalogueProblems(shelf Shelf) []string {
|
||||
var problems []string
|
||||
@@ -349,6 +351,9 @@ func CatalogueProblems(shelf Shelf) []string {
|
||||
}
|
||||
}
|
||||
|
||||
// A seat verb a module invokes is one some holder serves (issue 774).
|
||||
problems = append(problems, seatInvokeProblems(shelf, declared)...)
|
||||
|
||||
// Which seats name each path (novox/hq ADR 0287): a module's seat naming one another seat names loses.
|
||||
named, pathProblems := pathSeats(shelfOrder(shelf), shelf, declaredBy)
|
||||
problems = append(problems, pathProblems...)
|
||||
|
||||
@@ -51,12 +51,39 @@ func TestTwoModulesCannotDeclareTheSameSeat(t *testing.T) {
|
||||
}
|
||||
|
||||
// Where ADR 0110's guarantee lands under a derived set: a typo is refused, not resolved to
|
||||
// nothing at runtime.
|
||||
// nothing at runtime. And a seat verb a module invokes is one a holder serves (issue 774, sweep item 1: messenger
|
||||
// invoked a verb nothing answered, and Telegram held every ask silently).
|
||||
func TestUsingASeatNobodyDeclaresIsRefused(t *testing.T) {
|
||||
shop := Manifest{Module: "shop", Uses: []string{"telegram-sendr"}}
|
||||
got := problemsFor(t, Shelf{"telegram": telegram(), "shop": shop})
|
||||
if !strings.Contains(got, "telegram-sendr") || !strings.Contains(got, "no module declares") {
|
||||
t.Fatalf("a misspelled seat was accepted: %s", got)
|
||||
unclaimed := telegram()
|
||||
unclaimed.Claims = nil
|
||||
for _, row := range []struct {
|
||||
name, want string
|
||||
shop Manifest
|
||||
seat Manifest
|
||||
note bool // over some manifests alone it is a note (IsUndeclaredSeat)
|
||||
}{
|
||||
{"a misspelled seat used", `uses the seat "telegram-sendr", ` + UndeclaredSeat,
|
||||
Manifest{Module: "shop", Uses: []string{"telegram-sendr"}}, telegram(), true},
|
||||
{"a declared verb invoked of a held seat", "", Manifest{Module: "shop", Invokes: []string{"seat:telegram-sender.status"}}, telegram(), false},
|
||||
{"a verb the seat does not promise", `the seat telegram-sender serves no verb "resend"`,
|
||||
Manifest{Module: "shop", Invokes: []string{"seat:telegram-sender.resend"}}, telegram(), false},
|
||||
{"a verb of a misspelled seat", `a verb of the seat "telegram-sendr", ` + UndeclaredSeat,
|
||||
Manifest{Module: "shop", Invokes: []string{"seat:telegram-sendr.status"}}, telegram(), true},
|
||||
{"a verb of a declared seat nobody claims", `a verb of the seat "telegram-sender", ` + UnheldSeat,
|
||||
Manifest{Module: "shop", Invokes: []string{"seat:telegram-sender.status"}}, unclaimed, true},
|
||||
{"a verb of a mesh seat nobody claims", `a verb of the seat "mesh-controller", ` + UnheldSeat,
|
||||
Manifest{Module: "shop", Invokes: []string{"seat:mesh-controller.root-free"}}, telegram(), true},
|
||||
{"the engine's verb: every node's engine holds its seat", "",
|
||||
Manifest{Module: "shop", Invokes: []string{"seat:node-engine.root-free"}}, telegram(), false},
|
||||
{"a verb the engine's seat does not promise", `the seat node-engine serves no verb "root-freed"`,
|
||||
Manifest{Module: "shop", Invokes: []string{"seat:node-engine.root-freed"}}, telegram(), false},
|
||||
} {
|
||||
got := CatalogueProblems(Shelf{"telegram": row.seat, "shop": row.shop})
|
||||
joined := strings.Join(got, "; ")
|
||||
if row.want == "" && joined != "" || row.want != "" && (len(got) != 1 || !strings.Contains(joined, row.want) ||
|
||||
IsUndeclaredSeat(got[0]) != row.note) {
|
||||
t.Errorf("%s: %s", row.name, joined)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user