diff --git a/modules/mesh-grants/internal/buscompose/buscompose_test.go b/modules/mesh-grants/internal/buscompose/buscompose_test.go index 29a8307a..1b9cbdc8 100644 --- a/modules/mesh-grants/internal/buscompose/buscompose_test.go +++ b/modules/mesh-grants/internal/buscompose/buscompose_test.go @@ -1141,6 +1141,10 @@ func theBusModulesProcessKeepsBusState(t *testing.T) { perms[u.Username()] = p } keeper := perms["a.nats.keeper"] + // A key-value bucket's create asks the account's information first (the client library's CreateKeyValue). + if !MayPublish(keeper, "$JS.API.INFO") { + t.Errorf("the bus module's process may not read its account's information, which every bucket create asks first") + } for _, u := range []string{"a.nats.keeper", "a.nats.other", "a.mod-x.sneak", "a.node-tools", "a.nats.nats-tools", "a.admin-ish"} { if _, ok := perms[u]; !ok { t.Fatalf("no user %s composed: %v", u, slices.Collect(maps.Keys(perms))) diff --git a/modules/mesh-grants/internal/buscompose/busstate.go b/modules/mesh-grants/internal/buscompose/busstate.go index 6d964df6..47459b74 100644 --- a/modules/mesh-grants/internal/buscompose/busstate.go +++ b/modules/mesh-grants/internal/buscompose/busstate.go @@ -168,9 +168,12 @@ func BusStateNames(asked map[string][]catalogue.BusStateEntry, bound map[string] // update, information, deletion and snapshot with the acknowledgement of the snapshot's chunks (ADR 0626 §1; the server // waits on `$JS.SNAPSHOT.ACK..…` past its window), and the stream list it finds orphans in. Never a consumer of // one, nor any stream's own subjects: a snapshot carries an entry's data to the keeper's inbox alone, and the keeper -// hears no message of it (ADR 0626 §1, restricted streams included). +// hears no message of it (ADR 0626 §1, restricted streams included). And the account's own information +// ($JS.API.INFO): the client library asks it before every key-value bucket it creates, so without it each create times +// out on a refused publish (the keeper's KV_distribution_* and KV_gitea_*, 2026-11-08); it reads the account's limits and +// usage, never a stream's data. func busStateKeeperGrants(names []string) []string { - out := []string{"$JS.API.STREAM.NAMES", "$JS.API.STREAM.LIST"} + out := []string{"$JS.API.INFO", "$JS.API.STREAM.NAMES", "$JS.API.STREAM.LIST"} for _, n := range names { for _, op := range []string{"CREATE", "UPDATE", "INFO", "DELETE", "SNAPSHOT"} { out = append(out, "$JS.API.STREAM."+op+"."+n)