Files
jschoubben 63ca073938 The store owns the seat set, so only the control plane may judge a claim
A claim on a seat was checked against `SeatNamed` inside `ParseManifest`, and the
build machine parses manifests too. It has no store, so there it answered from the
set compiled into the binary — a copy of data the control plane owns (ADR 0122).

When the two disagreed, that copy won where it mattered. The store's row said the
bus seat answers for `amqp`; the binary's said `mesh-bus`; and a holder that
provides `amqp` was refused at build time for not providing `mesh-bus`. The seat
went unheld, the controller lost the address it composes through that seat, and the
control plane crash-looped on a bus that was healthy the whole time.

So the two checks that read the set — a seat's scope, and what its holder must
provide — move to CatalogueProblems, which runs only in the control plane and only
after UseSeats has replaced the set with the store's. The parser keeps what it can
judge from the manifest alone, the reserved-namespace rule included.

A test pins it: the same manifest, two different values in the store, and the answer
follows the store both times. It fails if the check moves back.
2026-09-27 21:59:40 +02:00

325 lines
14 KiB
Go

package catalogue
import (
"encoding/json"
"os"
"path/filepath"
"regexp"
"strings"
"testing"
)
// Defends novox/hq ADR 0110: a seat is a module assignment from a closed set.
// The set is closed, and changing it is a decision.
//
// **The count is asserted, and every entry names the record that made it a seat**, so the next
// person changing the set finds the argument rather than a number to edit — the pattern the host's
// vocabulary test follows. If this fails because a seat was added, the fix is a record in novox/hq
// and a row in to-be 26, not a new number here.
func TestTheSeatsAreAClosedSetAndEachNamesItsDecision(t *testing.T) {
record := regexp.MustCompile(`^novox/hq ADR \d{4}$`)
seen := map[string]bool{}
delivered := map[string]string{}
for _, s := range Seats() {
if seen[s.Name] {
t.Errorf("%s is in the set twice", s.Name)
}
seen[s.Name] = true
if !record.MatchString(s.Decision) {
t.Errorf("%s names %q as its decision; every seat names the record that made it one",
s.Name, s.Decision)
}
switch s.Scope {
case ScopeNode, ScopeSite, ScopeMesh:
default:
t.Errorf("%s is held per %q, which is not a scope", s.Name, s.Scope)
}
if s.Delivers != "" {
// Two seats answering for one provision would put the question "which one?" back,
// which is the question a seat exists to answer.
if other, twice := delivered[s.Delivers]; twice {
t.Errorf("%s and %s both deliver %q", other, s.Name, s.Delivers)
}
delivered[s.Delivers] = s.Name
}
}
if len(Seats()) != 14 {
t.Errorf("the mesh defines %d seats rather than 14; the set is closed, so a change here is "+
"a decision (novox/hq ADR 0110): %s", len(Seats()), seatNames())
}
}
// novox/hq ADR 0117: a machine's uplink is a seat, held per machine, and delivers nothing.
//
// **Nothing, because nothing may be required of it.** A holder only keeps its network manager from
// contradicting the mesh; a requirement resolving to it would make the manager the mesh's answer
// for something, and the manager's link is the one thing the mesh must never be able to break.
func TestTheUplinkIsANodeSeatThatDeliversNothing(t *testing.T) {
seat, known := SeatNamed("node-uplink")
if !known {
t.Fatalf("the uplink is not a seat; the seats are: %s", seatNames())
}
if seat.Scope != ScopeNode || seat.Delivers != "" || seat.Decision != "novox/hq ADR 0117" {
t.Fatalf("the uplink is %+v, not a node seat delivering nothing by ADR 0117", seat)
}
// And a manager's module can hold it without providing anything.
raw := []byte(`{"module":"networkmanager","version":"1","claims":[{"name":"node-uplink","scope":"node"}]}`)
if _, err := ParseManifest(raw); err != nil {
t.Fatalf("a network manager's module could not hold the uplink: %v", err)
}
}
func claimed(claims string) []byte {
return []byte(`{"module":"thing","version":"1","provides":[{"name":"npm-package-registry","scope":"mesh"}],"claims":` + claims + `}`)
}
// **The refusal moved, it did not go** (novox/hq ADR 0118, superseding 0110). A module may now
// declare its own seats, so whether a claimed seat exists is a fact about the *catalogue* and not
// about the manifest in front of the parser: a claim on a seat another registered module declares
// is perfectly good, and the parser cannot tell the two cases apart. So the parser accepts it and
// registration refuses it — the same guarantee, at the same moment work would otherwise start,
// from a set nobody maintains by hand.
func TestAClaimOnASeatNobodyDeclaresIsRefusedAtRegistration(t *testing.T) {
m, err := ParseManifest(claimed(`[{"name":"the-anything","scope":"node"}]`))
if err != nil {
t.Fatalf("the parser judged a claim it cannot judge alone: %v", err)
}
problems := CatalogueProblems(Shelf{m.Module: m})
if len(problems) == 0 {
t.Fatal("a module invented a seat by claiming it, and registration allowed it")
}
joined := strings.Join(problems, "; ")
if !strings.Contains(joined, "the-anything") || !strings.Contains(joined, "no module declares") {
t.Fatalf("the refusal does not say the seat is nobody's: %v", problems)
}
}
// And the same claim is fine once something declares that seat, which is the case the parser
// could not have distinguished.
func TestAClaimOnASeatAnotherModuleDeclaresIsAccepted(t *testing.T) {
claimant, err := ParseManifest(claimed(`[{"name":"the-anything","scope":"node"}]`))
if err != nil {
t.Fatal(err)
}
declarer := Manifest{Module: "someone", DefinesSeats: []SeatDeclaration{
{Name: "the-anything", Scope: ScopeNode, Accepts: []string{"work"}},
}}
if problems := CatalogueProblems(Shelf{claimant.Module: claimant, "someone": declarer}); len(problems) != 0 {
t.Fatalf("a claim on a declared seat was refused: %v", problems)
}
}
// A module may define its own seat and claim it — the mesh enforces exclusivity without knowing
// what it means (novox/hq ADR 0121). But it may not define one in the mesh's own namespace.
func TestAModuleDefinesAndClaimsItsOwnSeat(t *testing.T) {
ok := []byte(`{"module":"showcase","version":"1","seats":[{"name":"the-showcase","scope":"node"}],` +
`"claims":[{"name":"the-showcase","scope":"node"}]}`)
if _, err := ParseManifest(ok); err != nil {
t.Fatalf("a module could not define and claim its own seat: %v", err)
}
// Claiming a name nobody defines is still refused — but **at registration, not here**: with
// seats declared by modules, a claim on a seat *another* module declares is good, and the
// parser cannot tell that from an invented name. See
// TestAClaimOnASeatNobodyDeclaresIsRefusedAtRegistration.
claimant, err := ParseManifest(claimed(`[{"name":"the-anything","scope":"node"}]`))
if err != nil {
t.Fatalf("the parser judged a claim it cannot judge alone: %v", err)
}
if len(CatalogueProblems(Shelf{claimant.Module: claimant})) == 0 {
t.Fatal("a module claimed a seat nobody defines")
}
// A module may not carve its seat out of the mesh's own namespace.
bad := []byte(`{"module":"x","version":"1","seats":[{"name":"node-mine","scope":"node"}],` +
`"claims":[{"name":"node-mine","scope":"node"}]}`)
if _, err := ParseManifest(bad); err == nil || !strings.Contains(err.Error(), "own namespace") {
t.Fatalf("a module defined a seat in the mesh's namespace and was not refused: %v", err)
}
}
// **At registration, not in the parser** (novox/hq ADR 0122): a seat's scope is a property of the
// set, the set is the store's, and the parser also runs on a build machine that has no store.
func TestASeatClaimedAtAnotherScopeIsRefused(t *testing.T) {
m, err := ParseManifest(claimed(`[{"name":"npm-package-registry","scope":"node"}]`))
if err != nil {
t.Fatalf("the parser judged a scope it reads from data it may not have: %v", err)
}
got := strings.Join(CatalogueProblems(Shelf{m.Module: m}), "; ")
if !strings.Contains(got, "mesh seat") {
t.Fatalf("the refusal does not say which scope the seat is: %q", got)
}
}
func TestADeliveringSeatIsOnlyHeldByAModuleThatProvides(t *testing.T) {
// Holding it makes the module the mesh's answer for the provision. A module that cannot answer
// would be the answer anyway, and every consumer would be sent to it.
// And refused at registration, where the seat set is the store's: what a seat delivers is
// data, so a compiled copy of it may not be what refuses a build (novox/hq ADR 0122).
raw := []byte(`{"module":"thing","version":"1","claims":[{"name":"git","scope":"mesh"}]}`)
m, err := ParseManifest(raw)
if err != nil {
t.Fatalf("the parser judged what a seat delivers: %v", err)
}
got := strings.Join(CatalogueProblems(Shelf{m.Module: m}), "; ")
if !strings.Contains(got, `does not provide "git"`) {
t.Fatalf("the refusal does not say what is missing: %q", got)
}
}
func TestAClaimThatIsMalformedIsRefusedOnceForThat(t *testing.T) {
// Not a second time for being unknown: one mistake, one line.
_, err := ParseManifest(claimed(`[{"name":"Not A Name","scope":"node"}]`))
if err == nil {
t.Fatal("a malformed claim was accepted")
}
if strings.Contains(err.Error(), "not a seat") {
t.Fatalf("a malformed claim was also called unknown: %v", err)
}
}
// Every module in use claims a seat in the set, so closing it refuses nothing that runs.
//
// Read from the catalogue beside this checkout and from this repository's own manifest, the two
// places a manifest lives (ADR 0069). The private-network module's manifest is composed in code,
// and its claim is checked where it is composed.
func TestEveryManifestInUseClaimsASeatTheMeshDefines(t *testing.T) {
paths, _ := filepath.Glob("../../../mesh-catalog/modules/*/module.json")
if len(paths) == 0 {
t.Skip("the catalogue is not beside this checkout")
}
paths = append(paths, "../../module.json")
var checked int
for _, path := range paths {
raw, err := os.ReadFile(path)
if err != nil {
t.Fatal(err)
}
// Leniently, so a manifest refused for something unrelated is not reported as a seat
// problem, and the seat check below is the only thing this test holds a module to.
var m Manifest
if err := json.Unmarshal(raw, &m); err != nil {
t.Fatalf("%s: %v", path, err)
}
for _, problem := range claimProblems(m) {
t.Errorf("%s: %s", path, problem)
}
checked += len(m.Claims)
}
if checked == 0 {
t.Fatal("no claims were checked, so this proved nothing")
}
}
// The holder of a seat answers among several providers.
func registryShelf() map[string]Manifest {
return shelf(
Manifest{Module: "gitea", Version: "1", Provides: FromAnywhere("npm-package-registry"),
Claims: []Claim{{Name: "npm-package-registry", Scope: ScopeMesh}}},
Manifest{Module: "verdaccio", Version: "1", Provides: FromAnywhere("npm-package-registry")},
Manifest{Module: "builder", Version: "1", Requires: []string{"npm-package-registry"}},
)
}
func twoRegistries() map[string][]Provider {
return map[string][]Provider{"npm-package-registry": {
{Node: "anchor", At: "anchor.internal", Module: "gitea"},
{Node: "archive", At: "archive.internal", Module: "verdaccio"},
}}
}
func giteaHoldsTheSeat() []Held {
return []Held{{Claim: "npm-package-registry", Scope: ScopeMesh, Node: "anchor", Module: "gitea"}}
}
func TestTheSeatsHolderAnswersWhenSeveralProvide(t *testing.T) {
// The whole point: a second registry beside the holder harms nothing, and nobody pins.
got, err := Resolve(registryShelf(), []string{"builder"}, reachable(),
World{Offered: twoRegistries(), Held: giteaHoldsTheSeat()})
if err != nil {
t.Fatal(err)
}
if len(got.Needs) != 1 || got.Needs[0].From != "anchor" {
t.Fatalf("the seat's holder did not answer: %v", got.Needs)
}
}
func TestAPinStillWinsOverTheSeat(t *testing.T) {
// A consumer coupled to one provider's contents has said so, and the seat does not overrule it.
got, err := Resolve(registryShelf(), []string{"builder"}, reachable(),
World{Offered: twoRegistries(), Held: giteaHoldsTheSeat(),
Pinned: map[string]string{"npm-package-registry": "archive"}})
if err != nil {
t.Fatal(err)
}
if len(got.Needs) != 1 || got.Needs[0].From != "archive" {
t.Fatalf("the pin was overruled by the seat: %v", got.Needs)
}
}
func TestWithTheSeatUnheldSeveralProvidersAreStillRefused(t *testing.T) {
// No seat held is no choice made, and ADR 0009's rule stands: never guessed.
_, err := Resolve(registryShelf(), []string{"builder"}, reachable(),
World{Offered: twoRegistries()})
if err == nil {
t.Fatal("one of two registries was picked with nobody holding the seat")
}
if !strings.Contains(err.Error(), "pin") {
t.Fatalf("the refusal does not say how to choose: %v", err)
}
}
func TestTheHolderIsTheModuleNotTheMachine(t *testing.T) {
// Two modules on one machine could provide the same thing; only the one holding the seat
// answers. A holder matched by node alone would send consumers to whichever came first.
providers := []Provider{
{Node: "anchor", At: "anchor.internal", Module: "verdaccio"},
{Node: "anchor", At: "anchor.internal", Module: "gitea"},
}
holder, held := HolderAmong("npm-package-registry", providers, giteaHoldsTheSeat())
if !held || holder.Module != "gitea" {
t.Fatalf("the holder was not told apart from a neighbour: %+v", holder)
}
}
// The working set is loaded from the store, and an empty load never erases it (novox/hq ADR 0122).
func TestUseSeatsReplacesTheSetButNeverEmptiesIt(t *testing.T) {
before := Seats()
defer UseSeats(DefaultSeats()) // restore for other tests, whatever this leaves it as
// An empty load (store not seeded, or unreadable) leaves the compiled defaults in force.
UseSeats(nil)
if len(Seats()) != len(before) {
t.Fatalf("an empty load changed the set from %d to %d seats", len(before), len(Seats()))
}
// A non-empty load replaces it — this is how a rename in the store reaches the lookups.
UseSeats([]Seat{{Name: "node-firewall", Scope: ScopeNode, Decision: "novox/hq ADR 0122"}})
if _, known := SeatNamed("node-firewall"); !known {
t.Fatal("the loaded set did not replace the working set")
}
if len(Seats()) != 1 {
t.Fatalf("the working set is %d seats, not the one that was loaded", len(Seats()))
}
}
// A former name resolves to the seat it was renamed from (novox/hq ADR 0122), so a manifest's claim
// and a held record naming the old name break nothing after a rename.
func TestAFormerNameResolvesAfterARename(t *testing.T) {
defer func() { UseSeats(DefaultSeats()); UseAliases(nil) }()
UseSeats([]Seat{{Name: "git", Scope: ScopeMesh, Delivers: "git", Decision: "novox/hq ADR 0121"}})
UseAliases(map[string]string{"git": "git"})
// The old name resolves to the renamed seat.
if s, ok := SeatNamed("git"); !ok || s.Name != "git" {
t.Fatalf("the former name did not resolve to the renamed seat: %+v ok=%v", s, ok)
}
// And a holder recorded under the old name is still found for the provision the seat delivers.
providers := []Provider{{Node: "anchor", At: "anchor.internal", Module: "gitea"}}
held := []Held{{Claim: "git", Scope: ScopeMesh, Node: "anchor", Module: "gitea"}}
holder, found := HolderAmong("git", providers, held)
if !found || holder.Module != "gitea" {
t.Fatalf("the holder recorded under the former name was not matched: %+v found=%v", holder, found)
}
}