From bf06f0f6f3f8de134f1e0c98fab290e0c19e5a51 Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 12 Nov 2026 18:54:22 +0100 Subject: [PATCH] Names N2b: a part's holder function reads the seat-holders view it follows HoldersIn is the one holder function a part gives seatlist.Part.Holders: the modules holding a seat on any node, from the seat-holders view as the part last heard it (a views follower's or a watched state's values), so a callers entry `seat:` is resolved with no call per checked call. Builds: design 47 G A-295, A-210 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h --- go/seatlist/holder.go | 35 +++++++++++++++++++++++++++++++++++ go/seatlist/seatlist_test.go | 12 ++++++++---- 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/go/seatlist/holder.go b/go/seatlist/holder.go index 27a12e2d..ce38e37d 100644 --- a/go/seatlist/holder.go +++ b/go/seatlist/holder.go @@ -9,6 +9,8 @@ package seatlist import ( "encoding/json" "fmt" + "slices" + "strings" ) const ( @@ -50,3 +52,36 @@ func HolderOf(call HolderCaller, seat string) (string, error) { } return "", fmt.Errorf("no module holds the seat %s on record", seat) } + +// HoldersIn is a part's holder function (Part.Holders, design 47 G A-295): the modules holding a seat on any node, read from +// values, the seat-holders view of the seat that places modules as the part last heard it (a views follower's or a watched +// state's Values), so a callers entry `seat:` is resolved with no call per checked call. The view keys a seat held once +// `` and a node seat `.`; none while the view cannot be read or nobody holds the seat. +func HoldersIn[V ~[]byte](values func() (map[string]V, error)) func(seat string) []string { + return func(seat string) []string { + keys, err := values() + if err != nil { + return nil + } + var out []string + for key, raw := range keys { + if key != seat && !strings.HasPrefix(key, seat+".") { + continue + } + var held struct { + Holders []struct { + Module string `json:"module"` + } `json:"holders"` + } + if json.Unmarshal([]byte(raw), &held) != nil { + continue + } + for _, h := range held.Holders { + if h.Module != "" && !slices.Contains(out, h.Module) { + out = append(out, h.Module) + } + } + } + return out + } +} diff --git a/go/seatlist/seatlist_test.go b/go/seatlist/seatlist_test.go index da318d15..526c53fa 100644 --- a/go/seatlist/seatlist_test.go +++ b/go/seatlist/seatlist_test.go @@ -289,10 +289,14 @@ func TestTheTwoToolsReadTheHeldListAndReadItAgainForTheirCallers(t *testing.T) { 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} { + // HoldersIn reads the seat-holders view as the part last heard it: a seat held once by ``, a node seat by + // `.`, and no other seat's key. + view := map[string][]byte{"mesh-healers": []byte(`{"seat":"mesh-healers","holders":[{"module":"mesh-delivery-watchman","node":"g14"}]}`), + "node-tool-runner.ace": []byte(`{"seat":"node-tool-runner","node":"ace","holders":[{"module":"node-tools","node":"ace"}]}`), + "mesh-healers-x": []byte(`{"holders":[{"module":"mesh-build-go"}]}`)} + part.Holders(HoldersIn(func() (map[string][]byte, error) { return view, nil })) + bySeat = append(bySeat, "seat:node-tool-runner") + for caller, allowed := range map[string]bool{"ace.mesh-delivery-watchman": true, "ace.mesh-build-go": false, "person.jo": true, "g14.node-tools": 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) }