§556: a reader of a stream kept last per subject is granted its ordered consumer

Builds: design 47 G §556 (G-20 A-4)

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
This commit is contained in:
2026-11-11 21:37:12 +01:00
parent cad384d483
commit 97e990c8f8
2 changed files with 31 additions and 0 deletions
@@ -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
}
@@ -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)
}
}