A build is announced under both names on the old bus, or merging breaks the live mesh

Found by asking what merging this would do to the mesh that is actually running — the
only place the question could have been asked, because the tests were green and both
buses were self-consistent.

Moving the build outcome to the role means a catalogue built from the current manifests
listens for the role's name. The catalogue *already running* listens for the module's,
because that is what it was told when it was installed. The two do not meet, so merging
as it stood would have stopped the live mesh's module graph being updated — silently,
since a binding that matches nothing is not an error.

A rename on a live bus needs the publisher and the subscriber to change together, and a
deployment cannot promise which arrives first. So the old bus announces under both names
and the order stops mattering. The module's own name retires with the bus, in step 5's
list; nothing has ever run on the bus being built, so there is no legacy name there and
this doubling has no counterpart.
This commit is contained in:
2026-09-27 17:39:39 +02:00
parent e5007a7daa
commit 5fcde512bc
3 changed files with 64 additions and 2 deletions
+8
View File
@@ -27,6 +27,14 @@ const TheBuildMachine = "mesh-build-machine"
func BuildWork() string { return "mesh.seat." + TheBuildMachine + ".accept.build" }
func BuildOutcome() string { return "mesh.seat." + TheBuildMachine + ".event.built" }
// KeyRoleBuilt is the build outcome under the role's name, on the bus the mesh runs on today.
//
// The same event as KeyModuleBuilt and published beside it, because a catalogue installed before this
// change listens for the module's name and one installed after listens for the role's. Both, until
// this bus retires: a rename needs publisher and subscriber to change together, and a deployment
// cannot promise which arrives first.
const KeyRoleBuilt = "built"
// Builders is how work reaches a build machine and how the outcome comes back.
type Builders interface {
// Submit asks for one build and waits for its outcome.
+20 -2
View File
@@ -161,8 +161,26 @@ func (b *currentBuild) Announce(ctx context.Context, result BuildResult) error {
if result.Failed != "" || result.Commit == "" {
return nil
}
return EmitEvent(ctx, OverCurrent{Channel: b.channel}, KeyModuleBuilt, "builder", b.on,
announcementOf(result))
// **Announced under both names on this bus, for exactly as long as this bus lives.**
//
// A build's outcome belongs to the role now (novox/hq ADR 0121), so a catalogue built from the
// current manifests listens for the role's name. A catalogue that is *already running* listens for
// the module's, because that is what it was told when it was installed. A rename on a live bus
// needs the publisher and the subscriber to change together, and a merge cannot promise that: one
// of them is deployed first, and in that window the graph silently stops being updated — which is
// the failure this whole change was cleaning up after.
//
// So both, and the order stops mattering. The module's own name goes with the bus, in step 5's
// retirement list; nothing has ever run on the bus being built, so there is no legacy name there
// and this doubling has no counterpart.
announced := announcementOf(result)
if err := EmitEvent(ctx, OverCurrent{Channel: b.channel}, KeyModuleBuilt, "builder", b.on,
announced); err != nil {
return err
}
return EmitEvent(ctx, OverCurrent{Channel: b.channel}, KeyRoleBuilt, TheBuildMachine, b.on,
announced)
}
func (b *currentBuild) Done() error { return b.delivery.Ack(false) }
+36
View File
@@ -0,0 +1,36 @@
package link
import (
"strings"
"testing"
)
// **The old bus announces a build under both names, and that is not belt-and-braces.**
//
// A build's outcome belongs to the role now, so a catalogue built from the current manifests listens
// for the role's name — and a catalogue already running listens for the module's, because that is what
// it was told when it was installed. A rename on a live bus needs publisher and subscriber to change
// together, which a deployment cannot promise: one arrives first, and in that window the module graph
// silently stops being updated.
//
// Caught by asking what merging this would do to the mesh that is actually running, which is the only
// place the question could have been asked — the tests were green and both buses were self-consistent.
func TestTheOldBusAnnouncesABuildUnderBothNames(t *testing.T) {
// The routing key a catalogue installed before the change is bound to.
if KeyModuleBuilt != "module.builder.built" {
t.Fatalf("the module's own name is %q; a catalogue already running is bound to the old one",
KeyModuleBuilt)
}
// And the local name a catalogue built from the current manifests declares, which the old bus's
// client turns into `module.<role>.built`.
if KeyRoleBuilt != "built" {
t.Fatalf("the role's event is %q, and a holder emits its verbs bare", KeyRoleBuilt)
}
if TheBuildMachine != "mesh-build-machine" {
t.Fatalf("the role is %q", TheBuildMachine)
}
// The two must differ, or one publish would serve both and this doubling would be pointless.
if strings.HasSuffix(KeyModuleBuilt, "."+TheBuildMachine+"."+KeyRoleBuilt) {
t.Fatal("the two names are the same, so nothing was renamed and this is dead weight")
}
}