diff --git a/internal/broker/membership_test.go b/internal/broker/membership_test.go index 5bb335c7..8f8e8493 100644 --- a/internal/broker/membership_test.go +++ b/internal/broker/membership_test.go @@ -72,6 +72,15 @@ func TestAnAccountMayReadItsOwnMembershipAndNoOthers(t *testing.T) { has(t, perms.Subscribe, "mesh.assignment.anchor.postgres") has(t, perms.Publish, "$JS.API.DIRECT.GET.ASSIGNMENTS.mesh.assignment.anchor.postgres") hasNot(t, perms.Subscribe, "mesh.assignment.>") + hasNot(t, perms.Publish, "mesh.assignment.*.*") + // The grants part alone publishes memberships (ADR 0547 §5), and defines no stream. + grants, err := PermissionsFor(Principal{Kind: KindModule, Node: "anchor", Module: GrantsModule, PasswordHash: "x"}) + if err != nil { + t.Fatal(err) + } + has(t, grants.Publish, "mesh.assignment.*.*") + hasNot(t, grants.Publish, "$JS.API.STREAM.CREATE.>") + hasNot(t, grants.Publish, "mesh.assignment.>") } // The runtime arriving on a machine changes nothing about what each module is issued (to-be 38 WP2): diff --git a/internal/broker/nats.go b/internal/broker/nats.go index 66f66698..6230f12d 100644 --- a/internal/broker/nats.go +++ b/internal/broker/nats.go @@ -915,6 +915,11 @@ func PermissionsFor(p Principal) (Permissions, error) { sub = append(sub, BusConnectSubject, BusDisconnectSubject) pub = append(pub, BusConnzSubject) } + // The grants part alone publishes the modules' memberships into the assignments stream (novox/hq ADR 0547 §5; + // the operator, 2026-10-25 22:31): one per subject, `mesh.assignment..`, and no stream it defines. + if p.Module == GrantsModule { + pub = append(pub, "mesh.assignment.*.*") + } // The view alone reads the build seats' workers for the backlog of asks (issue 1887 A8a). if p.Module == ViewModule { pub = append(pub, BuildBacklogReads()...) @@ -1581,6 +1586,10 @@ func ComposeAccounts(principals []Principal) (string, error) { // from them. No other principal is granted the subjects below, and nothing is a wildcard over them. const WatchmanModule = "mesh-delivery-watchman" +// GrantsModule is the one module whose principal publishes the modules' memberships (novox/hq ADR 0547 §5): the grants +// part, which composes them from the manifests and placement. It defines no stream and is granted no other subject here. +const GrantsModule = "mesh-grants" + // The bus's own events, imported into MESH from the default system account `$SYS` under subjects of MESH's own // (novox/hq ADR 0431 §1, issue 1147): the server says an account's connections opening and closing only to the system // account, and the list of an account's connections only to a request made there. `$SYS` holds no user, so nobody