From 2312b05bb5574de0497f40d76315c8f26cd2ede5 Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 11 Nov 2026 15:51:15 +0100 Subject: [PATCH] IX30 (part 2): a revision's one claim of the seat node-engine is accepted, a second and none are refused MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Builds: design 47 G §508, §527 (IX30) Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ --- .../judge/busstate_test.go | 2 +- modules/mesh-delivery-indexer/judge/gwords.go | 28 +++++++++++ modules/mesh-delivery-indexer/judge/judge.go | 1 + .../mesh-delivery-indexer/judge/judge_test.go | 48 ++++++++++++++++++- modules/mesh-delivery-indexer/judge/rules.go | 7 --- 5 files changed, 76 insertions(+), 10 deletions(-) diff --git a/modules/mesh-delivery-indexer/judge/busstate_test.go b/modules/mesh-delivery-indexer/judge/busstate_test.go index 41b380f0..b924043b 100644 --- a/modules/mesh-delivery-indexer/judge/busstate_test.go +++ b/modules/mesh-delivery-indexer/judge/busstate_test.go @@ -160,7 +160,7 @@ func judgedByRequest(t *testing.T, shelf catalogue.Shelf, known KnownPackages) s if known != nil { names = append([]string{}, slices.Sorted(maps.Keys(known))...) } - c := composer.Case{Name: "change", Manifests: map[string]composer.Manifest{}} + c := composer.Case{Name: "change", Manifests: map[string]composer.Manifest{"node-engine": {Raw: []byte(engineManifest("node-engine"))}}} for name, m := range shelf { raw, err := json.Marshal(m) if err != nil { diff --git a/modules/mesh-delivery-indexer/judge/gwords.go b/modules/mesh-delivery-indexer/judge/gwords.go index 75991d62..a1fe4b7b 100644 --- a/modules/mesh-delivery-indexer/judge/gwords.go +++ b/modules/mesh-delivery-indexer/judge/gwords.go @@ -108,3 +108,31 @@ func findingEventsProblems(module string, e catalogue.BusStateEntry) []string { slices.Sort(problems) return problems } + +// NodeEngineSeat is the seat the node-engine claims as one module (design 47 G §508). +const NodeEngineSeat = "node-engine" + +// nodeEngineClaimProblems are the refusals of a revision's claims of the seat `node-engine`: one module claims it, a second +// is refused naming both (§508), and a revision in which none does is refused naming the seat (§527), since the +// node-engine's own module is read from the module whose entry claims it. A revision whose seat list lacks the seat is +// not judged. +func nodeEngineClaimProblems(shelf catalogue.Shelf) []string { + if _, known := catalogue.SeatNamed(NodeEngineSeat); !known { + return nil + } + var holders []string + for module, m := range shelf { + if slices.ContainsFunc(m.Claims, func(c catalogue.Claim) bool { return c.Name == NodeEngineSeat }) { + holders = append(holders, module) + } + } + slices.Sort(holders) + switch { + case len(holders) == 0: + return []string{fmt.Sprintf("no module of the revision claims the seat %s; the node-engine's own module is the one that does "+ + "(design 47 G §527)", NodeEngineSeat)} + case len(holders) > 1: + return []string{fmt.Sprintf("%s claim the seat %s, which one module claims (design 47 G §508)", strings.Join(holders, " and "), NodeEngineSeat)} + } + return nil +} diff --git a/modules/mesh-delivery-indexer/judge/judge.go b/modules/mesh-delivery-indexer/judge/judge.go index d20f441d..5ca9ce1f 100644 --- a/modules/mesh-delivery-indexer/judge/judge.go +++ b/modules/mesh-delivery-indexer/judge/judge.go @@ -170,6 +170,7 @@ func Between(shelf catalogue.Shelf, longestMachine int, known KnownPackages) []s between = append(between, PackagesCatalogueProblems(shelf, known)...) between = append(between, IdentityProblems(shelf, longestMachine)...) between = append(between, DataProblems(shelf)...) + between = append(between, nodeEngineClaimProblems(shelf)...) sort.Strings(between) return slices.Compact(between) } diff --git a/modules/mesh-delivery-indexer/judge/judge_test.go b/modules/mesh-delivery-indexer/judge/judge_test.go index 4dbd39c0..44df30da 100644 --- a/modules/mesh-delivery-indexer/judge/judge_test.go +++ b/modules/mesh-delivery-indexer/judge/judge_test.go @@ -2,13 +2,13 @@ package judge import ( "encoding/json" + "git.novox.be/novox/mesh-sdk/go/catalogue" "reflect" "strings" "testing" "git.novox.be/novox/mesh-delivery/sdk/composer" "git.novox.be/novox/mesh-networking/modules/mesh-wireguard/go/overlay" - "git.novox.be/novox/mesh-sdk/go/catalogue" ) // The judge (novox/hq ADR 0578 §1): a manifest alone is accepted, or refused with each problem; an unknown field is @@ -46,7 +46,7 @@ func TestTheJudge(t *testing.T) { "enrolment": `{"module":"enrolment","version":"1.0","claims":[{"name":"enrolment","scope":"mesh","serves":["issue","again"]}],` + `"tools":["enrolment_first_views","enrolment_first_view_again"]}`}}, } { - raws := map[string][]byte{} + raws := map[string][]byte{"node-engine": []byte(engineManifest("node-engine"))} // a revision has one claim of the seat node-engine (§527) for k, v := range row.raws { raws[k] = []byte(v) } @@ -153,3 +153,47 @@ func versioned(t *testing.T, raw []byte) []byte { } return out } + +// engineManifest is the manifest of a module that claims the seat node-engine, serving every verb the seat promises +// (design 47 G §508, §527). +func engineManifest(module string) string { + seat, _ := catalogue.SeatNamed(NodeEngineSeat) + var verbs []string + for _, v := range seat.Serves { + verbs = append(verbs, `"`+v.Name+`"`) + } + return `{"module":"` + module + `","version":"1.0","claims":[{"name":"node-engine","scope":"node","serves":[` + strings.Join(verbs, ",") + `]}]}` +} + +// Design 47 G §508, §527: one module of a revision claims the seat node-engine; a second is refused naming both, and none +// refuses the revision naming the seat. +func TestTheRevisionHasOneClaimOfTheNodeEngine(t *testing.T) { + for _, row := range []struct { + name string + raws map[string]string + want string // empty: accepted + }{ + {"one claim", map[string]string{"engine": engineManifest("engine")}, ""}, + {"two claims", map[string]string{"engine": engineManifest("engine"), "other": engineManifest("other")}, + "claim the seat node-engine"}, + {"none", map[string]string{"ok": `{"module":"ok","version":"1.0"}`}, "claims the seat node-engine"}, + } { + raws := map[string][]byte{} + for k, v := range row.raws { + raws[k] = []byte(v) + } + refused, unnamed := Shelf(raws, nil, 0) + got := strings.Join(append(unnamed, flat(refused)...), "\n") + if (row.want == "") != (got == "") || !strings.Contains(got, row.want) { + t.Errorf("%s: refused %q, want %q", row.name, got, row.want) + } + } +} + +func flat(refused map[string][]string) []string { + var out []string + for _, p := range refused { + out = append(out, p...) + } + return out +} diff --git a/modules/mesh-delivery-indexer/judge/rules.go b/modules/mesh-delivery-indexer/judge/rules.go index d4f603bf..61c48405 100644 --- a/modules/mesh-delivery-indexer/judge/rules.go +++ b/modules/mesh-delivery-indexer/judge/rules.go @@ -4638,13 +4638,6 @@ func claimProblems(m catalogue.Manifest) []string { } for _, c := range m.Claims { - if s, known := catalogue.SeatNamed(c.Name); known && s.HeldByTheEngine { - // The node's engine holds it on every node, and no module may (novox/hq ADR 0563 §1): a module answering - // root-free or a hand-over would be an agent answering it. - problems = append(problems, fmt.Sprintf("%s claims %s, which every node's engine holds itself and no "+ - "module may (novox/hq ADR 0563 §1)", m.Module, c.Name)) - continue - } if _, known := catalogue.SeatNamed(c.Name); known { // **A seat's scope and what it delivers are not judged here** (novox/hq ADR 0122). // This function runs wherever a manifest is parsed, and one of those places is the