Files
mesh-controller/internal/catalogue/bound_test.go
T
jochen 8bfaf1523e Keep a consumer bound where its data is; only a pin moves it (hq ADR 0232, issue 273)
Issue 258's fix let a mesh seat's holder elsewhere answer before this machine's own provider. Right
for the resolver, which any provider answers alike; for the store's seat it re-bound every database
consumer on a machine running its own store to the holder on another, each was given a fresh, empty
database there, and nothing said so for twenty hours.

- An offer says whether it keeps its consumers' data (`keeps-consumer-data`); unsaid, a provider
  that grants each consumer a credential does. For such a provision the seat's holder no longer
  overrules a provider beside the consumer; a pin still does.
- Where each such consumer was sent is recorded (migration 0071). A resolution that would bind it
  elsewhere keeps the recorded provider and says the move; one whose provider is gone is refused,
  never answered by another.
- A push says a kept move and raises it as an urgent condition at once; the self-check's D12 raises
  it every run, with a pinned move not yet sent as a warning and any unasked move as urgent.
2026-10-06 15:19:23 +02:00

258 lines
9.9 KiB
Go

package catalogue
import (
"encoding/json"
"strings"
"testing"
)
// A consumer of a provision that keeps its data stays bound where its data is (novox/hq ADR 0232,
// issue 273).
//
// The incident, exactly: a machine runs its own store and five consumers of it; the mesh's store seat
// is held by the store on another machine. Issue 258's rule — the seat's holder elsewhere answers
// before this machine's own provider — re-bound all five to the holder, each was made a fresh, empty
// database there, and nothing said so.
var incidentConsumers = []string{"board", "listings", "workflows", "agents", "game"}
func storeMesh() map[string]Manifest {
shelf := map[string]Manifest{
"store": {Module: "store", Version: "1",
Provides: []Offer{{Name: "postgres-database", Scope: ScopeMesh}},
Claims: []Claim{{Name: "mesh-store", Scope: ScopeMesh}},
Serves: map[string]map[string]any{"postgres-database": {"port": 5432}},
Grants: map[string]string{"postgres-database": "/grants"}},
"resolver": {Module: "resolver", Version: "1",
Provides: []Offer{{Name: "wildcard-resolution", Scope: ScopeMesh}},
Claims: []Claim{{Name: "mesh-dns-resolver", Scope: ScopeMesh}}},
"network": {Module: "network", Version: "1", Requires: []string{"wildcard-resolution"}},
}
for _, c := range incidentConsumers {
shelf[c] = Manifest{Module: c, Version: "1", Requires: []string{"postgres-database"}}
}
return shelf
}
// storeWorld is the rest of the mesh as the home server's plan sees it: the anchor runs a store and a
// resolver and holds both seats; the home server runs its own of each.
func storeWorld() World {
w := World{Offered: map[string][]Provider{
"postgres-database": {
{Node: "anchor", At: "anchor.internal", Module: "store", Serves: map[string]any{"port": 5432}},
{Node: "home", At: "home.internal", Module: "store", Serves: map[string]any{"port": 5434}},
},
"wildcard-resolution": {
{Node: "anchor", At: "anchor.internal", Module: "resolver"},
{Node: "home", At: "home.internal", Module: "resolver"},
},
}}
w.Held = []Held{
{Claim: "mesh-store", Scope: ScopeMesh, Node: "anchor", Module: "store"},
{Claim: "mesh-dns-resolver", Scope: ScopeMesh, Node: "anchor", Module: "resolver"},
}
w.Holdings = w.Held
return w
}
var home = Node{Name: "home", At: "home.internal"}
func homeAssigned() []string {
return append([]string{"store", "resolver", "network"}, incidentConsumers...)
}
func boundFrom(t *testing.T, r Resolution, consumer, provision string) Needed {
t.Helper()
for _, n := range r.Needs {
if n.For == consumer && n.Name == provision {
return n
}
}
t.Fatalf("no binding of %s for %s: %+v", consumer, provision, r.Needs)
return Needed{}
}
func TestTheIncidentAMachinesOwnStoreKeepsItsConsumersWhenTheSeatIsHeldElsewhere(t *testing.T) {
got, err := Resolve(storeMesh(), homeAssigned(), home, storeWorld())
if err != nil {
t.Fatal(err)
}
for _, c := range incidentConsumers {
if n := boundFrom(t, got, c, "postgres-database"); n.From != "home" || n.Module != "store" {
t.Errorf("%s bound to %s/%s; its data is on the store beside it (issue 273)", c, n.From, n.Module)
}
}
// And the resolver, which keeps nothing of anybody's, still answers from the seat's holder (258).
if n := boundFrom(t, got, "network", "wildcard-resolution"); n.From != "anchor" {
t.Errorf("the resolver bound to %s; the seat is held on anchor (issue 258)", n.From)
}
if len(got.Kept) != 0 {
t.Errorf("nothing was moved, and %d kept: %+v", len(got.Kept), got.Kept)
}
}
func TestTheIncidentWithEveryConsumerOnRecordStillMovesNothing(t *testing.T) {
w := storeWorld()
w.Bound = map[string]map[string]Chosen{}
for _, c := range incidentConsumers {
w.Bound[c] = map[string]Chosen{"postgres-database": {Node: "home", Module: "store"}}
}
got, err := Resolve(storeMesh(), homeAssigned(), home, w)
if err != nil {
t.Fatal(err)
}
for _, c := range incidentConsumers {
if n := boundFrom(t, got, c, "postgres-database"); n.From != "home" {
t.Errorf("%s bound to %s", c, n.From)
}
}
if len(got.Kept) != 0 {
t.Errorf("kept %+v", got.Kept)
}
}
func TestAPinElsewhereStillMovesAStoresConsumers(t *testing.T) {
w := storeWorld()
w.Pinned = map[string]Chosen{"postgres-database": {Node: "anchor", Module: "store"}}
w.Bound = map[string]map[string]Chosen{"board": {"postgres-database": {Node: "home", Module: "store"}}}
got, err := Resolve(storeMesh(), homeAssigned(), home, w)
if err != nil {
t.Fatal(err)
}
if n := boundFrom(t, got, "board", "postgres-database"); n.From != "anchor" || n.Module != "store" {
t.Errorf("bound to %s/%s; a person pinned it to anchor", n.From, n.Module)
}
if len(got.Kept) != 0 {
t.Errorf("a pin is a person's move, not one to keep: %+v", got.Kept)
}
}
// A consumer on a machine with no store of its own, bound to one store, when the seat moves to
// another: the holder would answer, and the binding stays.
func laptopWorld(holder string) World {
w := storeWorld()
w.Held = []Held{{Claim: "mesh-store", Scope: ScopeMesh, Node: holder, Module: "store"}}
w.Holdings = w.Held
return w
}
var laptop = Node{Name: "laptop", At: "laptop.internal"}
func TestABoundConsumerStaysWhenTheSeatsHolderChanges(t *testing.T) {
w := laptopWorld("home")
w.Bound = map[string]map[string]Chosen{"board": {"postgres-database": {Node: "anchor", Module: "store"}}}
got, err := Resolve(storeMesh(), []string{"board"}, laptop, w)
if err != nil {
t.Fatal(err)
}
n := boundFrom(t, got, "board", "postgres-database")
if n.From != "anchor" || n.At != "anchor.internal" || n.Serves["port"] != 5432 {
t.Fatalf("bound to %s at %s %v; its data is on anchor", n.From, n.At, n.Serves)
}
if len(got.Kept) != 1 {
t.Fatalf("the move was not said: %+v", got.Kept)
}
k := got.Kept[0]
if k.Bound != (Chosen{"anchor", "store"}) || k.Would != (Chosen{"home", "store"}) || k.Consumer != "board" {
t.Fatalf("kept %+v", k)
}
for _, want := range []string{"would move board's postgres-database from anchor/store to home/store",
"its data is on anchor/store", "pin laptop postgres-database home store", "move the data first"} {
if !strings.Contains(k.String(), want) {
t.Errorf("%q does not say %q", k.String(), want)
}
}
// Without a record it is a binding being made, and the holder answers it as before.
w.Bound = nil
got, err = Resolve(storeMesh(), []string{"board"}, laptop, w)
if err != nil {
t.Fatal(err)
}
if n := boundFrom(t, got, "board", "postgres-database"); n.From != "home" {
t.Errorf("a new consumer bound to %s; the seat is held on home", n.From)
}
}
func TestABoundConsumerWhoseProviderIsGoneIsRefusedNotMoved(t *testing.T) {
w := laptopWorld("anchor")
w.Offered["postgres-database"] = w.Offered["postgres-database"][:1] // only anchor's store is left
w.Bound = map[string]map[string]Chosen{"board": {"postgres-database": {Node: "home", Module: "store"}}}
_, err := Resolve(storeMesh(), []string{"board"}, laptop, w)
if err == nil {
t.Fatal("bound to home's store, which is gone, and answered by anchor's — the silent move")
}
for _, want := range []string{"board on laptop is bound to home/store", "home/store no longer provides it",
"pin laptop postgres-database anchor store"} {
if !strings.Contains(err.Error(), want) {
t.Errorf("%q does not say %q", err, want)
}
}
}
func TestAMachinesOwnStoreUnassignedRefusesItsBoundConsumers(t *testing.T) {
w := storeWorld()
w.Bound = map[string]map[string]Chosen{"board": {"postgres-database": {Node: "home", Module: "store"}}}
_, err := Resolve(storeMesh(), []string{"board"}, home, w)
if err == nil || !strings.Contains(err.Error(), "store no longer runs on home") {
t.Fatalf("got %v", err)
}
}
// The first pass asks only what a machine offers, and is never refused by a binding.
func TestTheFirstPassKeepsNoBinding(t *testing.T) {
w := storeWorld()
w.Unchecked = true
w.Bound = map[string]map[string]Chosen{"board": {"postgres-database": {Node: "gone", Module: "store"}}}
if _, err := Resolve(storeMesh(), []string{"board"}, laptop, w); err != nil {
t.Fatal(err)
}
}
func TestAnOfferSaysWhetherItKeepsConsumerData(t *testing.T) {
var o Offer
if err := json.Unmarshal([]byte(`{"name":"wildcard-resolution","scope":"mesh","keeps-consumer-data":false}`), &o); err != nil {
t.Fatal(err)
}
if o.KeepsConsumerData == nil || *o.KeepsConsumerData {
t.Fatalf("read %+v", o)
}
out, err := json.Marshal(o)
if err != nil || !strings.Contains(string(out), `"keeps-consumer-data":false`) {
t.Fatalf("wrote %s, %v", out, err)
}
yes, no := true, false
granting := Manifest{Module: "g", Provides: []Offer{{Name: "p", Scope: ScopeMesh}}, Grants: map[string]string{"p": "/g"}}
if !granting.KeepsConsumerData("p") {
t.Error("a provider granting each consumer a credential keeps what it writes, unsaid")
}
if (Manifest{Module: "r", Provides: []Offer{{Name: "p", Scope: ScopeMesh}}}).KeepsConsumerData("p") {
t.Error("a provider granting nothing keeps nothing, unsaid")
}
granting.Provides[0].KeepsConsumerData = &no
if granting.KeepsConsumerData("p") {
t.Error("what an offer says, it gets")
}
said := Manifest{Module: "s", Provides: []Offer{{Name: "p", Scope: ScopeMesh, KeepsConsumerData: &yes}}}
if !said.KeepsConsumerData("p") || !KeepsConsumerData(map[string]Manifest{"s": said, "r": {Module: "r",
Provides: []Offer{{Name: "p", Scope: ScopeMesh, KeepsConsumerData: &no}}}}, "p") {
t.Error("a name any provider says keeps data keeps data")
}
}
// An offer saying it keeps nothing is answered by the seat's holder as the resolver is, even where it
// grants a credential.
func TestAStoreSayingItKeepsNothingFollowsTheSeat(t *testing.T) {
shelf := storeMesh()
no := false
s := shelf["store"]
s.Provides = []Offer{{Name: "postgres-database", Scope: ScopeMesh, KeepsConsumerData: &no}}
shelf["store"] = s
got, err := Resolve(shelf, homeAssigned(), home, storeWorld())
if err != nil {
t.Fatal(err)
}
if n := boundFrom(t, got, "board", "postgres-database"); n.From != "anchor" {
t.Errorf("bound to %s; it keeps nothing, and the seat is held on anchor", n.From)
}
}