mesh-grants: a node seat's event that names its holder's machine is heard from every machine, by the one rule its holder is granted it by (ADR 0618 §4, issue 774)

A watch of the node-engine's manifest-reads-changed subscribed the flat subject, which no engine says, so its consumer would hear nothing. mesh-sdk is pinned by a local replace to the group's head while developing.

Claude-Session: https://claude.ai/code/session_01JG73KG7AfSdh6FP8VrBqCn
This commit is contained in:
2026-11-04 12:05:58 +01:00
parent ae31b66a9e
commit 4d94b0b9e4
9 changed files with 117 additions and 7 deletions
+2
View File
@@ -20,3 +20,5 @@ require (
golang.org/x/sys v0.48.0 // indirect
golang.org/x/time v0.15.0 // indirect
)
replace git.novox.be/novox/mesh-sdk/go => ../../../0618-sdk/go
-2
View File
@@ -1,5 +1,3 @@
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103221822-85ba0a9a70d8 h1:XEbIVImp6yGjA91rp0iZBoDdL5KfamjOuDLwWvqh0hc=
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103221822-85ba0a9a70d8/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0=
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI=
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
@@ -858,6 +858,8 @@ func healthChecks(t *testing.T) {
{"the holder says no other seat's", selfCheck, "mesh.seat.mesh-conditions.event.condition-raised", false},
{"a machine's event on a node seat is not composed here", Declared{Module: "pause", Holds: []Seat{{Name: "node-power",
Scope: "node", Emits: []string{"paused.*"}}}}, "mesh.seat.node-power.event.paused.b", false},
{"a flat event of a node seat held by a module is said flat", Declared{Module: "pause", Holds: []Seat{{Name: "node-power",
Scope: "node", Emits: []string{"slept"}}}}, "mesh.seat.node-power.event.slept", true},
} {
m := MembershipFor("a", c.d, Placements{})
listed := m.SeatTraffic != nil && slices.ContainsFunc(m.SeatTraffic.Publish, func(p string) bool { return p == c.subject })
@@ -870,6 +872,28 @@ func healthChecks(t *testing.T) {
MayPublish(perms, c.subject), c.may)
}
}
// 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.
engine, err := PermissionsFor(Principal{Kind: KindNode, Node: "ace"})
if err != nil {
t.Fatal(err)
}
watcher := Principal{Kind: KindModule, Node: "novox", Module: "mesh-delivery-assigner", Watches: []Seat{
{Name: EngineSeat, Scope: "node", Emits: []string{"manifest-reads-changed"}, HeldByTheEngine: true},
{Name: "node-power", Scope: "node", Emits: []string{"slept"}}}}
heard, err := PermissionsFor(watcher)
if err != nil {
t.Fatal(err)
}
consumer, _ := ConsumerFor(watcher)
said := "mesh.seat.node-engine.event.manifest-reads-changed.ace"
if !MayPublish(engine, said) || MayPublish(engine, "mesh.seat.node-engine.event.manifest-reads-changed.novox") ||
!MaySubscribe(heard, said) || !MaySubscribe(heard, "mesh.seat.node-power.event.slept") ||
!slices.Contains(consumer.Filters, "mesh.seat.node-engine.event.manifest-reads-changed.*") {
t.Errorf("the engine says %s: %v; the watcher hears it %v, the flat event %v; its consumer filters %v", said,
MayPublish(engine, said), MaySubscribe(heard, said), MaySubscribe(heard, "mesh.seat.node-power.event.slept"), consumer.Filters)
}
seen := map[string]bool{}
for _, u := range users {
name := u.Username()
@@ -51,7 +51,9 @@ func EnginePackagesGrants(node string) []string {
// principal may subscribe this seat's subjects of this node, and a caller is still granted each verb by name.
func engineGrants(p Principal) (pub, sub []string) {
sub = append(sub, "mesh.seat."+EngineSeat+".tool.*."+p.Node)
pub = append(pub, "mesh.seat."+EngineSeat+".event.*."+p.Node)
// Any event of the seat about its own node, by the one rule a watcher of it is composed by (machineEvent).
every, _ := machineEvent(Seat{Name: EngineSeat, Scope: "node", HeldByTheEngine: true}, "*")
pub = append(pub, seatSubject(Seat{Name: EngineSeat}, "event", onMachine(every, p.Node)))
sub = append(sub, announcing(EngineSeat)...)
pub = append(pub, discovering()...)
tp, ts := seatTrafficGrants(SeatTrafficOf(asks.EngineAsker(p.Node), nil, p.Uses, nil))
@@ -73,6 +73,17 @@ func UserListFixtures() []UserListFixture {
},
RootFree: map[string]bool{"ace": true},
}},
// ADR 0618 §4: an event of the node-engine's seat names the engine's machine, so its watcher hears it from every
// machine; a node seat held by a module says its other events flat, and is heard flat.
{Name: "a node seat's events, by machine and flat", Records: Records{
Nodes: []string{"ace", "novox"},
Assigned: map[string][]Declared{
"novox": {{Module: "mesh-delivery-assigner", Watches: []Seat{
{Name: EngineSeat, Scope: "node", Emits: []string{"manifest-reads-changed"}, HeldByTheEngine: true},
{Name: "node-power", Scope: "node", Emits: []string{"slept"}}}}},
"ace": {{Module: "pause", Holds: []Seat{{Name: "node-power", Scope: "node", Emits: []string{"slept", "paused.*"}}}}},
},
}},
}
}
@@ -98,6 +98,9 @@ type Seat struct {
// PulledByKind says each accepted kind is a worker of its own on the seat's queue, filtered to that kind, which
// every holder pulls in the order Accepts lists them: the build seats' (novox/hq ADR 0525 §2, ADR 0536 §4).
PulledByKind bool
// HeldByTheEngine says every node's engine holds the seat on its own account (catalogue.Seat.HeldByTheEngine, novox/hq
// ADR 0563 §1): each of its events names the engine's machine as its last token (machineEvent).
HeldByTheEngine bool
}
// A Principal is one user of the bus. Its permissions are derived from what it declares and
@@ -406,6 +409,25 @@ func NodeLoginsReads(node string) []string {
// holder's machine (novox/hq ADR 0219): `paused.<node>`, the build agent saying whether it takes work.
var perMachineEvents = map[string]bool{"paused.*": true}
// machineEvent is the one rule for where a node-scoped seat's event names its holder's machine as its last token (novox/hq
// ADR 0219, ADR 0618 §4), as a pattern whose last token `*` is that machine, and whether e is such an event: every event
// of a seat the node-engine holds (engineGrants), and each of perMachineEvents of any other node seat. The holder is
// granted the pattern with its own machine in place of `*`, a watcher the pattern, so the two cannot differ.
func machineEvent(s Seat, e string) (string, bool) {
switch {
case s.Scope != "node":
return "", false
case perMachineEvents[e]:
return e, true
case s.HeldByTheEngine:
return e + ".*", true
}
return "", false
}
// onMachine is a machineEvent pattern with node in place of its last `*`.
func onMachine(pattern, node string) string { return strings.TrimSuffix(pattern, "*") + node }
// enrolmentPrefix is the space every enrolling node's user and inbox live under, so the one place the
// controller may answer an enrolment is derived from the same constant the user is named from.
const enrolmentPrefix = "enrol"
@@ -879,6 +901,11 @@ func PermissionsFor(p Principal) (Permissions, error) {
continue // composed by SeatTrafficOf below
}
for _, e := range w.Emits {
// An event that names its holder's machine is heard from every machine (machineEvent): a flat subject would
// match no event the holder says.
if pattern, ok := machineEvent(w, e); ok {
e = pattern
}
sub = append(sub, seatSubject(w, "event", e))
}
}
@@ -921,8 +948,8 @@ func PermissionsFor(p Principal) (Permissions, error) {
// **A machine says its own state and no other's** (novox/hq ADR 0219): on a node-scoped
// seat, an event about the holder itself carries the machine as its last token, and
// each holder is granted its own machine's alone.
if s.Scope == "node" && p.Node != "" && perMachineEvents[e] {
pub = append(pub, seatSubject(s, "event", strings.TrimSuffix(e, "*")+p.Node))
if pattern, ok := machineEvent(s, e); ok && p.Node != "" {
pub = append(pub, seatSubject(s, "event", onMachine(pattern, p.Node)))
continue
}
pub = append(pub, seatSubject(s, "event", e))
@@ -64,7 +64,7 @@ func SeatTrafficOf(module string, holds, uses, watches []Seat) SeatTraffic {
// runtime and the runtime is granted them. A machine's event on a node seat carries the machine, which
// only the holder's own account is composed with, so it is left to that rule.
for _, e := range s.Emits {
if s.Scope == "node" && perMachineEvents[e] {
if _, ok := machineEvent(s, e); ok {
continue
}
t.Publish = append(t.Publish, seatSubject(s, "event", e))
File diff suppressed because one or more lines are too long
@@ -125,7 +125,10 @@ func declaredFor(m catalogue.Manifest, seats map[string]catalogue.SeatDeclaratio
}
}
if s, isASeat := seats[emitter]; isASeat && catalogue.SeatSays(s.Emits, event) {
watches = append(watches, buscompose.Seat{Name: s.Name, Emits: []string{event}})
// Its scope, and whether the node-engine holds it, place an event that names its holder's machine (ADR 0618 §4).
compiled, _ := catalogue.SeatNamed(s.Name)
watches = append(watches, buscompose.Seat{Name: s.Name, Scope: s.Scope, Emits: []string{event},
HeldByTheEngine: compiled.HeldByTheEngine})
continue
}
}