From 8b4134646606ef25da80eb62226c4f63c24cee1f Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 11 Nov 2026 21:46:19 +0100 Subject: [PATCH] =?UTF-8?q?GR4=20(=C2=A7386,=20=C2=A7644):=20the=20grants?= =?UTF-8?q?=20part=20tells=20catalogue.Hearing=20which=20events=20are=20de?= =?UTF-8?q?fined?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A seat definition's events of the seat list, and mesh-sdk's four finding events (views.FindingSubjects), so a `*` emitter before one reaches each declaring module's own stream; any other `*` entry stays the shared stream's. Builds: design 47 G §386, §474, §644 Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ --- .../internal/grants/defined_test.go | 27 +++++++++++++++++++ modules/mesh-grants/internal/grants/inputs.go | 5 ++++ modules/mesh-grants/internal/grants/kept.go | 19 +++++++++++++ .../mesh-grants/internal/grants/records.go | 6 ++--- 4 files changed, 54 insertions(+), 3 deletions(-) create mode 100644 modules/mesh-grants/internal/grants/defined_test.go diff --git a/modules/mesh-grants/internal/grants/defined_test.go b/modules/mesh-grants/internal/grants/defined_test.go new file mode 100644 index 00000000..aafa7126 --- /dev/null +++ b/modules/mesh-grants/internal/grants/defined_test.go @@ -0,0 +1,27 @@ +package grants + +import ( + "slices" + "testing" + + "git.novox.be/novox/mesh-sdk/go/catalogue" +) + +// Design 47 G §386, §474, §644: a `*` emitter before one of mesh-sdk's finding events reaches each declaring module's own +// stream, with no seat list read; one before an event no definition names stays the shared stream's. +func TestAStarEmitterBeforeAFindingEventReachesEachDeclaringStream(t *testing.T) { + shelf := map[string]catalogue.Manifest{ + "a": {Module: "a", Streams: []catalogue.StreamDeclaration{{Name: "a-findings", Events: []string{"finding-written"}}}}, + "b": {Module: "b", Streams: []catalogue.StreamDeclaration{{Name: "b-usage", Events: []string{"usage"}}}}, + } + none := func(string, string) []any { return nil } + defined := func(e string) bool { return definedEventsOf(nil)[e] } + keeper := catalogue.Manifest{Module: "keeper", Consumes: []string{"*.finding-written", "*.usage"}} + hears, touches, onEvents := hearsOf(keeper, shelf, "ace", none, keeper.Consumes, defined) + if !touches || len(hears) != 1 || hears[0].Emitter != "a" || !slices.Equal(onEvents, []string{"*.finding-written", "*.usage"}) { + t.Fatalf("heard %+v (touches %v, on EVENTS %v)", hears, touches, onEvents) + } + if hears, _, _ := hearsOf(keeper, shelf, "ace", none, keeper.Consumes, nil); len(hears) != 0 { + t.Fatalf("with no event defined, heard %+v", hears) + } +} diff --git a/modules/mesh-grants/internal/grants/inputs.go b/modules/mesh-grants/internal/grants/inputs.go index 5f6fc1e4..9dce0414 100644 --- a/modules/mesh-grants/internal/grants/inputs.go +++ b/modules/mesh-grants/internal/grants/inputs.go @@ -190,6 +190,9 @@ type Inputs struct { // EventSubjects are, by seat, the subjects of its events that carry parts, by the event's local name, as the definitions // give them: where a node-scoped seat's event names its machine (buscompose.Seat.EventSubjects). EventSubjects map[string]map[string]string + // Defined are the events a seat definition of the seat list defines, by local name, and mesh-sdk's finding events: the + // events a `*` emitter before them reaches each declaring module's own stream for (design 47 G §386, §474, §644). + Defined map[string]bool // SeatListUnread says the seat list has not been read (the gate says so, seatlist.Reader.MayAct with no seat): only the // bootstrapped seats compose, so the pass keeps what the bus loaded (keeploaded.go). SeatListUnread bool @@ -353,9 +356,11 @@ func ReadInputs(r Reader, cache *Manifests) Inputs { NoFingerprint: map[string]string{}, Running: map[string]catalogue.Manifest{}, Had: map[string]catalogue.Manifest{}, HadFailed: map[string]string{}, Values: map[string]any{}, Gaps: append([]Gap(nil), standingGaps...)} in.SeatListUnread = gate != nil && !gate.MayAct() + in.Defined = definedEventsOf(nil) if d, ok := gate.(SeatDefinitions); ok && !in.SeatListUnread { in.Kept = buscompose.KeptEventsOf(d.Definitions()) in.EventSubjects = eventSubjectsOf(d.Definitions()) + in.Defined = definedEventsOf(d.Definitions()) } nodes := map[string]bool{} // **The seat's assignments alone places modules** (novox/hq ADR 0549 §5, ADR 0567 §9; issue 774): the controller's diff --git a/modules/mesh-grants/internal/grants/kept.go b/modules/mesh-grants/internal/grants/kept.go index 4f320bc5..9c0a03ad 100644 --- a/modules/mesh-grants/internal/grants/kept.go +++ b/modules/mesh-grants/internal/grants/kept.go @@ -4,8 +4,10 @@ import ( "errors" "fmt" "slices" + "strings" "git.novox.be/novox/mesh-sdk/go/seatdef" + "git.novox.be/novox/mesh-sdk/go/views" "mesh-grants/internal/buscompose" ) @@ -103,6 +105,23 @@ func eventSubjectsOf(defs map[string]seatdef.Definition) map[string]map[string]s return out } +// definedEventsOf is the events a `*` emitter before them may reach (catalogue.Hearing's defined, design 47 G §386, §474, +// §644): each event, by its local name, of a seat definition of the seat list, and mesh-sdk's four finding events, which +// views.FindingSubjects names and which are defined with or without the list read. +func definedEventsOf(defs map[string]seatdef.Definition) map[string]bool { + out := map[string]bool{} + for _, d := range defs { + for _, e := range d.Events { + out[e.Local()] = true + } + } + s := views.FindingSubjects("module", "node") + for _, subject := range []string{s.Written, s.Cleared, s.Stated, s.Asked} { + out[strings.TrimSuffix(strings.TrimPrefix(subject, "mesh.stream.module.event."), ".node")] = true + } + return out +} + // withEventSubjects gives each seat a declaration holds or watches the subjects of its events (machineEvent). func withEventSubjects(subjects map[string]map[string]string, d *buscompose.Declared) { if len(subjects) == 0 { diff --git a/modules/mesh-grants/internal/grants/records.go b/modules/mesh-grants/internal/grants/records.go index 1b1e6bef..15e0fc29 100644 --- a/modules/mesh-grants/internal/grants/records.go +++ b/modules/mesh-grants/internal/grants/records.go @@ -95,7 +95,7 @@ func records(in Inputs, union bool) (buscompose.Records, []Gap) { for _, sr := range catalogue.ReadsGranted(m, here, node) { d.KeyedReads = append(d.KeyedReads, buscompose.KeyedRead{Read: sr.Read, Key: sr.Key}) } - d.Hears, d.HearsAStream, d.Consumes = hearsOf(m, declared, node, values, d.Consumes) + d.Hears, d.HearsAStream, d.Consumes = hearsOf(m, declared, node, values, d.Consumes, func(event string) bool { return in.Defined[event] }) var keptHeard bool d.Watches, d.Consumes, d.Hears, keptHeard = keptOutOfHearing(in.Kept, d.Watches, d.Consumes, d.Hears) d.HearsAStream = d.HearsAStream || keptHeard @@ -457,10 +457,10 @@ func processesOf(m catalogue.Manifest) []buscompose.ProcessPrincipal { } func hearsOf(m catalogue.Manifest, declared map[string]catalogue.Manifest, node string, values func(provider, setting string) []any, - consumes []string) ([]buscompose.Heard, bool, []string) { + consumes []string, defined func(event string) bool) ([]buscompose.Heard, bool, []string) { reading := m reading.Consumes = consumes - parts, touches, onEvents := catalogue.Hearing(reading, declared, node, values, nil) + parts, touches, onEvents := catalogue.Hearing(reading, declared, node, values, defined) var out []buscompose.Heard for _, p := range parts { h := buscompose.Heard{Stream: p.Stream.BusName(), Emitter: p.Emitter, Restricted: p.Stream.Grant != nil}