A-280: the grants part lists a module's finding-events stream in its membership
For a module that requires finding-events and is bound, the membership lists the stream its binding names, the subject
mesh.bus.<module>.finding-events.{event}.<node> and the finding events, so the tool runner carrying a stdio module (the
enrolment) says an emitted finding event there; the test holds the enrolment's membership.
Builds: design 47 G A-280, A-274, §529
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
This commit is contained in:
@@ -22,6 +22,7 @@ import (
|
||||
"git.novox.be/novox/mesh-sdk/go/announce"
|
||||
sdkbc "git.novox.be/novox/mesh-sdk/go/buscompose"
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
"git.novox.be/novox/mesh-sdk/go/membership"
|
||||
"git.novox.be/novox/mesh-sdk/go/seatdef"
|
||||
"github.com/nats-io/nats.go/jetstream"
|
||||
)
|
||||
@@ -916,6 +917,22 @@ func healthChecks(t *testing.T) {
|
||||
MayPublish(perms, c.subject), c.may)
|
||||
}
|
||||
}
|
||||
// Lead answer A-280 (A-274, §529): the enrolment's membership lists its finding-events stream under its binding's subject
|
||||
// with the machine last, and the finding events, so the tool runner carrying it says them there and is granted them.
|
||||
enrolment := Declared{Module: "mesh-enrolment", FindingEvents: []string{"finding-written.*", "finding-cleared.*", "findings-stated.*"},
|
||||
Bindings: []membership.BusBinding{{Local: FindingEventsLocal, Storage: catalogue.StorageStream, Template: catalogue.TemplateEvents,
|
||||
Stream: "mesh-enrolment_finding-events", Subject: "mesh.bus.mesh-enrolment.finding-events"}}}
|
||||
if got, want := MembershipFor("a", enrolment, Assignments{}).Streams, []StreamIssued{{Stream: "mesh-enrolment_finding-events",
|
||||
Events: []string{"finding-written", "finding-cleared", "findings-stated"}, Subject: "mesh.bus.mesh-enrolment.finding-events.{event}.a"}}; !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("the enrolment's membership streams are %+v, want %+v", got, want)
|
||||
}
|
||||
if perms, err := PermissionsFor(Principal{Kind: KindNodeTools, Node: "a", Carries: []Declared{enrolment}}); err != nil ||
|
||||
!MayPublish(perms, "mesh.bus.mesh-enrolment.finding-events.finding-written.a") {
|
||||
t.Errorf("the tool runner carrying the enrolment may not say its finding-written under its binding (%v)", err)
|
||||
}
|
||||
if got := MembershipFor("a", Declared{Module: "mesh-enrolment", FindingEvents: enrolment.FindingEvents}, Assignments{}).Streams; len(got) != 0 {
|
||||
t.Errorf("a finding-events entry not yet bound is listed: %+v", got)
|
||||
}
|
||||
// ADR 0618 §4: the node-engine says each of its seat's events with its own machine as the last token, and a watcher hears
|
||||
// that event from every machine, its consumer filtered the same way; a flat event of a node seat a module holds is
|
||||
// heard flat. One rule (machineEvent) places both sides.
|
||||
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
)
|
||||
|
||||
// What the mesh issues an assignment to serve and to reach (novox/hq ADR 0160).
|
||||
@@ -62,7 +64,7 @@ func MembershipFor(node string, d Declared, where Assignments) Membership {
|
||||
// both, so the composed side and the pass's agree.
|
||||
m.Bindings = d.Bindings
|
||||
m.Logs = append(logsIssuedFor(d), readLogsIssuedFor(withReadLogs(d, where.Logs))...)
|
||||
m.Streams = streamsIssuedFor(d)
|
||||
m.Streams = append(streamsIssuedFor(d), findingEventsIssued(d, node)...)
|
||||
m.Hears = HeardIssuedFor(node, d)
|
||||
t := SeatTrafficOf(d.Module, d.Holds, d.Uses, d.Watches)
|
||||
for _, s := range append(append([]Seat{}, d.Uses...), d.Watches...) {
|
||||
@@ -178,3 +180,26 @@ func kindListed(list []KindHeld, k KindHeld) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// findingEventsIssued is the module's `finding-events` stream as its membership lists it (design 47 G §529, A-274, lead
|
||||
// answer A-280): the stream its binding names, its subject `mesh.bus.<module>.finding-events.{event}.<node>` and the
|
||||
// finding events, so the tool runner says an emitted finding event there as on the first stream that carries it. None
|
||||
// before the bus module bound it.
|
||||
func findingEventsIssued(d Declared, node string) []StreamIssued {
|
||||
if len(d.FindingEvents) == 0 || !safeSubject.MatchString(node) {
|
||||
return nil
|
||||
}
|
||||
for _, b := range d.Bindings {
|
||||
if b.Local != FindingEventsLocal || b.Storage != catalogue.StorageStream || b.Stream == "" ||
|
||||
b.Subject != "mesh.bus."+d.Module+"."+FindingEventsLocal {
|
||||
continue
|
||||
}
|
||||
events := make([]string, 0, len(d.FindingEvents))
|
||||
for _, e := range d.FindingEvents {
|
||||
// The machine is the subject's last token, so an event is named without it.
|
||||
events = append(events, strings.TrimSuffix(e, ".*"))
|
||||
}
|
||||
return []StreamIssued{{Stream: b.Stream, Events: events, Subject: b.Subject + ".{event}." + node}}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -122,6 +122,9 @@ type Declared struct {
|
||||
// part from the bus module's `created`: the module's own account and the tool runner carrying it are granted each
|
||||
// entry's subjects (boundOf, issue 1966), in the same pass that writes the binding into its membership.
|
||||
Bindings []membership.BusBinding
|
||||
// FindingEvents are the events its `finding-events` entry carries (an `events` template it requires, design 47 G §529),
|
||||
// as its manifest names them: the membership lists that stream under its binding's subject (lead answer A-280).
|
||||
FindingEvents []string
|
||||
}
|
||||
|
||||
// ProcessPrincipal is one process of a module that declares bus (novox/hq ADR 0436 §1, §2), as the broker
|
||||
|
||||
@@ -174,6 +174,8 @@ func declaredFor(m catalogue.Manifest, seats map[string]catalogue.SeatDeclaratio
|
||||
Objects: objectsOf(m),
|
||||
// The ledgers it asks for in `requires` (issue 1966 D5): each assignment gets a ledger account.
|
||||
Ledgers: ledgersOf(m),
|
||||
// The events of its finding stream asked for in `requires` (lead answer A-280).
|
||||
FindingEvents: findingEventsOf(m),
|
||||
}
|
||||
d.Loads = loadsABundle(m)
|
||||
// Its marked tools (design 46 approval mark §1, §6): a name its `needs-approval` gives that it serves as a tool.
|
||||
@@ -712,3 +714,14 @@ func catchUpsOf(m catalogue.Manifest) []string {
|
||||
}
|
||||
return tools
|
||||
}
|
||||
|
||||
// findingEventsOf are the events of m's `finding-events` entry, an `events` template it requires (design 47 G §529; lead
|
||||
// answer A-280), none where it asks for none.
|
||||
func findingEventsOf(m catalogue.Manifest) []string {
|
||||
for _, e := range m.BusState {
|
||||
if e.Name == buscompose.FindingEventsLocal && e.Storage == catalogue.StorageStream && e.Template == catalogue.TemplateEvents {
|
||||
return append([]string(nil), e.Events...)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user