From ffaff3d3aa17c4f0abe112c918bfe7725a0cbb03 Mon Sep 17 00:00:00 2001 From: jochen Date: Sat, 7 Nov 2026 22:40:31 +0100 Subject: [PATCH] mesh-grants: prove the haves fallback lets the indexer serve the seat list's verb Issue 1966: the seat list is read through mesh-catalog's seat-definitions, which got no responders only because nothing was placed. A row shows that, the assignments view unanswered, the indexer placed by its have (its real manifest) is composed to hear mesh.seat.mesh-catalog.* and its membership serving seat-definitions is published, while the seat list stays unread and other modules still wait for it. Claude-Session: https://claude.ai/code/session_01NcbgfFfQ4Z9UE5jK32PmE5 --- modules/mesh-grants/grants_test.go | 36 ++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/modules/mesh-grants/grants_test.go b/modules/mesh-grants/grants_test.go index 0aec273e..b138b314 100644 --- a/modules/mesh-grants/grants_test.go +++ b/modules/mesh-grants/grants_test.go @@ -2542,6 +2542,42 @@ func TestGrants(t *testing.T) { if !strings.Contains(refused, "manifest of mod-x: it waits for the seat list") { t.Errorf("another module is not held: %v", p.Last().Refused) } + + // Issue 1966: the assignments view unanswered, the indexer placed by its have alone (its real manifest, claiming + // mesh-catalog and serving seat-definitions) is composed to serve the seat's verbs, in the list and its membership, + // while the seat list stays unread: the bootstrap reaches the verb that reads the list without opening the gate. + indexer, err := os.ReadFile("../mesh-delivery-indexer/module.json") + if err != nil { + t.Fatal(err) + } + fb := fixture() + fb.refused[grants.ReadAssignments] = true + fb.manifestOf("mesh-delivery-indexer", string(indexer)) + fb.put(grants.ReadHave, "novox.node", map[string]any{"v": 1}) + fb.put(grants.ReadHave, "novox.mesh-delivery-indexer", nodeengine.HaveByNodeModuleValue{V: keys.Version, Phase: nodeengineclient.PhaseRunning, Health: nodeengineclient.HealthOK}) + gate := &seatGate{} + fp := partOn(fb) + fp.SeatList = gate + res := fp.Compose() + if strings.Contains(strings.Join(res.Refused, "\n"), "manifest of mesh-delivery-indexer:") { + t.Fatalf("the indexer is held in a pass placed by the haves: %v", res.Refused) + } + listRecs, _ := grants.ListRecords(grants.ReadInputs(fb, &grants.Manifests{})) + users, err := grants.ComposeUsers(listRecs) + if err != nil { + t.Fatal(err) + } + at := slices.IndexFunc(users, func(u grants.User) bool { return u.User == "novox.mesh-delivery-indexer" }) + if at < 0 || !slices.ContainsFunc(users[at].Subscribe, func(s string) bool { return strings.HasPrefix(s, "mesh.seat.mesh-catalog.") }) { + t.Fatalf("novox.mesh-delivery-indexer is not composed to hear the seat's verbs: %+v", users) + } + raw := fb.own[grants.StateMemberships]["novox.mesh-delivery-indexer"] + if !slices.Contains(res.Published, "novox.mesh-delivery-indexer") || !strings.Contains(string(raw), "seat-definitions") { + t.Errorf("the indexer's membership serving seat-definitions was not published: %v, %s", res.Published, raw) + } + if !strings.Contains(strings.Join(res.Refused, "\n"), "manifest of mod-x: it waits for the seat list") { + t.Errorf("the seat list was opened by a pass placed by the haves: %v", res.Refused) + } }) // DLV-G1, 1966/AC32-AC33 (novox/hq ADR 0595 §1, §7; design 25 § 12.2): the bus module's created writes the binding into