A-165 callers: the tool runner refuses a seat verb's caller outside its effective callers before the bundle

Where serveSeats answers a seat's verb it reads the verb from the runner's seat list (seatdef's
EffectiveCallers): an unproven caller is refused, and every caller while the seat's definition is unread,
names no such verb or opens it to none; a proven caller is among them, since the grants part composes the
call subject only for the effective callers. A module's tool on the subject naming no caller is unchanged.

Builds: design 47 G §610; lead answers A-165, A-179

Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
This commit is contained in:
2026-11-12 05:49:57 +01:00
parent afc327392e
commit e7e20e3d13
4 changed files with 86 additions and 9 deletions
+12 -4
View File
@@ -16,6 +16,8 @@ import (
"sync"
"time"
"git.novox.be/novox/mesh-sdk/go/seatdef"
"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"
@@ -304,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.fingerprintOf, logf)
stopSeats, again := serveSeats(conn, r.modules, r.registrations, r.events.gated, r.events.admitted, r.fingerprintOf, logf)
r.seatsAgain = again
r.stops = append(r.stops, stopSeats)
@@ -707,8 +709,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, gated gatedRun, fingerprintOf func(string) string,
logf func(string, ...any)) (func(), func()) {
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()) {
var mu sync.Mutex
var stops []func()
stopped := false
@@ -747,9 +749,13 @@ 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).
// Its callers checked, then through the approval gate, before the bundle (design 47 G §610, A-179; design 46
// 7-approval-mark §20).
module, v := module, v
stop, err := conn.HandleSubjectCalled(v.Subject, func(caller string, body json.RawMessage) (any, error) {
if err := admitted(v.Seat, v.Verb, caller); err != nil {
return nil, err
}
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)
})
@@ -794,6 +800,8 @@ type consumers struct {
sessions sessionTable
// gate is the approval gate every call is served through (gate.go); nil serves every call as before.
gate Gate
// definitions are the seat definitions a seat verb's callers are read from (gate.go); nil admits every caller.
definitions func() map[string]seatdef.Definition
}
type moduleEvents struct {