Files
mesh-tools/node-tools/internal/runtime/gate_approvals_test.go
T
jschoubben 9fcb14e741 A3h item 1: the tool runner serves a held seat's approvals from its gate's record
The seat verb approvals is the gate's own, served wherever a marked verb is: the runner answers it by caller from the
calls its gate holds, with no bundle naming it; a marked verb already reaches the gate by caller. Tested with zsh's
node-login-shell execute as the case.

Builds: design 47 G §677, §697; lead answers A-264, A-265, A-266, A-271

Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 10:21:21 +01:00

80 lines
3.4 KiB
Go

package runtime
import (
"encoding/json"
"testing"
"git.novox.be/novox/mesh-sdk/go/approval"
"git.novox.be/novox/mesh-sdk/go/seatdef"
"github.com/novox/mesh-tools/node-tools/internal/bus"
mt "github.com/novox/mesh-tools/node-tools/internal/meshtest"
)
// gateHolding is the runner's gate as far as the runtime reaches it: it records the calls it is run for and answers
// `approvals` from them.
type gateHolding struct{ calls []approval.Call }
func (g *gateHolding) Unserved(string, string, []string) []string { return nil }
func (g *gateHolding) Run(c approval.Call, run func() (any, error)) (any, error) {
g.calls = append(g.calls, c)
return run()
}
func (g *gateHolding) Approvals(caller string) (approval.Approvals, error) {
out := approval.Approvals{Calls: []approval.ListedCall{}, Acts: []approval.ListedAct{}, Claims: []string{}}
for i, c := range g.calls {
l := approval.ListedCall{Call: string(rune('a' + i)), Caller: c.Caller, Verb: c.Seat + "." + c.Name, State: "waiting"}
if caller == c.Caller {
l.Arguments = c.Args
}
out.Calls = append(out.Calls, l)
}
return out, nil
}
// **The tool runner answers a held seat's marked verb by caller and serves the seat's `approvals`** (design 47 G §677; lead
// answers A-265, A-271), with zsh's node-login-shell `execute` as the case: the marked verb reaches the gate with the caller
// its subject names, and `approvals` is answered from the runner's own record of the calls its gate holds, with the
// arguments for the call's own caller only, though no bundle names the verb.
func TestTheRunnerServesApprovalsForTheSeatsItsBundlesHold(t *testing.T) {
mesh := mt.New(t)
mesh.Issue(t, mt.MembershipOf("zsh", "anchor", true, map[string][]string{"node-login-shell": {"execute", seatdef.ApprovalsVerb}}))
nodeTools := connect(t, "node-tools", "anchor")
gate := &gateHolding{}
defined := WithSeatDefinitions(func() map[string]seatdef.Definition {
return map[string]seatdef.Definition{"node-login-shell": {Seat: "node-login-shell", Verbs: []seatdef.Verb{
{Name: "execute", NeedsApproval: true}, {Name: seatdef.ApprovalsVerb}}}}
})
stop, err := Run(nodeTools, []Served{{"zsh", []string{mt.Fixture("login-shell-execute.py")}}}, nil, func(string, ...any) {}, defined, WithGate(gate))
if err != nil {
t.Fatal(err)
}
defer stop()
asker, err := bus.Connect(bus.Credential{URL: mt.URL(t), Module: "node-tools", Node: "shanks", User: "shanks.node-tools", Password: "unused"})
if err != nil {
t.Fatal(err)
}
asker.Logf = func(string, ...any) {}
defer asker.Close()
mt.Until(t, func() error {
_, err := asker.Ask("seat:node-login-shell.execute@anchor", map[string]any{"command": "ls"}, "")
return err
})
if len(gate.calls) != 1 || gate.calls[0].Seat != "node-login-shell" || gate.calls[0].Name != "execute" || gate.calls[0].Caller != "shanks.node-tools" {
t.Fatalf("the marked verb reached the gate as %+v, want the seat's execute by shanks.node-tools", gate.calls)
}
var got approval.Approvals
mt.Until(t, func() error {
answered, err := asker.Ask("seat:node-login-shell.approvals@anchor", map[string]any{}, "")
if err == nil {
err = json.Unmarshal(answered.Result, &got)
}
return err
})
if len(got.Calls) != 1 || got.Calls[0].Caller != "shanks.node-tools" || got.Calls[0].Verb != "node-login-shell.execute" ||
string(got.Calls[0].Arguments) != `{"command":"ls"}` {
t.Fatalf("approvals answered %+v, want the one call the gate holds, with its caller's arguments", got)
}
}