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
This commit is contained in:
@@ -18,6 +18,8 @@ type Gate interface {
|
||||
Unserved(module, fingerprint string, registered []string) []string
|
||||
// Run is a call through the gate: run is called only when the call may run now, once.
|
||||
Run(call approval.Call, run func() (any, error)) (any, error)
|
||||
// Approvals is the seat verb `approvals` for a caller: the calls the gate holds, its acts and claims (A-265, A-271).
|
||||
Approvals(caller string) (approval.Approvals, error)
|
||||
}
|
||||
|
||||
// gatedRun is how a handler calls a tool: through the gate where there is one.
|
||||
@@ -32,6 +34,14 @@ func (c *consumers) gated(call approval.Call, t launch.Tool, caller string) (any
|
||||
return c.gate.Run(call, run)
|
||||
}
|
||||
|
||||
// approvalsOf is how the runner answers a held seat's verb `approvals`: from its gate's record, nil without a gate.
|
||||
func (r *runner) approvalsOf() func(caller string) (any, error) {
|
||||
if r.events.gate == nil {
|
||||
return nil
|
||||
}
|
||||
return func(caller string) (any, error) { return r.events.gate.Approvals(caller) }
|
||||
}
|
||||
|
||||
// WithGate serves every call through the approval gate.
|
||||
func WithGate(g Gate) Option { return func(c *consumers) { c.gate = g } }
|
||||
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -306,7 +306,7 @@ func Run(conn *bus.Conn, served []Served, envs map[string]map[string]string, log
|
||||
}
|
||||
logf("%s", line)
|
||||
conn.OnMembership(func(bus.Membership) { go func() { r.refreshRepairs(); conn.Flush() }() })
|
||||
stopSeats, again := serveSeats(conn, r.modules, r.registrations, r.events.gated, r.events.admitted, r.fingerprintOf, logf)
|
||||
stopSeats, again := serveSeats(conn, r.modules, r.registrations, r.events.gated, r.events.admitted, r.approvalsOf(), r.fingerprintOf, logf)
|
||||
r.seatsAgain = again
|
||||
r.stops = append(r.stops, stopSeats)
|
||||
|
||||
@@ -655,6 +655,9 @@ func endpointsOf(conn *bus.Conn, own []registration, registrations []registratio
|
||||
}
|
||||
for _, v := range m.Seats {
|
||||
t, ok := impl[v.Seat][v.Verb]
|
||||
if v.Verb == seatdef.ApprovalsVerb && !ok {
|
||||
t, ok = launch.Tool{Name: v.Verb, Description: "The calls this seat's holder holds for the operator's approval, its acts and its claims."}, true
|
||||
}
|
||||
if !ok || have[v.Subject] {
|
||||
continue
|
||||
}
|
||||
@@ -710,7 +713,8 @@ func subjectsOf(conn *bus.Conn, module, tool string) []string {
|
||||
// whenever a membership changes, and when again is called because a bundle was loaded again. Whether
|
||||
// this machine holds the seat is the bus's to decide. It answers its stop and again.
|
||||
func serveSeats(conn *bus.Conn, modules []string, current func() []registration, gated gatedRun,
|
||||
admitted func(seat, verb, caller string) error, fingerprintOf func(string) string, logf func(string, ...any)) (func(), func()) {
|
||||
admitted func(seat, verb, caller string) error, approvals func(caller string) (any, error), fingerprintOf func(string) string,
|
||||
logf func(string, ...any)) (func(), func()) {
|
||||
var mu sync.Mutex
|
||||
var stops []func()
|
||||
stopped := false
|
||||
@@ -744,6 +748,24 @@ func serveSeats(conn *bus.Conn, modules []string, current func() []registration,
|
||||
continue
|
||||
}
|
||||
have[v.Subject] = true
|
||||
// The seat's verb `approvals` is the gate's own, served wherever a marked verb is: the runner answers it from its
|
||||
// record of the calls its gate holds, by caller, and no bundle names it (lead answers A-265, A-266, A-271).
|
||||
if v.Verb == seatdef.ApprovalsVerb && approvals != nil {
|
||||
seat := v.Seat
|
||||
stop, err := conn.HandleSubjectCalled(v.Subject, func(caller string, _ json.RawMessage) (any, error) {
|
||||
if err := admitted(seat, seatdef.ApprovalsVerb, caller); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return approvals(caller)
|
||||
})
|
||||
if err != nil {
|
||||
logf("[mesh-tools] cannot serve %s's approvals on %s: %v", seat, v.Subject, err)
|
||||
continue
|
||||
}
|
||||
stops = append(stops, stop)
|
||||
logf("[mesh-tools] serving %s's approvals on %s, the gate's own verb", seat, v.Subject)
|
||||
continue
|
||||
}
|
||||
t, ok := impl[v.Seat][v.Verb]
|
||||
if !ok {
|
||||
logf("[mesh-tools] %s claims %s and implements no %s, which that seat promises; not served", module, v.Seat, v.Verb)
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env python3
|
||||
# The login shell's bundle as the seat node-login-shell has it: MCP over stdio, no SDK, one seat verb, execute, which says
|
||||
# the command it was given and the caller the call's `_meta` named.
|
||||
import json, sys
|
||||
|
||||
def say(m):
|
||||
m["jsonrpc"] = "2.0"; sys.stdout.write(json.dumps(m) + "\n"); sys.stdout.flush()
|
||||
|
||||
TOOLS = [
|
||||
{"name": "node-login-shell.execute", "description": "run a command", "inputSchema": {"type": "object", "properties": {"command": {"type": "string"}}}},
|
||||
]
|
||||
for line in sys.stdin:
|
||||
req = json.loads(line); rid = req.get("id"); m = req.get("method"); p = req.get("params") or {}
|
||||
if m == "initialize":
|
||||
say({"id": rid, "result": {"protocolVersion": "2025-03-26", "capabilities": {"tools": {}}, "serverInfo": {"name": "zsh", "version": "1"}}})
|
||||
elif m == "tools/list":
|
||||
say({"id": rid, "result": {"tools": TOOLS}})
|
||||
elif m == "tools/call":
|
||||
meta = p.get("_meta") or {}
|
||||
said = {"ran": (p.get("arguments") or {}).get("command", ""), "caller": meta.get("mesh/caller", "")}
|
||||
say({"id": rid, "result": {"content": [{"type": "text", "text": json.dumps(said)}]}})
|
||||
elif rid is not None:
|
||||
say({"id": rid, "result": {}})
|
||||
Reference in New Issue
Block a user