Let a tool runner's catalog-half reads through its gate while that half is unread (A-353)
A runner serving the indexer's tools refused every call until its own catalog half was read, and that read is a call of the same seat verbs through the same gate: G could not start. The four seat verbs the catalog half is read by now run unmarked while it is unread; every other call waits and is refused as before. Builds: design 47 G lead answers A-353, A-359 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
This commit is contained in:
@@ -2,10 +2,13 @@ package approval
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/operatorask"
|
||||
"git.novox.be/novox/mesh-sdk/go/seatdef"
|
||||
)
|
||||
|
||||
// call is a fixture call of the marked tool wipe.
|
||||
@@ -78,3 +81,53 @@ func TestTheActAndItsAsk(t *testing.T) {
|
||||
t.Errorf("headline %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
// unread is a catalog half whose first view is not read yet.
|
||||
type unread struct{}
|
||||
|
||||
func (unread) Ready() error { return errors.New("the registrations are not answered") }
|
||||
func (unread) Revision() string { return "" }
|
||||
func (unread) Served(string) (Entry, bool, string) { return Entry{}, false, "not read" }
|
||||
func (unread) Newest(string) (Entry, bool) { return Entry{}, false }
|
||||
func (unread) Definitions() map[string]seatdef.Definition { return nil }
|
||||
func (unread) Entries() []Entry { return nil }
|
||||
|
||||
// Design 47 G lead answers A-353, A-359: while its catalog half is unread, the gate lets through the four seat verbs that
|
||||
// half is read by, told from the call's seat and verb, and holds every other call as before.
|
||||
func TestAnUnreadCatalogHalfLetsItsOwnReadsThrough(t *testing.T) {
|
||||
waited := 0
|
||||
g := New(Deps{Node: "n1", Runner: "node-tools", Catalog: unread{}, Wait: func(time.Duration) error {
|
||||
waited++
|
||||
return errors.New("still unread")
|
||||
}})
|
||||
for _, tc := range []struct {
|
||||
seat, name string
|
||||
runs bool
|
||||
}{
|
||||
{"mesh-catalog", "registrations", true},
|
||||
{"mesh-catalog", "revision", true},
|
||||
{"mesh-catalog", "entry", true},
|
||||
{"mesh-delivery", "source", true},
|
||||
{"mesh-catalog", "register", false},
|
||||
{"mesh-delivery", "registrations", false},
|
||||
{"", "registrations", false}, // a module's tool of that name is no seat verb
|
||||
{"", "revision", false},
|
||||
} {
|
||||
ran := false
|
||||
address := tc.seat + "." + tc.name
|
||||
if tc.seat == "" {
|
||||
address = "mesh-delivery-indexer." + tc.name
|
||||
}
|
||||
_, err := g.Run(Call{Address: address, Module: "mesh-delivery-indexer", Seat: tc.seat, Name: tc.name, Fingerprint: "fp1",
|
||||
Caller: "node-tools"}, func() (any, error) { ran = true; return "ok", nil })
|
||||
if ran != tc.runs || (err == nil) != tc.runs {
|
||||
t.Errorf("%s: ran %v (%v), want ran %v", address, ran, err, tc.runs)
|
||||
}
|
||||
if !tc.runs && (err == nil || !strings.Contains(err.Error(), "are not read yet")) {
|
||||
t.Errorf("%s: refused with %v, want the marks not read yet", address, err)
|
||||
}
|
||||
}
|
||||
if waited != 4 {
|
||||
t.Errorf("the other calls waited for the catalog half %d times, want 4 (the four start reads wait for nothing)", waited)
|
||||
}
|
||||
}
|
||||
|
||||
+17
-2
@@ -3,6 +3,7 @@ package approval
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/operatorask"
|
||||
@@ -67,13 +68,27 @@ func (g *Gate) Run(c Call, run func() (any, error)) (any, error) {
|
||||
// Binding is a Store that says whether the runner's membership binds the state now (§13).
|
||||
type Binding interface{ Bound() bool }
|
||||
|
||||
// marksOf is the module's marks as the catalog half has them now; before its first view, the call waits for it within
|
||||
// RecordWithin and is then judged, refused naming mesh_first_views when it is still unread (§19).
|
||||
// startReads are the seat verbs the catalog half is read by, as `<seat>.<verb>`: while it is unread the gate lets them
|
||||
// through, so the runner that serves them answers the reads its own catalog half waits on (design 47 G lead answers
|
||||
// A-353, A-359).
|
||||
var startReads = []string{"mesh-catalog.registrations", "mesh-catalog.revision", "mesh-catalog.entry", "mesh-delivery.source"}
|
||||
|
||||
// startRead says the call is one of startReads, told from its seat and verb.
|
||||
func startRead(c Call) bool {
|
||||
return c.Seat != "" && slices.Contains(startReads, c.Seat+"."+c.Name)
|
||||
}
|
||||
|
||||
// marksOf is the module's marks as the catalog half has them now; before its first view, a call of startReads runs
|
||||
// unmarked (A-353, A-359) and any other waits for it within RecordWithin and is then judged, refused naming
|
||||
// mesh_first_views when it is still unread (§19).
|
||||
func (g *Gate) marksOf(c Call) (Marks, error) {
|
||||
if g.d.Catalog == nil {
|
||||
return Marks{}, g.refused(c, "", "the tool runner reads no catalog half, so its marks are not read")
|
||||
}
|
||||
if err := g.d.Catalog.Ready(); err != nil {
|
||||
if startRead(c) {
|
||||
return Marks{Module: c.Module}, nil
|
||||
}
|
||||
if g.d.Wait != nil {
|
||||
err = g.d.Wait(RecordWithin)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user