From a192052d82fe59a192394bc40a935d2f2a1fb1b2 Mon Sep 17 00:00:00 2001 From: jochen Date: Mon, 2 Nov 2026 10:38:22 +0100 Subject: [PATCH] Refuse any placement of the controller (issue 1126) The grants part, the enrolment seat and the watchman now act without reading the lease, so a controller placed again would write beside them with nothing saying so. The assigner refuses every proposal, seed or joining node's write that would place mesh-controller, naming why. Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL --- .../cmd/mesh-delivery-assigner/seat.go | 1 + .../internal/assign/seat.go | 34 +++++++++++++++++++ .../internal/assign/seat_test.go | 17 ++++++---- 3 files changed, 46 insertions(+), 6 deletions(-) diff --git a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/seat.go b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/seat.go index aba87c50..e43ccd8f 100644 --- a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/seat.go +++ b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/seat.go @@ -559,6 +559,7 @@ func (p *passer) addNode(ctx context.Context, prop delivery.PlacementProposal, r return } laid, findings := delivery.LayChanges(current, prop.Changes) + findings = append(findings, assign.ControllerRefusals(prop.Changes, laid)...) node := prop.Changes[0].Node if prop.Changes[0].Op == delivery.OpRemoveNode { p.removeNode(ctx, prop, current, findings) diff --git a/modules/mesh-delivery-assigner/internal/assign/seat.go b/modules/mesh-delivery-assigner/internal/assign/seat.go index e9f99901..1ee398c1 100644 --- a/modules/mesh-delivery-assigner/internal/assign/seat.go +++ b/modules/mesh-delivery-assigner/internal/assign/seat.go @@ -2,6 +2,8 @@ package assign import ( "fmt" + "maps" + "slices" "strings" "time" @@ -53,6 +55,7 @@ func Propose(in ProposeIn) (delivery.PlacementProposal, map[string]*delivery.Pla } laid, lay := delivery.LayChanges(in.Current, in.Changes) findings = append(findings, lay...) + findings = append(findings, ControllerRefusals(in.Changes, laid)...) all := map[string]delivery.Placement{} for node, pl := range in.Current { all[node] = pl @@ -105,6 +108,37 @@ func Propose(in ProposeIn) (delivery.PlacementProposal, map[string]*delivery.Pla return p, out, findings } +// ControllerModule is the controller's module, which nothing places again (issue 1126). +const ControllerModule = "mesh-controller" + +// ControllerEnded is why a placement of the controller is refused: the parts that acted only once its lease was handed +// (consumers, the user list, enrolment, dead letters) now act without reading the lease, so a controller placed again +// would write beside them, silently. +const ControllerEnded = "the controller ended by its unassignment on 2026-11-02; placing it again would make two writers (issue 1126)" + +// ControllerRefusals names every change, and every node as laid, that places the controller: by a place, by a seed's +// whole placement, or in what the changes lay. None when nothing does. +func ControllerRefusals(changes []delivery.PlacementProposalChange, laid map[string]delivery.Placement) []string { + var out []string + nodes := map[string]bool{} + for i, c := range changes { + places := c.Module == ControllerModule && c.Op != delivery.OpTakeOff + if c.Whole != nil && slices.Contains(c.Whole.Assigned(), ControllerModule) { + places = true + } + if places { + out = append(out, fmt.Sprintf("change %d places %s on %s: %s", i+1, ControllerModule, c.Node, ControllerEnded)) + nodes[c.Node] = true + } + } + for _, node := range slices.Sorted(maps.Keys(laid)) { + if !nodes[node] && slices.Contains(laid[node].Assigned(), ControllerModule) { + out = append(out, fmt.Sprintf("%s would run %s: %s", node, ControllerModule, ControllerEnded)) + } + } + return out +} + // Write is one node a warrant writes (ยง5): the node, its node key `.node` (the commit of the node's keys, // mesh-sdk delivery.PlacementKeys.Writes), the revision of the node's placement it is written at (0: it has none, so // it is created) and its value (nil: it is deleted). diff --git a/modules/mesh-delivery-assigner/internal/assign/seat_test.go b/modules/mesh-delivery-assigner/internal/assign/seat_test.go index 9dea2d49..cd5bcfa6 100644 --- a/modules/mesh-delivery-assigner/internal/assign/seat_test.go +++ b/modules/mesh-delivery-assigner/internal/assign/seat_test.go @@ -37,12 +37,17 @@ func TestThePlacementSeatDecides(t *testing.T) { in ProposeIn want string }{ - "a module not held": {in(delivery.PlacementProposalChange{Op: delivery.OpPlace, Node: "laptop", Module: "ghost"}), "the indexer holds no module ghost"}, - "a node with no record": {in(delivery.PlacementProposalChange{Op: delivery.OpPlace, Node: "stranger", Module: "web"}), "stranger is a node with no record"}, - "nothing changes": {in(delivery.PlacementProposalChange{Op: delivery.OpPlace, Node: "anchor", Module: "web"}), "leave every placement as it is"}, - "no why": {func() ProposeIn { i := in(move...); i.Why = " "; return i }(), "says why"}, - "no caller": {func() ProposeIn { i := in(move...); i.By = ""; return i }(), "names no caller"}, - "a seed by hand": {in(delivery.PlacementProposalChange{Op: delivery.OpSeed, Node: "laptop", Whole: &laptop}), "gone with adopt"}, + "a module not held": {in(delivery.PlacementProposalChange{Op: delivery.OpPlace, Node: "laptop", Module: "ghost"}), "the indexer holds no module ghost"}, + "a node with no record": {in(delivery.PlacementProposalChange{Op: delivery.OpPlace, Node: "stranger", Module: "web"}), "stranger is a node with no record"}, + "nothing changes": {in(delivery.PlacementProposalChange{Op: delivery.OpPlace, Node: "anchor", Module: "web"}), "leave every placement as it is"}, + "no why": {func() ProposeIn { i := in(move...); i.Why = " "; return i }(), "says why"}, + "no caller": {func() ProposeIn { i := in(move...); i.By = ""; return i }(), "names no caller"}, + "a seed by hand": {in(delivery.PlacementProposalChange{Op: delivery.OpSeed, Node: "laptop", Whole: &laptop}), "gone with adopt"}, + // 1126: the controller ended by its unassignment; placed again it would be a second writer beside the parts that + // act without its lease. + "the controller placed again": {in(delivery.PlacementProposalChange{Op: delivery.OpPlace, Node: "laptop", Module: "mesh-controller"}), "ended by its unassignment"}, + "the controller seeded whole": {in(delivery.PlacementProposalChange{Op: delivery.OpSeed, Node: "anchor", Whole: &delivery.Placement{V: delivery.Version, Node: "anchor", + Decided: anchor.Decided, Modules: []delivery.PlacedModule{{Module: "mesh-controller"}}}}), "two writers (issue 1126)"}, "a pin of nobody placed": {in(delivery.PlacementProposalChange{Op: delivery.OpPin, Node: "laptop", Provision: "db", Provider: &delivery.Provider{Node: "laptop", Module: "store"}}), "not placed there"}, } { if _, _, f := Propose(c.in); !slices.ContainsFunc(f, func(s string) bool { return strings.Contains(s, c.want) }) {