From fc4c5d1a60ddcbbfdc9aa6b18f88adc38e62c5b7 Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 17 Sep 2026 02:13:26 +0200 Subject: [PATCH] The controller's seat is mesh-controller; a foundation module on a second node is refused Renames the module's own claim the-controller -> mesh-controller (the seat is the server, ADR 0079), and adds TestAFoundationModuleCannotBeRaisedOnASecondNode asserting each foundation module's second assignment is refused with 'one per mesh'. Closes hq issue 056. https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx --- internal/catalogue/resolve_test.go | 23 +++++++++++++++++++++++ module.json | 2 +- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/internal/catalogue/resolve_test.go b/internal/catalogue/resolve_test.go index 80c4e69..24d7144 100644 --- a/internal/catalogue/resolve_test.go +++ b/internal/catalogue/resolve_test.go @@ -224,6 +224,29 @@ func TestAMeshWideClaimIsHeldByOneNode(t *testing.T) { } } +func TestAFoundationModuleCannotBeRaisedOnASecondNode(t *testing.T) { + // novox/hq 04-ISSUES/056. The store and broker are adopted in place on the control-node; each + // foundation module claims a mesh-scoped seat named after its server — the same mechanism that + // keeps one controller — so a second `assign` to another node is refused rather than silently + // raising a second postgres or broker that holds none of the first's data. + for _, tc := range []struct{ module, seat string }{ + {"postgres", "mesh-store"}, + {"lavinmq", "mesh-broker"}, + {"mesh-controller", "mesh-controller"}, + } { + _, err := Resolve( + shelf(mod(tc.module, nil, nil, nil, Claim{Name: tc.seat, Scope: ScopeMesh})), + []string{tc.module}, workstation(), + World{Held: []Held{{Claim: tc.seat, Scope: ScopeMesh, Node: "anchor", Module: tc.module}}}) + if err == nil { + t.Fatalf("%s was raised on a second node though %s is a mesh-wide seat", tc.module, tc.seat) + } + if !strings.Contains(err.Error(), "one per mesh") { + t.Errorf("%s: the refusal does not say it is one per mesh: %v", tc.module, err) + } + } +} + func TestASiteClaimOnlyCollidesWithinThatSite(t *testing.T) { // A DHCP server per segment. Two of them is a fault at one site and perfectly ordinary // across two, and treating site as mesh would forbid the ordinary case. diff --git a/module.json b/module.json index a3b7a9d..7d55ffc 100644 --- a/module.json +++ b/module.json @@ -7,7 +7,7 @@ ], "claims": [ { - "name": "the-controller", + "name": "mesh-controller", "scope": "mesh" } ], -- 2.54.0