A-3 judge and claim: the same call again runs once on the router's record of its Approve

The gate sits on both handlers the runner serves, a module's tools (its ledger repairs
among them) and the seat verbs its modules hold, before the bundle. The same call again
is judged on the router's own record of its act's newest ask: held to the warrant
(operatorchannelclient.For), the option and the act it bound (Performs), within 30 minutes
of the choice, and claimed by compare-and-set (Client.Take) before it runs, so one
approval runs one call. A later call is told it still waits, was declined (not asked again
for 10 minutes), or asks anew after expired, approved too long ago or already run. A
runner's own tool marked is refused unasked; with no state approvals bound a marked call is
refused and an unmarked one runs. The gate's reach (state, publish, the router's record)
is the runner's own connection, in one place; it waits for the catalog half's first view
on the events the runner hears.

Builds: design 46 7-approval-mark §13, §14, §15, §17, §20

Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
This commit is contained in:
2026-11-12 05:13:18 +01:00
parent 9a8636a8ed
commit e0cfcabe60
10 changed files with 560 additions and 7 deletions
+14
View File
@@ -12,6 +12,20 @@ const FingerprintWord = "MESH_FINGERPRINT"
type Gate interface {
// Unserved is, of a module's marked tools, those its bundle does not register, saying it as the module's finding.
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)
}
// gatedRun is how a handler calls a tool: through the gate where there is one.
type gatedRun func(call approval.Call, t launch.Tool, caller string) (any, error)
// gated calls the tool through the gate, or straight where the runner has none.
func (c *consumers) gated(call approval.Call, t launch.Tool, caller string) (any, error) {
run := func() (any, error) { return t.Run(caller, call.Args) }
if c.gate == nil {
return run()
}
return c.gate.Run(call, run)
}
// WithGate serves every call through the approval gate.
+12 -4
View File
@@ -17,6 +17,7 @@ import (
"time"
"github.com/novox/mesh-tools/node-tools/internal/announce"
"github.com/novox/mesh-tools/node-tools/internal/approval"
"github.com/novox/mesh-tools/node-tools/internal/bus"
"github.com/novox/mesh-tools/node-tools/internal/launch"
)
@@ -303,7 +304,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, logf)
stopSeats, again := serveSeats(conn, r.modules, r.registrations, r.events.gated, r.fingerprintOf, logf)
r.seatsAgain = again
r.stops = append(r.stops, stopSeats)
@@ -464,8 +465,11 @@ func (r *runner) serve(l *loadedModule) {
for _, t := range reg.tools {
t := t
// Told who called, from the subject the call arrived on, never from its body (novox/hq issue 365).
// Through the approval gate, before the bundle or the runner's own tool (design 46 7-approval-mark §20).
module, own := reg.module, reg.repairs
stop, err := r.conn.HandleCalled(reg.module+"."+t.Name, func(caller string, body json.RawMessage) (any, error) {
return t.Run(caller, argsOf(body))
return r.events.gated(approval.Call{Address: module + "." + t.Name, Module: module, Name: t.Name, Own: own,
Fingerprint: r.fingerprintOf(module), Caller: caller, Args: argsOf(body), Description: t.Description}, t, caller)
})
if err != nil {
r.logf("[mesh-tools] cannot serve %s.%s: %v", reg.module, t.Name, err)
@@ -703,7 +707,8 @@ func subjectsOf(conn *bus.Conn, module, tool string) []string {
// the tool of the same name registered under the seat's name (ADR 0159, 0160) — and serves again
// 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, logf func(string, ...any)) (func(), func()) {
func serveSeats(conn *bus.Conn, modules []string, current func() []registration, gated gatedRun, fingerprintOf func(string) string,
logf func(string, ...any)) (func(), func()) {
var mu sync.Mutex
var stops []func()
stopped := false
@@ -742,8 +747,11 @@ func serveSeats(conn *bus.Conn, modules []string, current func() []registration,
logf("[mesh-tools] %s claims %s and implements no %s, which that seat promises; not served", module, v.Seat, v.Verb)
continue
}
// Through the approval gate, before the bundle (design 46 7-approval-mark §20).
module, v := module, v
stop, err := conn.HandleSubjectCalled(v.Subject, func(caller string, body json.RawMessage) (any, error) {
return t.Run(caller, argsOf(body))
return gated(approval.Call{Address: v.Seat + "." + v.Verb, Module: module, Seat: v.Seat, Name: v.Verb,
Fingerprint: fingerprintOf(module), Caller: caller, Args: argsOf(body), Description: t.Description}, t, caller)
})
if err != nil {
logf("[mesh-tools] cannot serve %s's %s on %s: %v", v.Seat, v.Verb, v.Subject, err)