Files
jschoubben c5dc7e732a A store row keeps its seat's protocol, and a holder may take work from its queue
The seat table has name, scope, delivers and decision, and the protocol ADR 0129
gave a seat lives only in the compiled defaults; loading the rows dropped it, so
no role's work queue was ever raised and the first build submitted over the new
bus met "no response from stream". Until the table gains the columns, a row with
no protocol keeps the compiled one of its name. And the holder of a seat is
granted what taking work from its queue needs — asking about the worker consumer
it binds, and acknowledging on it — which the first machine to try was refused.

The control plane's own seat placeholders no longer include the old bus's port,
which the switch removed with the variable.
2026-09-28 02:08:56 +02:00

161 lines
6.8 KiB
Go

package catalogue
import (
"strings"
"testing"
)
// **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 oldBroker() Manifest {
return Manifest{Module: "old-broker", Provides: []Offer{{Name: "mesh-bus", Scope: ScopeMesh}},
Claims: []Claim{{Name: "mesh-broker", Scope: ScopeMesh}}}
}
func newBroker() Manifest {
return Manifest{Module: "new-broker", Provides: []Offer{{Name: "mesh-bus", Scope: ScopeMesh}},
Claims: []Claim{{Name: "mesh-broker", Scope: ScopeMesh}}}
}
// Nothing on record: exactly the old rule. One claimant holds; two are refused.
func TestWithNoHolderOnRecordTheSoleClaimantHoldsAndTwoAreRefused(t *testing.T) {
busSeatDelivering(t, "mesh-bus")
node := Node{Name: "anchor"}
held, problems := checkClaims([]Manifest{oldBroker()}, node, nil, nil)
if len(problems) != 0 || len(held) != 1 || held[0].Module != "old-broker" {
t.Fatalf("a sole claimant did not hold the seat: held=%v problems=%v", held, problems)
}
_, problems = checkClaims([]Manifest{oldBroker(), newBroker()}, node, nil, nil)
if len(problems) != 1 || !strings.Contains(problems[0], "both claim") {
t.Fatalf("two claimants with nothing on record were not refused: %v", problems)
}
}
// With a holder on record, the other eligible assignment is silent: not refused, and not holding.
func TestTheHolderOnRecordHoldsAndTheOtherClaimantStandsBesideIt(t *testing.T) {
busSeatDelivering(t, "mesh-bus")
node := Node{Name: "anchor"}
record := []Held{{Claim: "mesh-broker", Scope: ScopeMesh, Node: "anchor", Module: "new-broker"}}
held, problems := checkClaims([]Manifest{oldBroker(), newBroker()}, node, nil, record)
if len(problems) != 0 {
t.Fatalf("the assignment beside the holder was refused: %v", problems)
}
if len(held) != 1 || held[0].Module != "new-broker" {
t.Fatalf("the holder on record is not the one holding: %v", held)
}
}
// The record names a node too: an eligible module on another machine holds nothing, and its
// machine's set still resolves.
func TestAHolderOnRecordElsewhereLeavesThisMachinesClaimantSilent(t *testing.T) {
busSeatDelivering(t, "mesh-bus")
record := []Held{{Claim: "mesh-broker", Scope: ScopeMesh, Node: "anchor", Module: "new-broker"}}
held, problems := checkClaims([]Manifest{oldBroker()}, Node{Name: "laptop"}, nil, record)
if len(problems) != 0 || len(held) != 0 {
t.Fatalf("a claimant elsewhere than the recorded holder was not simply silent: held=%v problems=%v",
held, problems)
}
}
// A record naming a seat's former name still applies to it after a rename (ADR 0122).
func TestAHolderRecordedUnderAFormerNameStillHolds(t *testing.T) {
busSeatDelivering(t, "mesh-bus")
wasAliases := aliases
t.Cleanup(func() { UseAliases(wasAliases) })
UseAliases(map[string]string{"the-broker": "mesh-broker"})
record := []Held{{Claim: "the-broker", Scope: ScopeMesh, Node: "anchor", Module: "new-broker"}}
held, problems := checkClaims([]Manifest{oldBroker(), newBroker()}, Node{Name: "anchor"}, nil, record)
if len(problems) != 0 || len(held) != 1 || held[0].Module != "new-broker" {
t.Fatalf("a record under the former name did not settle the seat: held=%v problems=%v", held, problems)
}
}
// 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}}}
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)
}
// 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)
}
}
// A machine being moved is handed its membership for the new bus as a sealed file in its own
// declaration — the machine's, not any module's (design 28, task 5.2).
func TestAMembershipForTheNewBusIsComposedAsASealedFile(t *testing.T) {
r := Resolution{Node: "anchor"}
got, err := r.Compose(Rendering{BusMembership: "sealed-blob"})
if err != nil {
t.Fatal(err)
}
var found map[string]any
for _, res := range got.Resources {
if res["id"] == BusMembershipID() {
found = res
}
}
if found == nil {
t.Fatalf("no membership resource in %v", got.Resources)
}
if found["path"] != BusMembershipPath || found["sealed"] != "sealed-blob" || found["mode"] != "0600" {
t.Fatalf("the membership is not a sealed 0600 file where the host reads it: %v", found)
}
// And a machine not being moved is handed nothing.
got, _ = r.Compose(Rendering{})
for _, res := range got.Resources {
if res["id"] == BusMembershipID() {
t.Fatal("a machine with no membership on record was handed one")
}
}
}
// 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: "mesh-build-machine", Scope: ScopeMesh, Decision: "row"}})
got, ok := SeatNamed("mesh-build-machine")
if !ok || len(got.Accepts) == 0 {
t.Fatalf("the build machine's 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)
}
}