GR4 (§386, §644): the grants part tells catalogue.Hearing which events are defined

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
This commit is contained in:
2026-11-11 21:46:19 +01:00
parent 95e2427553
commit 8b41346466
4 changed files with 54 additions and 3 deletions
@@ -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)
}
}
@@ -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
@@ -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 {
@@ -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}