From 81e76fa485b9c06073489f2ae17fb4d5786b4069 Mon Sep 17 00:00:00 2001 From: jochen Date: Mon, 28 Sep 2026 03:13:57 +0200 Subject: [PATCH] The controller follows the subject it decodes The decoder named the forge's merge subject as the fourth thing followed and the list was three long: every message that fell through to that switch panicked the control plane (2026-09-28). The entry was written and lost between two attempts at the same edit. A test now walks the list; the composed grants and the genesis template carry the subject. --- internal/broker/streams.go | 3 +++ internal/broker/testdata/composed.conf | 2 +- internal/link/receive_nats_test.go | 14 ++++++++++++++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/internal/broker/streams.go b/internal/broker/streams.go index 9b31266..9c6dcf7 100644 --- a/internal/broker/streams.go +++ b/internal/broker/streams.go @@ -175,6 +175,9 @@ var ControllerFollows = []string{ // message on the control branch. Same three audiences, one publish: whoever asked, this, and the // catalogue. seatEventSubject("mesh-build-machine", "built"), + // The forge's merges: what moved a source, so the mesh builds what that source produces + // without anybody telling it (novox/hq 04-ISSUES/131). Appended, because the index is a name. + moduleEventSubject("gitea", "pull.merged"), } // moduleEventSubject is where one module's event lands. The same derivation PermissionsFor uses, so diff --git a/internal/broker/testdata/composed.conf b/internal/broker/testdata/composed.conf index 365f227..1def85c 100644 --- a/internal/broker/testdata/composed.conf +++ b/internal/broker/testdata/composed.conf @@ -25,7 +25,7 @@ accounts { users = [ { user: "controller", password: "$2a$11$cccccccccccccccccccccc", permissions: { publish: { allow: ["$JS.ACK.CONTROL.controller.>", "$JS.ACK.EVENTS.controller.>", "$JS.API.>", "_INBOX.enrol.>", "mesh.control.>", "mesh.node.>", "mesh.seat.mesh-build-machine.accept.>"] } - subscribe: { allow: ["$JS.API.>", "_DELIVER.controller", "_DELIVER.controller.>", "_INBOX.controller.>", "mesh.control.>", "mesh.mod.mesh-catalog.event.catching-up", "mesh.mod.mesh-catalog.event.upgraded", "mesh.seat.mesh-build-machine.event.built"] } + subscribe: { allow: ["$JS.API.>", "_DELIVER.controller", "_DELIVER.controller.>", "_INBOX.controller.>", "mesh.control.>", "mesh.mod.gitea.event.pull.merged", "mesh.mod.mesh-catalog.event.catching-up", "mesh.mod.mesh-catalog.event.upgraded", "mesh.seat.mesh-build-machine.event.built"] } allow_responses: { max: 1, ttl: "1m" } } } { user: "enrol.one", password: "$2a$11$eeeeeeeeeeeeeeeeeeeeee", permissions: { diff --git a/internal/link/receive_nats_test.go b/internal/link/receive_nats_test.go index dda1a34..e7b9a59 100644 --- a/internal/link/receive_nats_test.go +++ b/internal/link/receive_nats_test.go @@ -377,3 +377,17 @@ func TestNatsAReportIsLetGoOnceTheStoreHasBeenGoneTooLong(t *testing.T) { // A merge announcement is not what these tests are about; taken and forgotten. func (t *toldAbout) SourceMoved(context.Context, SourceMoved) error { return nil } + +// Every subject the controller follows decodes to a kind, and decoding never reaches past the +// list: the day the list was three long and the decoder named a fourth, every message panicked +// the control plane (2026-09-28). +func TestEverySubjectTheControllerFollowsDecodesToAKind(t *testing.T) { + for _, subject := range broker.ControllerFollows { + if _, ok := kindOfSubject(subject); !ok { + t.Errorf("%s is followed and decodes to nothing", subject) + } + } + if _, ok := kindOfSubject("mesh.mod.nobody.event.nothing"); ok { + t.Error("a subject nobody follows decoded to a kind") + } +}