Files
mesh-sdk/go/catalogue/holdings_test.go
T
jschoubben 5e02dccb98 SDK-8, SDK-12: the seat list is mesh-contracts' generated one; mesh-sdk keeps the runtime (ADR 0608 §5, ADR 0613 §2, ADR 0620 §3, design 26)
catalogue's compiled set is read from git.novox.be/novox/mesh-contracts/go/seats; the hand-written seat literal and its
verb builders go. The bar's block keeps its checks as code beside the data. A case of the seat set's test holds the
generated set equal, entry for entry, to the list compiled at 5a38a12 (testdata), but for the whole seats' additions
and the vault's two verbs; the three new registry seats are the only new entries. A holder of mesh-broker now serves its
eight verbs.

SDK-12: the vault's seat no longer promises accept and move-asks, which no holder served and nothing called; retire is
optional (design 32). The intake and move rows join one table; the move's own test goes with the two verbs.

Requires mesh-contracts (no version on the forge yet: a go.work, uncommitted, uses the sibling worktree until the
group pins it).

Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-04 11:57:50 +01:00

93 lines
4.4 KiB
Go

package catalogue
import (
"strings"
"testing"
"git.novox.be/novox/mesh-contracts/go/seats/meshbroker"
)
// **A seat's holder on record settles who holds it, and lets the next holder stand beside the
// current one** (novox/hq ADR 0131, design 28 task 5.3). Until the record existed, two assignments
// whose modules both claimed a seat were refused outright — which left no way to hand a seat over
// without a moment where nobody held it, and the control plane finds its own bus through one of
// these seats. That moment was the outage of 2026-09-27.
func busSeatDelivering(t *testing.T, delivers string) {
t.Helper()
was := Seats()
t.Cleanup(func() { UseSeats(was) })
UseSeats([]Seat{{Name: "mesh-broker", Scope: ScopeMesh, Delivers: delivers, Decision: "test"}})
}
func newBroker() Manifest {
return Manifest{Module: "new-broker", Provides: []Offer{{Name: "mesh-bus", Scope: ScopeMesh}},
Claims: []Claim{{Name: "mesh-broker", Scope: ScopeMesh, Serves: meshbroker.Verbs()}}}
}
// CanHold is the one judgement registration and the handover share, against the store's row.
func TestCanHoldJudgesClaimScopeAndWhatTheSeatDelivers(t *testing.T) {
busSeatDelivering(t, "mesh-bus")
seat, _ := SeatNamed("mesh-broker")
if err := CanHold(newBroker(), seat); err != nil {
t.Fatalf("a module that claims the seat and provides what it delivers was refused: %v", err)
}
noClaim := Manifest{Module: "quiet", Provides: []Offer{{Name: "mesh-bus", Scope: ScopeMesh}}}
if err := CanHold(noClaim, seat); err == nil || !strings.Contains(err.Error(), "does not claim") {
t.Fatalf("a module that never claimed the seat was allowed to hold it: %v", err)
}
wrongScope := newBroker()
wrongScope.Claims[0].Scope = ScopeNode
if err := CanHold(wrongScope, seat); err == nil || !strings.Contains(err.Error(), "scope") {
t.Fatalf("a claim at the wrong scope was allowed: %v", err)
}
cannotAnswer := Manifest{Module: "amqp-only", Provides: []Offer{{Name: "amqp", Scope: ScopeMesh}},
Claims: []Claim{{Name: "mesh-broker", Scope: ScopeMesh, Serves: meshbroker.Verbs()}}}
if err := CanHold(cannotAnswer, seat); err == nil || !strings.Contains(err.Error(), `does not provide "mesh-bus"`) {
t.Fatalf("a holder that cannot answer for the seat was allowed: %v", err)
}
// A holder's own tools need not be the seat's verbs: the claim may name what it serves for the
// role (ADR 0160), and then only those count — and only the seat's verbs may be named.
promising := seat
promising.Serves = []Verb{{Name: "databases"}, {Name: "query"}}
engine := newBroker()
engine.Tools = []string{"engine_list_databases", "engine_query"}
if err := CanHold(engine, promising); err == nil || !strings.Contains(err.Error(), "does not serve databases, query") {
t.Fatalf("a holder whose tools are not the seat's verbs was allowed without saying what it serves: %v", err)
}
engine.Claims[0].Serves = []string{"databases", "query"}
if err := CanHold(engine, promising); err != nil {
t.Fatalf("a claim naming the seat's verbs was refused: %v", err)
}
engine.Claims[0].Serves = []string{"databases"}
if err := CanHold(engine, promising); err == nil || !strings.Contains(err.Error(), "does not serve query") {
t.Fatalf("a claim naming half the verbs was allowed: %v", err)
}
engine.Claims[0].Serves = []string{"databases", "query", "engine_query"}
if err := CanHold(engine, promising); err == nil || !strings.Contains(err.Error(), "does not promise") {
t.Fatalf("a claim naming a verb the seat never promised was allowed: %v", err)
}
// And the judgement follows the store's row, not a compiled copy.
busSeatDelivering(t, "amqp")
seat, _ = SeatNamed("mesh-broker")
if err := CanHold(cannotAnswer, seat); err != nil {
t.Fatalf("with the row saying amqp, an amqp provider was refused: %v", err)
}
}
// The store's seat rows have no protocol columns yet; loading them must not drop the protocol the
// bus is derived from, or no role's work queue is ever raised (found live, 2026-09-28).
func TestAStoreRowWithoutAProtocolKeepsTheCompiledOne(t *testing.T) {
was := Seats()
t.Cleanup(func() { UseSeats(was) })
UseSeats([]Seat{{Name: "node-build-go", Scope: ScopeNode, Decision: "row"}})
got, ok := SeatNamed("node-build-go")
if !ok || len(got.Accepts) == 0 || !got.PulledByKind || !got.Yields {
t.Fatalf("the Go build seat lost what it accepts when loaded from the store: %+v", got)
}
if got.Decision != "row" {
t.Fatalf("the store's own columns were not kept: %+v", got)
}
}