IX30 (part 2): a revision's one claim of the seat node-engine is accepted, a second and none are refused

Builds: design 47 G §508, §527 (IX30)

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
This commit is contained in:
2026-11-11 15:51:15 +01:00
parent 268d2be034
commit 2312b05bb5
5 changed files with 76 additions and 10 deletions
@@ -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 {
@@ -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
}
@@ -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)
}
@@ -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
}
@@ -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