The node-engine's seat reader reads invokes in both forms and refuses a form it cannot read

Builds: design 47 G A-275, §558

Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
This commit is contained in:
2026-11-12 11:07:38 +01:00
parent bef467febe
commit d18bf8b3fd
2 changed files with 48 additions and 15 deletions
+19
View File
@@ -2,9 +2,11 @@ package selfcompose
import (
"context"
"slices"
"strings"
"testing"
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshcatalog"
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshwants"
)
@@ -33,3 +35,20 @@ func TestTheKeepingNodeIsTheWantsKeptOn(t *testing.T) {
}
}
}
// Design 47 G §558, A-275: a manifest's `invokes` in its long form {verb, fields} is read for its seat, and one of a form that
// cannot be read is refused, never read as no seats.
func TestSeatsOfReadsALongFormInvokeAndRefusesAnUnreadableOne(t *testing.T) {
c := New(Env{Node: "n1"})
c.modules["m"] = meshcatalog.ModulesByModuleValue{Manifest: "d1"}
c.modules["bad"] = meshcatalog.ModulesByModuleValue{Manifest: "d2"}
c.manifests["d1"] = []byte(`{"module":"m","invokes":["seat:a.b",{"verb":"seat:mesh-catalog.revision","fields":["x"]}]}`)
c.manifests["d2"] = []byte(`{"module":"bad","invokes":[3]}`)
seats, err := c.seatsOf(context.Background(), reader{c: c}, "m")
if err != nil || !slices.Equal(seats, []string{"a", "mesh-catalog"}) {
t.Fatalf("seats %v, %v", seats, err)
}
if seats, err := c.seatsOf(context.Background(), reader{c: c}, "bad"); err == nil {
t.Fatalf("an unreadable invokes gave seats %v", seats)
}
}
+29 -15
View File
@@ -2035,7 +2035,7 @@ func (c *Composer) heldUntilListed(ctx context.Context, r reader, own meshmodule
held := map[string]string{}
for _, pm := range own.Modules {
if pm.Module == "" || pm.Module == agent.NodeModule || slices.Contains(holders, pm.Module) ||
c.env.SeatList.MayAct(c.seatsOf(ctx, r, pm.Module)...) {
c.mayAct(ctx, r, pm.Module) {
continue
}
held[pm.Module] = line + composesNothingNewOfIt
@@ -2094,7 +2094,7 @@ func (c *Composer) holdUntilListed(ctx context.Context, r reader, compositions m
held[module] = WaitsForTheSeatListOwnPart
continue
}
} else if c.env.SeatList.MayAct(c.seatsOf(ctx, r, module)...) {
} else if c.mayAct(ctx, r, module) {
continue
}
delete(compositions, key)
@@ -2207,41 +2207,55 @@ func (c *Composer) claimants(ctx context.Context, r reader, seat string) []strin
return out
}
// seatsOf is the seats a module claims, invokes or provides, from the manifest its module record names; none when it
// cannot be read, which the reader judges as a module no bootstrapped seat names.
func (c *Composer) seatsOf(ctx context.Context, r reader, module string) []string {
// mayAct is whether the seat list lets module act: of the seats it claims or invokes (seatsOf). A manifest whose `invokes`
// is of a form that cannot be read is refused, never read as no seats (design 47 G A-275).
func (c *Composer) mayAct(ctx context.Context, r reader, module string) bool {
seats, err := c.seatsOf(ctx, r, module)
if err != nil {
return false // refused: held with the module's other waits, never judged as no seats
}
return c.env.SeatList.MayAct(seats...)
}
// seatsOf is the seats a module claims or invokes, from the manifest its module record names; none when it cannot be
// fetched, which the reader judges as a module no bootstrapped seat names, and an error when its manifest is of a form that
// cannot be read (design 47 G §558, A-275).
func (c *Composer) seatsOf(ctx context.Context, r reader, module string) ([]string, error) {
c.mu.Lock()
m, ok := c.modules[module]
c.mu.Unlock()
if !ok || m.Manifest == "" {
return nil
return nil, nil
}
raw, err := r.Manifest(ctx, m.Manifest)
if err != nil {
return nil
return nil, nil
}
var man struct {
Claims []struct {
Name string `json:"name"`
} `json:"claims"`
Invokes []string `json:"invokes"`
Provides []struct {
Name string `json:"name"`
} `json:"provides"`
Invokes json.RawMessage `json:"invokes"`
}
if json.Unmarshal(raw, &man) != nil {
return nil
if err := json.Unmarshal(raw, &man); err != nil {
return nil, fmt.Errorf("the manifest of %s cannot be read: %w", module, err)
}
var invokes []string
if len(man.Invokes) > 0 {
if invokes, err = catalogue.InvokedVerbs(man.Invokes); err != nil {
return nil, fmt.Errorf("the manifest of %s: %w", module, err)
}
}
var seats []string
for _, cl := range man.Claims {
seats = append(seats, cl.Name)
}
for _, in := range man.Invokes {
for _, in := range invokes {
if seat, _, ok := strings.Cut(strings.TrimPrefix(in, "seat:"), "."); ok && strings.HasPrefix(in, "seat:") {
seats = append(seats, seat)
}
}
return seats
return seats, nil
}
// unmadeOf is each module wanted at a fingerprint (fps) of which the pass made no composition at it, with why, apart from