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:
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user