A node's engine answers its own node's node-engine verbs, says the seat's events and asks the operator as node-engine-<node>, and no other account answers them (issue 774, ADR 0563 §1, §3, §6)

This commit is contained in:
2026-10-28 21:54:04 +01:00
parent 222d605f6f
commit 79231a9e6c
5 changed files with 102 additions and 2 deletions
+62 -1
View File
@@ -55,6 +55,18 @@ func mayBeyondTheControllers(user string) map[string]bool {
VaultSecretChanged: true}
}
// engineBeyond is a node's engine holding its node seat and asking the operator in its own name (novox/hq ADR 0563 §1,
// §3), beyond the controller's on purpose: its own node's verbs, events and discovery, the bus's discovery asked, and
// the router's seat traffic under node-engine-<node>.
func engineBeyond(user, g string) bool {
node, ok := strings.CutPrefix(user, "node.")
if !ok {
return false
}
return strings.HasPrefix(g, "mesh.seat."+EngineSeat+".") && strings.Contains(g, "."+node) ||
strings.HasPrefix(g, "$SRV.") || strings.Contains(g, ".node-engine-"+node)
}
// createsAnAccountsConsumer is the grants part's create and read of a durable consumer an account needs, by its stream
// and name (novox/hq ADR 0561 §6): beyond the controller's on purpose, since the controller made them alone.
func createsAnAccountsConsumer(user, g string) bool {
@@ -114,7 +126,7 @@ func grantsAsTheControllers(t *testing.T, got, want string) {
}
for g := range now {
if !was[g] && !intended[g] && !mayBeyondTheControllers(user)[g] && !createdAdvisory.MatchString(`"`+g+`", `) &&
!createsAnAccountsConsumer(user, g) {
!createsAnAccountsConsumer(user, g) && !engineBeyond(user, g) {
t.Errorf("%s: the copy grants %s beyond the controller's, and no decision names it", user, g)
}
}
@@ -235,6 +247,55 @@ func TestTheCopiedComposerComposesAsTheControllers(t *testing.T) {
t.Error("a user of a seat not pulled by kind reads DEAD_LETTERS")
}
})
// ADR 0563 §1, §3, §6: each node's engine alone answers its own node's node-engine verbs and says its events, any
// runtime may call them, and it asks the operator under node-engine-<node> alone.
t.Run("a node's engine holds its node seat and asks in its own name", func(t *testing.T) {
router := Seat{Name: AsksSeat, Accepts: []string{"ask", "cancel"}, Emits: []string{"decided"},
ByCaller: []string{"ask", "cancel", "decided"}, Records: []string{"messenger_asks"}}
users, err := Users(Records{Nodes: []string{"one", "two"}, Assigned: map[string][]Declared{
"one": {{Module: "messenger", Holds: []Seat{router}, Invokes: []string{"seat:" + EngineSeat + ".root-free"}}},
"two": {{Module: RuntimeModule}, {Module: "shop", Invokes: []string{"*"}}}}})
if err != nil {
t.Fatal(err)
}
for _, u := range users {
perms, err := PermissionsFor(u)
if err != nil {
t.Fatal(err)
}
answers := MaySubscribe(perms, "mesh.seat."+EngineSeat+".tool.root-free.one") ||
MaySubscribe(perms, "mesh.seat."+EngineSeat+".call.root-free.one.messenger~one")
if engineOne := u.Kind == KindNode && u.Node == "one"; answers != engineOne {
t.Errorf("%s answers one's root-free: %v", u.Username(), answers)
}
switch {
case u.Kind == KindNode && u.Node == "one":
for subject, want := range map[string]bool{
"mesh.seat.operator-channel.accept.ask.node-engine-one": true,
"mesh.seat.operator-channel.accept.cancel.node-engine-one": true,
"mesh.seat.operator-channel.accept.ask.node-engine-two": false,
"$JS.API.DIRECT.GET.KV_messenger_asks.$KV.messenger_asks.node-engine-one.hand-over-1": true,
"$JS.API.DIRECT.GET.KV_messenger_asks.$KV.messenger_asks.node-engine-two.hand-over-1": false,
"mesh.seat." + EngineSeat + ".event.done.one": true,
"mesh.seat." + EngineSeat + ".event.done.two": false,
"$SRV.INFO": true,
} {
if got := MayPublish(perms, subject); got != want {
t.Errorf("node one may publish %s: %v, want %v", subject, got, want)
}
}
if !MaySubscribe(perms, "mesh.seat.operator-channel.event.decided.node-engine-one") ||
MaySubscribe(perms, "mesh.seat.operator-channel.event.decided.node-engine-two") ||
!MaySubscribe(perms, "$SRV.INFO."+EngineSeat) || MaySubscribe(perms, "mesh.seat."+EngineSeat+".tool.hand-over.two") {
t.Errorf("node one hears %v", perms.Subscribe)
}
case u.Kind == KindNodeTools || u.Module == "messenger":
if !MayPublish(perms, "mesh.seat."+EngineSeat+".tool.root-free.one") {
t.Errorf("%s cannot call one's root-free", u.Username())
}
}
}
})
// Issue 1126 (ADR 0550 §5): a node's engine asks the vault's `own` in its own name while the vault serves it, and no
// other node's.
t.Run("a node asks the vault its own secrets", func(t *testing.T) {