diff --git a/cmd/mesh-controller/rollout.go b/cmd/mesh-controller/rollout.go index c386490..13b9f24 100644 --- a/cmd/mesh-controller/rollout.go +++ b/cmd/mesh-controller/rollout.go @@ -25,11 +25,11 @@ import ( // against a mesh that is serving. It answers from records: what is missing, and what would happen. // `rollout` itself refuses unless the check is clean. // -// **The old broker is not switched off by this.** It stays an ordinary provider of `amqp` for whatever -// else uses it — on this installation, a whole automation layer that has nothing to do with the mesh -// ([ADR 0119](../../02-DECISIONS/0119-amqp-is-a-provision-not-the-bus.md)). Only the mesh's own -// traffic moves, which is why this is survivable at all: what breaks if it goes wrong is the mesh's -// ability to change things, not the services its modules are serving. +// **The old broker goes with the move, and goes last** (novox/hq ADR 0131): AMQP is not a provision, +// so once every machine reports on the new bus its module is unassigned. Only the mesh's own traffic +// is what moves, which is why this is survivable at all: what breaks if it goes wrong is the mesh's +// ability to change things, not the services its modules are serving — measured on 2026-09-27, when +// a seat emptied mid-change and the control plane looped for two hours while every service stayed up. const rolloutUsage = "rollout check | rollout --confirm" @@ -105,7 +105,6 @@ func readinessOf(ctx context.Context, inv *inventory.Inventory) (broker.Readines ModuleCredentialled: map[string]bool{}, // The old broker keeps its other clients on this installation, and saying so is how the plan // stops reading as a retirement. - OldBusHasOtherClients: true, } address, _, err := broker.OnNATS() diff --git a/internal/broker/readiness.go b/internal/broker/readiness.go index a1302d3..0f978b9 100644 --- a/internal/broker/readiness.go +++ b/internal/broker/readiness.go @@ -35,10 +35,6 @@ type Readiness struct { Modules []string // ModuleCredentialled is which of those has one. ModuleCredentialled map[string]bool - // StillOnTheOldBus is whether anything of the mesh's own still needs the bus it is leaving — - // which is not a reason to stop, because that broker stays as an ordinary provider of `amqp` - // (ADR 0119). Recorded so nobody reads the move as a retirement. - OldBusHasOtherClients bool } // NotReady is every reason this mesh cannot move its bus yet, in the order somebody would fix them. @@ -120,10 +116,11 @@ func WhatMoves(r Readiness) []string { out = append(out, fmt.Sprintf("move %d module runtime(s), and confirm each answers", len(r.Modules))) } - if r.OldBusHasOtherClients { - out = append(out, "leave the old broker running: it stays an ordinary provider of `amqp` for "+ - "whatever else uses it (ADR 0119), and this move is not its retirement") - } + // **The old broker goes, and it goes last** (novox/hq ADR 0131). AMQP is not a provision, so once + // every machine reports on the new bus nothing of the mesh is left speaking to it, and its module + // is unassigned. Said as a step so nobody reads the move as leaving a second bus behind. + out = append(out, "then unassign the old broker's module: AMQP is not a provision (ADR 0131), and "+ + "once every machine reports on the new bus nothing of the mesh speaks to it") return out } diff --git a/internal/broker/readiness_test.go b/internal/broker/readiness_test.go index 827b786..97ff917 100644 --- a/internal/broker/readiness_test.go +++ b/internal/broker/readiness_test.go @@ -79,9 +79,8 @@ func TestEachThingMissingNamesItsOwnRemedy(t *testing.T) { // What the move would do is written out rather than summarised, because this is the one step with // nothing to inspect afterwards — so reading it is the last chance to disagree. -func TestWhatMovesNamesEveryMachineAndSaysTheOldBrokerStays(t *testing.T) { +func TestWhatMovesNamesEveryMachineAndEndsWithTheOldBrokerGoing(t *testing.T) { r := aMeshReadyToMove() - r.OldBusHasOtherClients = true steps := strings.Join(WhatMoves(r), "\n") for _, want := range []string{"anchor", "laptop", "user list", "module runtime"} { @@ -89,9 +88,11 @@ func TestWhatMovesNamesEveryMachineAndSaysTheOldBrokerStays(t *testing.T) { t.Errorf("the plan does not mention %q:\n%s", want, steps) } } - // Said explicitly, so nobody reads the move as switching the old broker off — it stays serving - // whatever else uses it, and that is a decision already taken. - if !strings.Contains(steps, "not its retirement") { - t.Errorf("the plan does not say the old broker stays:\n%s", steps) + // Said explicitly, and last: AMQP is not a provision (novox/hq ADR 0131), so the move ends with + // the old broker's module unassigned, not left behind as a second bus. An earlier version of this + // test pinned the opposite, under a record 0131 superseded. + lines := WhatMoves(r) + if last := lines[len(lines)-1]; !strings.Contains(last, "unassign the old broker") { + t.Errorf("the plan does not end with the old broker going:\n%s", steps) } }