The mesh's four streams, asserted on every start
Task 1.4. The foundation set only — a seat's streams come at registration and a module's consumers at assignment, neither of which has happened at genesis (ADR 0118). Asserted rather than created: a stream that was deleted, or a mesh raised from a backup, must converge rather than run without the guarantee its messages assume. Two things the definitions have to get right, both tested: - CONTROL names its subjects instead of taking mesh.control.>, because heartbeats live under that prefix and a stream of them competes for retention with the messages that matter - EVENTS filters on the event token, which is why that token exists; a filter over a module's whole namespace would persist every tool call Overlapping filters are refused where the set is written: NATS accepts two streams matching one subject and stores the message twice under two retentions, which nothing reports. Adds nats.go as a dependency; it pulled golang.org/x/* forward. Full suite green.
This commit is contained in:
+31
-13
@@ -41,6 +41,7 @@ type Seat struct {
|
||||
Name string
|
||||
Accepts []string
|
||||
Emits []string
|
||||
Serves []string
|
||||
Versions []string // protocol versions served beside the current one; empty for v1 only
|
||||
}
|
||||
|
||||
@@ -149,10 +150,8 @@ func PermissionsFor(p Principal) (Permissions, error) {
|
||||
// else may publish into it, so an event's source is a fact the server enforces rather
|
||||
// than a claim in the body (design 29 §2).
|
||||
own := "mesh.mod." + p.Module
|
||||
if len(p.Emits) > 0 {
|
||||
for _, e := range p.Emits {
|
||||
pub = append(pub, own+"."+e)
|
||||
}
|
||||
for _, e := range p.Emits {
|
||||
pub = append(pub, own+".event."+e)
|
||||
}
|
||||
for _, t := range p.Serves {
|
||||
sub = append(sub, own+".tool."+t)
|
||||
@@ -161,16 +160,24 @@ func PermissionsFor(p Principal) (Permissions, error) {
|
||||
// 2. What it consumes, by the emitter's own subject — an event is addressed to its
|
||||
// emitter, because the emitter's identity is the meaning (ADR 0118).
|
||||
for _, c := range p.Consumes {
|
||||
sub = append(sub, "mesh.mod."+c)
|
||||
emitter, event, ok := strings.Cut(c, ".")
|
||||
if !ok {
|
||||
return Permissions{}, fmt.Errorf(
|
||||
"%q does not name an emitter and an event: a consumed event is <module>.<event>", c)
|
||||
}
|
||||
sub = append(sub, "mesh.mod."+emitter+".event."+event)
|
||||
}
|
||||
|
||||
// 3. Seats it holds: full participation.
|
||||
for _, s := range p.Holds {
|
||||
for _, a := range s.Accepts {
|
||||
sub = append(sub, seatSubject(s, a))
|
||||
sub = append(sub, seatSubject(s, "accept", a))
|
||||
}
|
||||
for _, e := range s.Emits {
|
||||
pub = append(pub, seatSubject(s, e))
|
||||
pub = append(pub, seatSubject(s, "event", e))
|
||||
}
|
||||
for _, t := range s.Serves {
|
||||
sub = append(sub, seatSubject(s, "tool", t))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,7 +186,10 @@ func PermissionsFor(p Principal) (Permissions, error) {
|
||||
// events and lie about outcomes (design 29 §2).
|
||||
for _, s := range p.Uses {
|
||||
for _, a := range s.Accepts {
|
||||
pub = append(pub, seatSubject(s, a))
|
||||
pub = append(pub, seatSubject(s, "accept", a))
|
||||
}
|
||||
for _, t := range s.Serves {
|
||||
pub = append(pub, seatSubject(s, "tool", t))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -207,11 +217,19 @@ func PermissionsFor(p Principal) (Permissions, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// seatSubject places a seat's verb. A seat serving more than its current protocol version carries
|
||||
// the version as a token (design 29 §8): the seat stays one role, and v1 and v2 run beside each
|
||||
// other until nothing is bound to the old one.
|
||||
func seatSubject(s Seat, verb string) string {
|
||||
return "mesh.seat." + s.Name + "." + verb
|
||||
// seatSubject places a seat's verb under the kind of traffic it is.
|
||||
//
|
||||
// **The kind token is load-bearing, not decoration.** A stream is defined by a subject filter, so
|
||||
// without it a stream over a seat or a module's namespace would capture that namespace's *tool*
|
||||
// traffic too — and a tool call must never be persisted (design 25 §3: tools stay on core NATS).
|
||||
// Found while defining the streams: the first draft of design 29 had one namespace per module
|
||||
// with no kind, which reads well and cannot be filtered.
|
||||
//
|
||||
// A seat serving more than its current protocol version carries the version as a token
|
||||
// (design 29 §8): the seat stays one role, and v1 and v2 run beside each other until nothing is
|
||||
// bound to the old one.
|
||||
func seatSubject(s Seat, kind, verb string) string {
|
||||
return "mesh.seat." + s.Name + "." + kind + "." + verb
|
||||
}
|
||||
|
||||
// consumerName is the durable consumer the controller derives for this principal. It is here
|
||||
|
||||
Reference in New Issue
Block a user