diff --git a/03-DESIGN/01-to-be/29-what-a-module-declares.md b/03-DESIGN/01-to-be/29-what-a-module-declares.md index 1351bbe..77aee42 100644 --- a/03-DESIGN/01-to-be/29-what-a-module-declares.md +++ b/03-DESIGN/01-to-be/29-what-a-module-declares.md @@ -103,6 +103,28 @@ seat: telegram-sender If each consumer could tune it, the mesh's durability would be an emergent property of whichever manifest was edited last. +**Why a module's own events do not carry their own retention, though the same rule would allow +it.** A seat owns its namespace and gets a stream of its own, so it can say. A module's events +share one `EVENTS` stream, and three facts about JetStream decide that they must: + +- **Storage is not a property of a subject.** A subject is only an address; a *stream* is a + separate object that captures subjects matching a filter. So "this topic is durable" is always + really "some stream covers it", and something has to create that stream. +- **Overlapping streams are refused, not merged.** Verified against the server: a per-module + stream beside a shared `mesh.mod.*.event.>` is rejected with *subjects overlap with an existing + stream*. So "one stream by default, its own for a module that wants different retention" is not + available — it is all of one or all of the other, and a filter cannot express an exception + either. +- **A stream per module breaks cross-module consumption.** An audit logger consuming every + module's events is one consumer on one stream today; with a stream each it becomes one consumer + per module, created and destroyed as modules come and go. + +So: one stream, and **per-subject caps** for the fairness that actually matters — a noisy emitter +cannot evict a quiet one, which is verified (a cap of three, ten messages on one subject and one +on another, leaves four). What is genuinely unavailable is a different *age* per module, because +JetStream ages per stream and not per subject. A module that truly needs its own retention has a +way to say so: declare a seat, which owns its namespace and gets its own stream. + **Scope gives per-node workers without a new concept.** A module running on three nodes that each need their own queue declares a node-scoped seat: one holder per node, three queues, same machinery. Mesh-scoped and node-scoped seats already exist; here they do the work of "one shared