diff --git a/internal/broker/nats.go b/internal/broker/nats.go index 17bca40..65d5120 100644 --- a/internal/broker/nats.go +++ b/internal/broker/nats.go @@ -173,8 +173,10 @@ func PermissionsFor(p Principal) (Permissions, error) { switch p.Kind { case KindController: // The controller owns the mesh's own traffic and the streams. It is the only writer of - // stream definitions (design 25 §3), so it alone reaches the JetStream API. - pub = []string{"mesh.control.>", "mesh.node.>", "$JS.API.>"} + // stream definitions (design 25 §3), so it alone reaches the JetStream API — and it alone + // issues memberships (novox/hq ADR 0160), which it publishes into the assignments stream + // after each push; refused by the server on 2026-10-01 until this line named them. + pub = []string{"mesh.control.>", "mesh.node.>", "mesh.assignment.>", "$JS.API.>"} // **And where its consumers deliver.** A push consumer delivers on `_DELIVER.`, // and a client bound to it subscribes exactly that; the server refused it for every // principal the first time one bound a consumer (2026-09-28). Each kind below is granted diff --git a/internal/broker/testdata/composed.conf b/internal/broker/testdata/composed.conf index 10f86b9..623f8c9 100644 --- a/internal/broker/testdata/composed.conf +++ b/internal/broker/testdata/composed.conf @@ -24,7 +24,7 @@ accounts { jetstream: enabled 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.mod.*.tool.>", "mesh.node.>", "mesh.seat.mesh-build-machine.accept.>", "mesh.seat.mesh-controller.event.applied", "mesh.seat.mesh-controller.event.built-before", "mesh.seat.mesh-controller.event.refused"] } + publish: { allow: ["$JS.ACK.CONTROL.controller.>", "$JS.ACK.EVENTS.controller.>", "$JS.API.>", "_INBOX.enrol.>", "mesh.assignment.>", "mesh.control.>", "mesh.mod.*.tool.>", "mesh.node.>", "mesh.seat.mesh-build-machine.accept.>", "mesh.seat.mesh-controller.event.applied", "mesh.seat.mesh-controller.event.built-before", "mesh.seat.mesh-controller.event.refused"] } 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", "mesh.seat.mesh-controller.tool.>"] } allow_responses: { max: 1, ttl: "1m" } } }