A-233 (grants): a hearer of a seat's kept event is granted the ordered consumer of its whole read

As STREAM.INFO is, for every heard SEAT_ stream whatever its keep, so a part hearing an event kept by age
reads it whole from its first message at each start (mesh-sdk process.FromFirst); no manifest word.

Builds: design 47 G A-233, A-189

Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
This commit is contained in:
2026-11-12 06:21:30 +01:00
parent 121d6f24a9
commit 019a7ab9f7
2 changed files with 13 additions and 2 deletions
@@ -273,15 +273,18 @@ 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 strings.HasPrefix(h.Stream, seatdef.KeptStreamPrefix) || h.LastPerSubject {
seatKept := strings.HasPrefix(h.Stream, seatdef.KeptStreamPrefix)
if seatKept || h.LastPerSubject {
// A seat's kept event, and a module's own stream kept last per subject, are heard through process.HearSeatEvents and
// process.HearStream, which reads the stream's own info to see whether it is
// kept last per subject (design 47 G §641, A-19): the info of this stream alone.
out = append(out, "$JS.API.STREAM.INFO."+h.Stream)
}
if h.LastPerSubject {
if seatKept || 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).
// So is every seat's kept event, whatever its keep: one kept by age is read whole from its first message with
// process.FromFirst, with no manifest word (A-233).
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+".*",
@@ -20,6 +20,14 @@ func TestAReaderOfAStreamKeptLastPerSubjectIsGrantedItsOrderedConsumer(t *testin
if slices.Contains(plain, ordered[0]) {
t.Errorf("a reader of another stream is granted an ordered consumer: %v", plain)
}
// A seat's kept event kept by age is read whole from its first message (process.FromFirst, A-233): its ordered consumer too.
byAge := heardGrants(p, []Heard{{Stream: "SEAT_mesh-catalog_version-released", Filters: []string{"f"}}})
for _, w := range []string{"$JS.API.CONSUMER.CREATE.SEAT_mesh-catalog_version-released", "$JS.API.CONSUMER.MSG.NEXT.SEAT_mesh-catalog_version-released.*",
"$JS.API.CONSUMER.INFO.SEAT_mesh-catalog_version-released.*", "$JS.API.CONSUMER.DELETE.SEAT_mesh-catalog_version-released.*"} {
if !slices.Contains(byAge, w) {
t.Errorf("a reader of a seat's event kept by age is not granted %s: %v", w, byAge)
}
}
}
// A reader of a seat's kept event is granted the info of that stream, which process.HearSeatEvents reads to see whether the