Merge pull request 'The move ends with the old broker going, not staying' (#90) from fix/rollout-retires-the-old-broker into main

This commit was merged in pull request #90.
This commit is contained in:
2026-09-27 21:05:47 +00:00
3 changed files with 17 additions and 20 deletions
+5 -6
View File
@@ -25,11 +25,11 @@ import (
// against a mesh that is serving. It answers from records: what is missing, and what would happen. // 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. // `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 // **The old broker goes with the move, and goes last** (novox/hq ADR 0131): AMQP is not a provision,
// else uses it — on this installation, a whole automation layer that has nothing to do with the mesh // so once every machine reports on the new bus its module is unassigned. Only the mesh's own traffic
// ([ADR 0119](../../02-DECISIONS/0119-amqp-is-a-provision-not-the-bus.md)). Only the mesh's own // is what moves, which is why this is survivable at all: what breaks if it goes wrong is the mesh's
// 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 — measured on 2026-09-27, when
// ability to change things, not the services its modules are serving. // a seat emptied mid-change and the control plane looped for two hours while every service stayed up.
const rolloutUsage = "rollout check | rollout --confirm" const rolloutUsage = "rollout check | rollout --confirm"
@@ -105,7 +105,6 @@ func readinessOf(ctx context.Context, inv *inventory.Inventory) (broker.Readines
ModuleCredentialled: map[string]bool{}, ModuleCredentialled: map[string]bool{},
// The old broker keeps its other clients on this installation, and saying so is how the plan // The old broker keeps its other clients on this installation, and saying so is how the plan
// stops reading as a retirement. // stops reading as a retirement.
OldBusHasOtherClients: true,
} }
address, _, err := broker.OnNATS() address, _, err := broker.OnNATS()
+5 -8
View File
@@ -35,10 +35,6 @@ type Readiness struct {
Modules []string Modules []string
// ModuleCredentialled is which of those has one. // ModuleCredentialled is which of those has one.
ModuleCredentialled map[string]bool 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. // 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", out = append(out, fmt.Sprintf("move %d module runtime(s), and confirm each answers",
len(r.Modules))) len(r.Modules)))
} }
if r.OldBusHasOtherClients { // **The old broker goes, and it goes last** (novox/hq ADR 0131). AMQP is not a provision, so once
out = append(out, "leave the old broker running: it stays an ordinary provider of `amqp` for "+ // every machine reports on the new bus nothing of the mesh is left speaking to it, and its module
"whatever else uses it (ADR 0119), and this move is not its retirement") // 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 return out
} }
+7 -6
View File
@@ -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 // 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. // 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 := aMeshReadyToMove()
r.OldBusHasOtherClients = true
steps := strings.Join(WhatMoves(r), "\n") steps := strings.Join(WhatMoves(r), "\n")
for _, want := range []string{"anchor", "laptop", "user list", "module runtime"} { 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) 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 // Said explicitly, and last: AMQP is not a provision (novox/hq ADR 0131), so the move ends with
// whatever else uses it, and that is a decision already taken. // the old broker's module unassigned, not left behind as a second bus. An earlier version of this
if !strings.Contains(steps, "not its retirement") { // test pinned the opposite, under a record 0131 superseded.
t.Errorf("the plan does not say the old broker stays:\n%s", steps) 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)
} }
} }