A tool reads the bindings the grants part keeps, beside the memberships it wrote (issue 1966)
bound.<module> had no reader: a binding lost or kept but not yet written into a membership could only be guessed from grants_membership. grants_bindings names each kept binding and, by membership, the ones it lacks. Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
This commit is contained in:
@@ -251,6 +251,11 @@ func toolsOf(p *grants.Part) []stdio.Tool {
|
||||
Run: func(a map[string]any) (any, error) {
|
||||
return p.AskFirstViewAgain(stdio.CallerOf(a), grants.Answer(a, "state"), grants.Answer(a, "why"), recordHandAct)
|
||||
}},
|
||||
{Name: grants.ToolBindings, Description: "The bindings the grants part keeps of a module's kept data on the bus (novox/hq " +
|
||||
"ADR 0595 §1, §7): each entry the bus module said created or recreated, by local name with its names on the bus, beside " +
|
||||
"every membership of the module the part wrote, and the kept bindings a membership lacks. One module, or every one. Read-only.",
|
||||
Input: schema(map[string]string{"module": "one module, e.g. distribution; none for every one"}),
|
||||
Run: func(a map[string]any) (any, error) { return p.BindingsOf(grants.Answer(a, "module")) }},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3117,6 +3117,29 @@ func TestGrants(t *testing.T) {
|
||||
if !hear("mesh.seat.mesh-broker.event.created", first) || hear("mesh.seat.mesh-broker.event.created", first) {
|
||||
t.Fatal("created: not kept once")
|
||||
}
|
||||
// Issue 1966: grants_bindings reads what the part keeps: a binding kept before the pass that writes it into the
|
||||
// memberships is named lacking there, and no longer once the pass has written it; a module with none says so.
|
||||
lacking := func() string {
|
||||
got, err := p.BindingsOf("mod-x")
|
||||
if err != nil {
|
||||
t.Fatalf("grants_bindings: %v", err)
|
||||
}
|
||||
raw, _ := json.Marshal(got)
|
||||
if !strings.Contains(string(raw), `"bucket":"mod-x_records"`) {
|
||||
t.Errorf("grants_bindings names no kept binding: %s", raw)
|
||||
}
|
||||
return string(raw)
|
||||
}
|
||||
if got := lacking(); !strings.Contains(got, `"lacking":{"a.mod-x":["records"]}`) {
|
||||
t.Errorf("a binding kept and not written is not named lacking: %s", got)
|
||||
}
|
||||
p.Compose()
|
||||
if got := lacking(); strings.Contains(got, `"lacking"`) || !strings.Contains(got, `"a.mod-x":["records"]`) {
|
||||
t.Errorf("a binding written is named lacking: %s", got)
|
||||
}
|
||||
if got, err := p.BindingsOf("mod-y"); err != nil || !strings.Contains(fmt.Sprint(got), "keeps no binding of mod-y") {
|
||||
t.Errorf("grants_bindings of a module with none = %v, %v", got, err)
|
||||
}
|
||||
// Issue 1966 (F3): two created of one module heard at once are both kept: the second writer comes between the
|
||||
// first's read and its write, and the first, refused at its revision, reads again and keeps both.
|
||||
holds := membership.BusBinding{Local: "holds", Storage: "key-value", Template: "state", Bucket: "mod-x_holds"}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/meshbroker"
|
||||
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
@@ -181,6 +182,76 @@ func (p *Part) bindingsOf(module string) ([]membership.BusBinding, error) {
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// BindingsOf answers the tool grants_bindings: every binding the part keeps of one module, or of every module when module
|
||||
// is empty (its own state memberships, key BoundKey), each beside the memberships `<node>.<module>` it wrote, and by
|
||||
// membership the kept local names its bindings lack, so a binding kept but not yet written into a membership is named.
|
||||
func (p *Part) BindingsOf(module string) (any, error) {
|
||||
keys, err := p.Store.Keys(StateMemberships)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("its own %s cannot be listed: %w", StateMemberships, err)
|
||||
}
|
||||
type kept struct {
|
||||
Module string `json:"module"`
|
||||
Bindings []membership.BusBinding `json:"bindings"`
|
||||
Memberships map[string][]string `json:"memberships"`
|
||||
Lacking map[string][]string `json:"lacking,omitempty"`
|
||||
}
|
||||
out := []kept{}
|
||||
for _, key := range keys {
|
||||
mod, ok := strings.CutPrefix(key, BoundPrefix)
|
||||
if !ok || (module != "" && mod != module) {
|
||||
continue
|
||||
}
|
||||
binds, err := p.bindingsOf(mod)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
k := kept{Module: mod, Bindings: binds, Memberships: map[string][]string{}}
|
||||
if k.Bindings == nil {
|
||||
k.Bindings = []membership.BusBinding{}
|
||||
}
|
||||
for _, mk := range keys {
|
||||
if _, of, _ := strings.Cut(mk, "."); strings.HasPrefix(mk, BoundPrefix) || of != mod {
|
||||
continue
|
||||
}
|
||||
raw, _, found, err := p.Store.Get(StateMemberships, mk)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("its own membership %s cannot be read: %w", mk, err)
|
||||
}
|
||||
if !found {
|
||||
continue
|
||||
}
|
||||
bare, err := membership.BareGrantsMembership(raw)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("its own membership %s does not read: %w", mk, err)
|
||||
}
|
||||
var m membership.Membership
|
||||
if err := json.Unmarshal(bare, &m); err != nil {
|
||||
return nil, fmt.Errorf("its own membership %s does not read: %w", mk, err)
|
||||
}
|
||||
written := []string{}
|
||||
for _, b := range m.Bindings {
|
||||
written = append(written, b.Local)
|
||||
}
|
||||
k.Memberships[mk] = written
|
||||
for _, b := range binds {
|
||||
if !slices.Contains(written, b.Local) {
|
||||
if k.Lacking == nil {
|
||||
k.Lacking = map[string][]string{}
|
||||
}
|
||||
k.Lacking[mk] = append(k.Lacking[mk], b.Local)
|
||||
}
|
||||
}
|
||||
}
|
||||
out = append(out, k)
|
||||
}
|
||||
if module != "" && len(out) == 0 {
|
||||
return map[string]any{"modules": out, "says": "the part keeps no binding of " + module + ": the bus module said no created for it that the part heard"}, nil
|
||||
}
|
||||
return map[string]any{"modules": out,
|
||||
"says": "each binding the part kept from the bus module's created and recreated, beside the memberships it wrote; lacking names a kept binding a membership does not carry yet"}, nil
|
||||
}
|
||||
|
||||
func sameBinding(a, b membership.BusBinding) bool {
|
||||
ra, _ := json.Marshal(a)
|
||||
rb, _ := json.Marshal(b)
|
||||
|
||||
@@ -31,11 +31,13 @@ const (
|
||||
// ToolSeatList and ToolSeatListAgain read and read again the seat list the part composes from (mesh-sdk seatlist.Tools).
|
||||
ToolSeatList = "grants_seat_list"
|
||||
ToolSeatListAgain = "grants_seat_list_again"
|
||||
// ToolBindings reads the bindings the part keeps from the bus module's events (bindings.go, issue 1966).
|
||||
ToolBindings = "grants_bindings"
|
||||
)
|
||||
|
||||
// Tools is every tool, in the manifest's order.
|
||||
var Tools = []string{ToolGrants, ToolMembership, ToolUserList, ToolComposeNow, ToolAssert, ToolAccounts, ToolConsumerMatch,
|
||||
ToolAcceptHashes, ToolFirstViews, ToolFirstViewAgain, ToolSeatList, ToolSeatListAgain}
|
||||
ToolAcceptHashes, ToolFirstViews, ToolFirstViewAgain, ToolSeatList, ToolSeatListAgain, ToolBindings}
|
||||
|
||||
// WritesGrants is what grants says of the part's own grants: written once the list the bus loaded carries a build's
|
||||
// accounts, and the one key the assigner reads (ADR 0571 clause 9, which changes ADR 0547 clause 5).
|
||||
|
||||
@@ -61,7 +61,8 @@
|
||||
"grants_first_views",
|
||||
"grants_first_view_again",
|
||||
"grants_seat_list",
|
||||
"grants_seat_list_again"
|
||||
"grants_seat_list_again",
|
||||
"grants_bindings"
|
||||
],
|
||||
"state": [
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user