Names N2a: a callers list names a seat, seat:<seat>, resolved by the part's holder function

A tool's callers list named modules ("node-tools", "mesh-healers"). A-295: an entry `seat:<seat>`
allows a module caller whose module holds that seat, as a holder function the part is given answers
it from what it last heard of the assigner's seat-holders view, so no call is made per check.
VerbCallers.AllowedWith and seatlist.RefusalWith/ToolsWith take the function; Allowed, Refusal and
Tools keep their form (no seat entry allows anyone without one); a seatlist.Part is given it by
Holders.

Builds: design 47 G A-210, A-295

Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
This commit is contained in:
2026-11-12 18:43:40 +01:00
parent 9b4e2648ec
commit b088bdaaef
5 changed files with 73 additions and 6 deletions
+16
View File
@@ -140,6 +140,22 @@ func TestACallerInTheArgumentsIsNeverTheCaller(t *testing.T) {
t.Errorf("%s by %q allowed: %v, want %v", c.verb, c.caller, got, c.want)
}
}
// A-295: an entry `seat:<seat>` allows the modules the part's holder function answers for the seat, and
// nobody without holders.
seats := VerbCallers{"again": {AnyPerson, "seat:node-tool-runner", "seat:mesh-healers"}}
holders := func(seat string) []string {
return map[string][]string{"node-tool-runner": {"node-tools"}, "mesh-healers": {"mesh-delivery-watchman"}}[seat]
}
for _, c := range []struct {
caller string
holders func(string) []string
want bool
}{{"ace.node-tools", holders, true}, {"g14.mesh-delivery-watchman", holders, true}, {"ace.mesh-build-go", holders, false},
{"person.jo", holders, true}, {"ace.node-tools", nil, false}} {
if got := seats.AllowedWith("again", c.caller, c.holders) == nil; got != c.want {
t.Errorf("again by %q (holders %v) allowed: %v, want %v", c.caller, c.holders != nil, got, c.want)
}
}
if _, err := ParseVerbCallers(`{"mirror":["x"]}`, []string{"publish"}, nil); err == nil {
t.Error("a list naming a verb the holder does not serve was read")
}
+17 -2
View File
@@ -80,14 +80,29 @@ func ParseVerbCallers(raw string, verbs []string, defaults VerbCallers) (VerbCal
return c, nil
}
// CallerSeatPrefix opens an entry naming a seat, `seat:<seat>`: every module holding it, so a list names no module (design 47
// G A-210, A-295).
const CallerSeatPrefix = "seat:"
// Allowed answers nil when caller, as the runtime proved it, may call verb; else the refusal naming both. An unproven
// caller is never allowed, not even by AnyCaller.
func (c VerbCallers) Allowed(verb, caller string) error {
// caller is never allowed, not even by AnyCaller. An entry `seat:<seat>` allows no one here (AllowedWith).
func (c VerbCallers) Allowed(verb, caller string) error { return c.AllowedWith(verb, caller, nil) }
// AllowedWith is Allowed whose entries `seat:<seat>` allow a module caller whose module holders answers for that seat:
// the part's holder function, which answers from what it last heard of the assigner's seat-holders view (design 47 G A-295),
// so no call is made per check. A nil holders, or a seat it answers none for, allows no one by that entry.
func (c VerbCallers) AllowedWith(verb, caller string, holders func(seat string) []string) error {
if caller == "" || caller == Unproven {
return fmt.Errorf("%s: refused: the request carries no caller the tool runner proved", verb)
}
person := strings.HasPrefix(caller, "person.")
for _, entry := range c[verb] {
if seat, ok := strings.CutPrefix(entry, CallerSeatPrefix); ok {
if !person && holders != nil && slices.Contains(holders(seat), ModuleOf(caller)) {
return nil
}
continue
}
switch entry {
case AnyCaller:
return nil
+14 -2
View File
@@ -21,6 +21,18 @@ type Part struct {
prefix string
callers []string
reader atomic.Pointer[Reader]
holders atomic.Pointer[func(seat string) []string]
}
// Holders gives the part the function that answers a seat's holders, by which its callers' entries `seat:<seat>` are
// resolved when a call is checked (design 47 G A-295): the part's follower of the assigner's seat-holders view.
func (p *Part) Holders(h func(seat string) []string) { p.holders.Store(&h) }
func (p *Part) holdersOf(seat string) []string {
if h := p.holders.Load(); h != nil && *h != nil {
return (*h)(seat)
}
return nil
}
// NewPart is the wiring of a part whose tools are named `<prefix>_seat_list` and `<prefix>_seat_list_again`, the second
@@ -112,7 +124,7 @@ func (p *Part) ToolNames() (read, again string) {
// reader has not started.
func (p *Part) Tools() []stdio.Tool {
read, again := p.ToolNames()
shape := Tools(&Reader{}, read, again, p.callers)
shape := ToolsWith(&Reader{}, read, again, p.callers, p.holdersOf)
for i := range shape {
run := i
shape[i].Run = func(args map[string]any) (any, error) {
@@ -120,7 +132,7 @@ func (p *Part) Tools() []stdio.Tool {
if r == nil {
return nil, errors.New("the seat list reader has not started yet: the runtime has not served this module")
}
return Tools(r, read, again, p.callers)[run].Run(args)
return ToolsWith(r, read, again, p.callers, p.holdersOf)[run].Run(args)
}
}
return shape
+13 -2
View File
@@ -470,6 +470,11 @@ func (r *Reader) changed() {
// answers only to the callers the host's list names (the operator and the healers). The host names the tools and the list;
// again records nothing but the read it asked.
func Tools(r *Reader, read, again string, callers []string) []stdio.Tool {
return ToolsWith(r, read, again, callers, nil)
}
// ToolsWith is Tools whose callers' entries `seat:<seat>` are resolved by holders (RefusalWith, design 47 G A-295).
func ToolsWith(r *Reader, read, again string, callers []string, holders func(seat string) []string) []stdio.Tool {
return []stdio.Tool{
{Name: read,
Description: "The seat list this part holds (novox/hq ADR 0636): each seat's definition with its source (trunk or bootstrap) and " +
@@ -484,7 +489,7 @@ func Tools(r *Reader, read, again string, callers []string) []stdio.Tool {
"and answer what it holds afterwards. Answered only to the operator and the healers.",
Input: map[string]any{"type": "object", "properties": map[string]any{}},
Run: func(args map[string]any) (any, error) {
if err := Refusal(again, callers, stdio.CallerOf(args)); err != nil {
if err := RefusalWith(again, callers, stdio.CallerOf(args), holders); err != nil {
return nil, err
}
err := r.Reload()
@@ -502,7 +507,13 @@ func Tools(r *Reader, read, again string, callers []string) []stdio.Tool {
// callers, the entries of a verb's list as VerbCallers reads them, so stdio.AnyPerson names every operator's session
// the runtime proved (person.<name>) and a module name its instances (ADR 0636 §7).
func Refusal(tool string, callers []string, caller string) error {
if err := (stdio.VerbCallers{tool: callers}).Allowed(tool, caller); err != nil {
return RefusalWith(tool, callers, caller, nil)
}
// RefusalWith is Refusal whose entries `seat:<seat>` allow the modules holders answers for the seat (stdio.VerbCallers.
// AllowedWith, design 47 G A-295).
func RefusalWith(tool string, callers []string, caller string, holders func(seat string) []string) error {
if err := (stdio.VerbCallers{tool: callers}).AllowedWith(tool, caller, holders); err != nil {
return fmt.Errorf("%s is refused to %q: it answers only %s (novox/hq ADR 0636 §7)", tool, caller, strings.Join(callers, ", "))
}
return nil
+13
View File
@@ -284,6 +284,19 @@ func TestTheTwoToolsReadTheHeldListAndReadItAgainForTheirCallers(t *testing.T) {
if _, err := part.Tools()[1].Run(map[string]any{}); err == nil || !strings.Contains(err.Error(), "it answers only operator, mesh-healers") {
t.Fatalf("again without a proven caller: %v", err)
}
// A-295: the part's list names seats, `seat:<seat>`, resolved by the holder function it is given when a call is checked.
bySeat := []string{stdio.AnyPerson, "seat:mesh-healers"}
if err := RefusalWith("seat_list_again", bySeat, "ace.mesh-delivery-watchman", part.holdersOf); err == nil {
t.Fatal("a seat entry allowed a module before the part was given its holders")
}
part.Holders(func(seat string) []string {
return map[string][]string{"mesh-healers": {"mesh-delivery-watchman"}}[seat]
})
for caller, allowed := range map[string]bool{"ace.mesh-delivery-watchman": true, "ace.mesh-build-go": false, "person.jo": true} {
if err := RefusalWith("seat_list_again", bySeat, caller, part.holdersOf); (err == nil) != allowed {
t.Errorf("again by %s through the seat entry: %v, want allowed %v", caller, err, allowed)
}
}
owner2.table["node-c"] = def("node-c", "node", "put")
if err := owner2.say(ChangedEvent, "node-c", SourceTrunk); err != nil || len(woken) == 0 {
t.Fatalf("a change did not wake the part: %v", err)