The bus on NATS: both transports behind seams, and the rollout switch #87

Merged
jschoubben merged 40 commits from feat/nats-genesis into main 2026-09-27 17:36:41 +00:00
Showing only changes of commit 112cbd294d - Show all commits
+21 -6
View File
@@ -37,8 +37,13 @@ type Stream struct {
Name string
Subjects []string
Retention Retention
// MaxAge in seconds, zero for unbounded.
// MaxAge in seconds, zero for unbounded. Per stream — JetStream has no per-subject age,
// which is why differing retention between modules would mean a stream each.
MaxAge int
// MaxMsgsPerSubject caps each subject independently, so one noisy emitter cannot push
// another's events out of a shared stream. Verified: with a cap of 3, ten messages on one
// subject and one on another leave four in the stream, not three.
MaxMsgsPerSubject int
// Why is carried into the assertion so an operator reading the server's own state finds the
// reason there, rather than only in a repository they may not have.
Why string
@@ -82,7 +87,10 @@ func MeshStreams() []Stream {
Subjects: []string{"mesh.mod.*.event.>"},
Retention: RetentionLimits,
MaxAge: 7 * 24 * 60 * 60,
Why: "a subscriber that was down catches up; tool traffic under the same prefix is excluded by the event token",
MaxMsgsPerSubject: 10000,
Why: "a subscriber that was down catches up; tool traffic under the same prefix is " +
"excluded by the event token; per-subject caps keep a noisy emitter from " +
"evicting a quiet one without splitting the stream",
},
}
}
@@ -113,10 +121,17 @@ func AssertMeshStreams(a Asserter) error {
// Overlaps reports subject filters claimed by more than one stream.
//
// Two streams matching one subject is not a warning in NATS; it is accepted, and the message is
// stored twice under two retentions. For the mesh that would mean a declaration kept as both
// state and a work queue, acknowledged in one and lingering in the other — a divergence nothing
// reports and nobody would think to look for. So it is refused here, where the set is written.
// **Corrected against the server**: an earlier version of this comment said NATS accepts
// overlapping streams and stores the message twice. It does not — it refuses the second stream
// with "subjects overlap with an existing stream" (verified against nats-server 2.10). The check
// still earns its place, for a different reason: the server's refusal arrives when the controller
// is applying, naming one stream, at a moment when the mesh is half-configured. This one arrives
// where the set is written, names both, and cannot reach a running mesh.
//
// It also decides a design question. Because overlap is refused rather than merged, a shared
// EVENTS stream and a per-module stream cannot coexist — the module's would be refused — so
// "one stream for most, its own for a module that wants different retention" is not an option
// the server allows. It is all of one or all of the other.
func Overlaps() []string {
seen := map[string]string{}
var clashes []string