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") + } +}