diff --git a/cmd/mesh-controller/replays_test.go b/cmd/mesh-controller/replays_test.go new file mode 100644 index 0000000..636b0c2 --- /dev/null +++ b/cmd/mesh-controller/replays_test.go @@ -0,0 +1,113 @@ +package main + +import ( + "testing" + + "github.com/novox/mesh-controller/internal/catalogue" +) + +// The replays of the controller's incidents (novox/hq to-be 45 §9, M9): each a scripted replay of what +// happened, asserting the rule's outcome rather than the fix's mechanism, so it can be run against the +// commit before the fix and fail there, and against the fix and pass. **Written only with what the +// controller had before each fix** — the stores, register, assign, planFor, declarationFor — so the +// prover (mesh-lab replays/cmd/prove) can lay this file over the older commit and run it there. +// +// Registered in mesh-lab's replays/register.go with the fix each one proves; run by this repository's +// merge check on every pull request with the rest of the suite. + +// **R263 — a consumer's identity never refuses its provider's machine.** On 2026-10-06 the network +// manager came to require the mesh's resolver; on a machine whose name made its identity 23 to 26 +// characters against the one global bound of 20, the anchor — the resolver's holder, carrying every +// consumer's grant — could not compose, and no push to it could go through. The outcome asserted: the +// provider's machine composes whatever its consumers are called, and a provision that mints no +// credential holds no consumer to a key's length. +func TestReplay263AnIdentityTooLongNeverRefusesItsProvidersMachine(t *testing.T) { + open := aMesh(t) + ctx := t.Context() + register(t, open, catalogue.Manifest{Module: "resolver", Version: "1", + Provides: []catalogue.Offer{{Name: "wildcard-resolution", Scope: catalogue.ScopeMesh}}}) + register(t, open, catalogue.Manifest{Module: "networkmanager", Version: "1", + Requires: []string{"wildcard-resolution"}}) + for _, a := range [][2]string{{"anchor", "resolver"}, {"laptop", "networkmanager"}} { + if _, err := assign(ctx, open, a[0], a[1]); err != nil { + t.Fatalf("assign %s %s: %v", a[0], a[1], err) + } + } + // The consumer's machine composes first, as a push does: what it is sent is what its provider grants. + for _, node := range []string{"laptop", "anchor"} { + plan, settings, err := planFor(ctx, open, node) + if err != nil { + t.Fatalf("%s does not resolve: %v", node, err) + } + if _, err := declarationFor(ctx, open, node, plan, settings); err != nil { + t.Fatalf("%s cannot be sent anything — the consumer networkmanager on laptop, identified "+ + "mesh_laptop_networkmanager (26 characters), refused it: %v", node, err) + } + } +} + +// **R273 — a binding to a consumer's data does not move by itself.** On 2026-10-05 a rule written for +// the resolver re-bound every database consumer on the home server — which runs its own store, beside +// its applications' data — to the store seat's holder on the control node, which made each a new empty +// database; five applications ran on empty data for twenty hours. The outcome asserted: a consumer on a +// machine running its own store stays bound to it while another machine holds the store's seat; the +// resolver, which every holder answers alike, follows its seat. +func TestReplay273AConsumerBesideItsStoreStaysBoundToIt(t *testing.T) { + open := aMesh(t) + ctx := t.Context() + inv := open.inventory + if _, err := inv.SeedSeats(ctx, catalogue.DefaultSeats()); err != nil { + t.Fatal(err) + } + for _, m := range []catalogue.Manifest{ + {Module: "store", Version: "1", + Provides: []catalogue.Offer{{Name: "postgres-database", Scope: catalogue.ScopeMesh}}, + Claims: []catalogue.Claim{{Name: "mesh-store", Scope: catalogue.ScopeMesh}}, + Serves: map[string]map[string]any{"postgres-database": {"port": 5432}}, + Grants: map[string]string{"postgres-database": "/var/lib/mesh/store/grants"}}, + {Module: "resolver", Version: "1", + Provides: []catalogue.Offer{{Name: "wildcard-resolution", Scope: catalogue.ScopeMesh}}, + Claims: []catalogue.Claim{{Name: "mesh-dns-resolver", Scope: catalogue.ScopeMesh}}}, + {Module: "network", Version: "1", Requires: []string{"wildcard-resolution"}}, + {Module: "board", Version: "1", Requires: []string{"postgres-database"}}, + } { + register(t, open, m) + } + assignAll := func(pairs ...[2]string) { + for _, a := range pairs { + if _, err := assign(ctx, open, a[0], a[1]); err != nil { + t.Fatalf("assign %s %s: %v", a[0], a[1], err) + } + } + } + // The control node holds the mesh's store and resolver seats; the home server runs its own of each. + assignAll([2]string{"anchor", "store"}, [2]string{"anchor", "resolver"}) + for _, seat := range [][2]string{{"mesh-store", "store"}, {"mesh-dns-resolver", "resolver"}} { + if err := inv.HoldSeat(ctx, seat[0], catalogue.ScopeMesh, "anchor", seat[1]); err != nil { + t.Fatal(err) + } + } + assignAll([2]string{"laptop", "store"}, [2]string{"laptop", "resolver"}, [2]string{"laptop", "network"}, + [2]string{"laptop", "board"}) + + plan, _, err := planFor(ctx, open, "laptop") + if err != nil { + t.Fatal(err) + } + var board, network string + for _, n := range plan.Needs { + switch { + case n.For == "board" && n.Name == "postgres-database": + board = n.From + case n.For == "network" && n.Name == "wildcard-resolution": + network = n.From + } + } + if board != "laptop" { + t.Fatalf("the consumer beside its store was bound to the store on %q, which would make it a new, empty "+ + "database there (issue 273)", board) + } + if network != "anchor" { + t.Fatalf("the resolver was bound to %q; its seat is held on anchor (issue 258)", network) + } +}