From cf2db47e577571a9299924edb6f53ae04d8b0a1e Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 12 Nov 2026 03:11:42 +0100 Subject: [PATCH] RD-2: the validator accepts one module claiming the seat node-engine MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit G's node-engine claims its seat as one module; the trunk refused every claim of a seat the engine holds. One claim is accepted, a second refused naming both. Builds: design 47 G §508, §494, §621 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h --- modules/mesh-delivery-indexer/judge/judge.go | 1 + modules/mesh-delivery-indexer/judge/rules.go | 26 +++++++++++---- .../mesh-delivery-indexer/judge/rules_test.go | 33 +++++++++++++++++++ 3 files changed, 53 insertions(+), 7 deletions(-) diff --git a/modules/mesh-delivery-indexer/judge/judge.go b/modules/mesh-delivery-indexer/judge/judge.go index 59c4921b..a66acdb4 100644 --- a/modules/mesh-delivery-indexer/judge/judge.go +++ b/modules/mesh-delivery-indexer/judge/judge.go @@ -167,6 +167,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/rules.go b/modules/mesh-delivery-indexer/judge/rules.go index 6e5cf777..5cf96ea3 100644 --- a/modules/mesh-delivery-indexer/judge/rules.go +++ b/modules/mesh-delivery-indexer/judge/rules.go @@ -4651,13 +4651,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 @@ -7918,3 +7911,22 @@ func ObjectsCatalogueProblems(shelf catalogue.Shelf) []string { } return problems } + +// NodeEngineSeat is the seat the node-engine's module claims as one module (design 47 G §508). +const NodeEngineSeat = "node-engine" + +// nodeEngineClaimProblems refuses a revision in which more than one module claims the seat node-engine, naming them: one +// module claims it, the node-engine's own (design 47 G §508), in place of refusing every claim. +func nodeEngineClaimProblems(shelf catalogue.Shelf) []string { + 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) + } + } + if len(holders) < 2 { + return nil + } + slices.Sort(holders) + return []string{fmt.Sprintf("%s claim the seat %s, which one module claims (design 47 G §508)", strings.Join(holders, " and "), NodeEngineSeat)} +} diff --git a/modules/mesh-delivery-indexer/judge/rules_test.go b/modules/mesh-delivery-indexer/judge/rules_test.go index 809a516b..a6420d79 100644 --- a/modules/mesh-delivery-indexer/judge/rules_test.go +++ b/modules/mesh-delivery-indexer/judge/rules_test.go @@ -6380,6 +6380,7 @@ func TestRulesOther(t *testing.T) { t.Run("TakesSettingsIsRefusedWhereItCannotMeanAnything", caseTakesSettingsIsRefusedWhereItCannotMeanAnything) t.Run("TheInstallationCheckReadsADefault", caseTheInstallationCheckReadsADefault) t.Run("ASeatNamedViewIsJudgedAgainstTheSeatsViews", caseASeatNamedViewIsJudgedAgainstTheSeatsViews) + t.Run("OneModuleClaimsTheNodeEnginesSeat", caseOneModuleClaimsTheNodeEnginesSeat) } // A consumed view, `seat:.views.`, is accepted where the seat offers the state as a view and refused, by name, @@ -6401,3 +6402,35 @@ func caseASeatNamedViewIsJudgedAgainstTheSeatsViews(t *testing.T) { } } } + +// One module of a revision claims the seat node-engine and is accepted; a second is refused naming both (design 47 G §508). +func caseOneModuleClaimsTheNodeEnginesSeat(t *testing.T) { + was := catalogue.Seats() + t.Cleanup(func() { catalogue.UseSeats(was) }) + catalogue.UseSeats([]catalogue.Seat{{Name: NodeEngineSeat, Scope: catalogue.ScopeNode, Decision: "test", HeldByTheEngine: true}}) + engine := func(module string) []byte { + return []byte(`{"module":"` + module + `","version":"1","claims":[{"name":"node-engine","scope":"node"}]}`) + } + for _, row := range []struct { + name string + raws map[string][]byte + wants string + }{ + {"one claim", map[string][]byte{"mesh-host": engine("mesh-host")}, ""}, + {"two claims", map[string][]byte{"mesh-host": engine("mesh-host"), "other": engine("other")}, + "mesh-host and other claim the seat node-engine, which one module claims"}, + } { + shelf := catalogue.Shelf{} + for name, raw := range row.raws { + m, problems := Manifest(raw, nil) + if len(problems) > 0 { + t.Fatalf("%s: %s refused alone: %v", row.name, name, problems) + } + shelf[name] = m + } + got := strings.Join(Between(shelf, 0, nil), "\n") + if row.wants == "" && strings.Contains(got, "node-engine") || row.wants != "" && !strings.Contains(got, row.wants) { + t.Errorf("%s: %s", row.name, got) + } + } +}