diff --git a/modules/mesh-grants/internal/buscompose/modstreams.go b/modules/mesh-grants/internal/buscompose/modstreams.go index 64e0cec1..af945c1b 100644 --- a/modules/mesh-grants/internal/buscompose/modstreams.go +++ b/modules/mesh-grants/internal/buscompose/modstreams.go @@ -260,6 +260,14 @@ func heardGrants(p Principal, hears []Heard) []string { "$JS.API.CONSUMER.INFO."+h.Stream+"."+durable, "$JS.API.CONSUMER.MSG.NEXT."+h.Stream+"."+durable, "$JS.ACK."+h.Stream+"."+durable+".>") + if h.LastPerSubject { + // A stream kept last per subject is read whole at each start through an ordered consumer, made for that one read + // and with no durable name: its creation, its next message, its info and the deletion of its own (design 47 G §556, A-4). + out = append(out, + "$JS.API.CONSUMER.CREATE."+h.Stream, "$JS.API.CONSUMER.CREATE."+h.Stream+".>", + "$JS.API.CONSUMER.MSG.NEXT."+h.Stream+".*", "$JS.API.CONSUMER.INFO."+h.Stream+".*", + "$JS.API.CONSUMER.DELETE."+h.Stream+".*") + } } return out } diff --git a/modules/mesh-grants/internal/buscompose/orderedread_test.go b/modules/mesh-grants/internal/buscompose/orderedread_test.go new file mode 100644 index 00000000..fc61c48f --- /dev/null +++ b/modules/mesh-grants/internal/buscompose/orderedread_test.go @@ -0,0 +1,23 @@ +package buscompose + +import ( + "slices" + "testing" +) + +// A reader of a stream kept last per subject is granted what an ordered consumer of that stream uses, and a reader of +// another stream only its own durable (design 47 G §556, A-4). +func TestAReaderOfAStreamKeptLastPerSubjectIsGrantedItsOrderedConsumer(t *testing.T) { + p := Principal{Module: "m", Node: "a"} + ordered := []string{"$JS.API.CONSUMER.CREATE.S", "$JS.API.CONSUMER.MSG.NEXT.S.*", "$JS.API.CONSUMER.INFO.S.*", "$JS.API.CONSUMER.DELETE.S.*"} + last := heardGrants(p, []Heard{{Stream: "S", Filters: []string{"f"}, LastPerSubject: true}}) + for _, w := range ordered { + if !slices.Contains(last, w) { + t.Errorf("a last-per-subject reader is not granted %s: %v", w, last) + } + } + plain := heardGrants(p, []Heard{{Stream: "S", Filters: []string{"f"}}}) + if slices.Contains(plain, ordered[0]) { + t.Errorf("a reader of another stream is granted an ordered consumer: %v", plain) + } +}