From 91b1719af98cca22596b273de5d98ce9fd0edc2b Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 12 Nov 2026 02:17:45 +0100 Subject: [PATCH 1/2] Fix 10: the validator accepts a consumed view of a seat and an invoke of a seat's accepted kind MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Items 1 and 2 of the round: a consumes seat:.views. is accepted where the seat offers the state as a view, an invokes seat:. or seat:.. where the seat accepts it. Builds: design 47 G §616, §699, A-102, A-131 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h --- modules/mesh-delivery-indexer/judge/rules.go | 24 +++++++++++++- .../mesh-delivery-indexer/judge/rules_test.go | 32 +++++++++++++++++-- 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/modules/mesh-delivery-indexer/judge/rules.go b/modules/mesh-delivery-indexer/judge/rules.go index e00d44d3..2a25acc0 100644 --- a/modules/mesh-delivery-indexer/judge/rules.go +++ b/modules/mesh-delivery-indexer/judge/rules.go @@ -1706,6 +1706,19 @@ func EventProblems(m catalogue.Manifest) []string { m.Module, c, catalogue.TheRest)) continue } + if seat, state, isSeat, err := catalogue.SeatViewConsume(c); isSeat { + // A consumed view of a seat (design 47 G §616, §699, A-131): the seat offers the state as a view; the holder is + // resolved where it is read. + switch s, known := catalogue.SeatNamed(seat); { + case err != nil: + problems = append(problems, fmt.Sprintf("%s consumes %v", m.Module, err)) + case !known: + problems = append(problems, fmt.Sprintf("%s consumes %q, a view of the seat %q, "+catalogue.UndeclaredSeat, m.Module, c, seat)) + case !slices.Contains(s.Views, state): + problems = append(problems, fmt.Sprintf("%s consumes %q, and the seat %s does not offer the state %q as a view (design 47 G §699)", m.Module, c, seat, state)) + } + continue + } if err := consumePattern(c); err != nil { problems = append(problems, fmt.Sprintf("%s consumes %q: %v", m.Module, c, err)) } @@ -4497,7 +4510,7 @@ func seatInvokeProblems(shelf catalogue.Shelf, declared map[string]catalogue.Sea problems = append(problems, fmt.Sprintf("%s invokes %s, a verb of the seat %q, "+catalogue.UndeclaredSeat, module, invoke, seat)) continue } - if !slices.ContainsFunc(verbs, func(v catalogue.Verb) bool { return v.Name == verb }) { + if !slices.ContainsFunc(verbs, func(v catalogue.Verb) bool { return v.Name == verb }) && !acceptsKind(seat, verb, declared) { problems = append(problems, fmt.Sprintf("%s invokes %s, and the seat %s serves no verb %q", module, invoke, seat, verb)) continue } @@ -4509,6 +4522,15 @@ func seatInvokeProblems(shelf catalogue.Shelf, declared map[string]catalogue.Sea return problems } +// acceptsKind says the seat accepts the kind an invoke names after its seat (design 47 G A-102): ``, or +// `.` for a kind that rides a stream. A module's own seat names its accepts by kind alone. +func acceptsKind(seat, kind string, declared map[string]catalogue.SeatDeclaration) bool { + if s, isMesh := catalogue.SeatNamed(seat); isMesh { + return slices.Contains(s.Accepts, kind) || slices.Contains(s.AcceptedOn, kind) + } + return slices.Contains(declared[seat].Accepts, kind) +} + // SeatPathProblems is what is wrong with the paths a set of the mesh's own seats names (novox/hq ADR // 0287): only a node seat names one, each absolute and literal; every path has exactly one seat naming it // without stepping back; and a seat steps back only for another node seat naming the same path without diff --git a/modules/mesh-delivery-indexer/judge/rules_test.go b/modules/mesh-delivery-indexer/judge/rules_test.go index f1f9906e..e5d69b46 100644 --- a/modules/mesh-delivery-indexer/judge/rules_test.go +++ b/modules/mesh-delivery-indexer/judge/rules_test.go @@ -6418,11 +6418,14 @@ func TestRulesOther(t *testing.T) { } // A read of a seat holder's state, `seat:.`, is accepted where the seat declares the state and refused, by name, -// where it does not or the seat is unknown (design 47 G A-116, A-128). +// where it does not or the seat is unknown (design 47 G A-116, A-128); a consumed view, `seat:.views.`, where the +// seat declares the state as a view (§616, §699, A-131); an invoke of an accepted kind, `seat:.` or +// `seat:..`, where the seat accepts it (A-102). func TestASeatNamedReadIsJudgedAgainstTheSeatsStates(t *testing.T) { was := catalogue.Seats() t.Cleanup(func() { catalogue.UseSeats(was) }) - catalogue.UseSeats([]catalogue.Seat{{Name: "mesh-provisioner", Scope: catalogue.ScopeMesh, Decision: "test", States: []string{"plans"}}}) + catalogue.UseSeats([]catalogue.Seat{{Name: "mesh-provisioner", Scope: catalogue.ScopeMesh, Decision: "test", + States: []string{"plans", "other"}, Views: []string{"plans"}, Accepts: []string{"step", "step"}, AcceptedOn: []string{"check.step"}}}) for _, row := range []struct{ read, says string }{ {"seat:mesh-provisioner.plans", ""}, {"seat:mesh-provisioner.nothing", `declares no state "nothing"`}, @@ -6434,4 +6437,29 @@ func TestASeatNamedReadIsJudgedAgainstTheSeatsStates(t *testing.T) { t.Errorf("%s: problems %q, want %q", row.read, problems, row.says) } } + for _, row := range []struct{ consume, says string }{ + {"seat:mesh-provisioner.views.plans", ""}, + {"seat:mesh-provisioner.views.other", "does not offer the state"}, + {"seat:mesh-provisioner.views.nothing", "does not offer the state"}, + {"seat:mesh-unknown.views.plans", "mesh-unknown"}, + {"seat:mesh-provisioner.plans", "does not name a seat and its view"}, + } { + problems := strings.Join(EventProblems(catalogue.Manifest{Module: "reader", Consumes: []string{row.consume}}), "\n") + if row.says == "" && problems != "" || row.says != "" && !strings.Contains(problems, row.says) { + t.Errorf("%s: problems %q, want %q", row.consume, problems, row.says) + } + } + shelf := catalogue.Shelf{"holder": {Module: "holder", Claims: []catalogue.Claim{{Name: "mesh-provisioner"}}}} + for _, row := range []struct{ invoke, says string }{ + {"seat:mesh-provisioner.step", ""}, + {"seat:mesh-provisioner.check.step", ""}, + {"seat:mesh-provisioner.delivery.step", `serves no verb "delivery.step"`}, + {"seat:mesh-provisioner.check.other", `serves no verb "check.other"`}, + } { + shelf["caller"] = catalogue.Manifest{Module: "caller", Invokes: []string{row.invoke}} + problems := strings.Join(seatInvokeProblems(shelf, nil), "\n") + if row.says == "" && problems != "" || row.says != "" && !strings.Contains(problems, row.says) { + t.Errorf("%s: problems %q, want %q", row.invoke, problems, row.says) + } + } } From 5f3410cd24fa4212f8672beea00ec364f91d7f37 Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 12 Nov 2026 02:19:06 +0100 Subject: [PATCH 2/2] Fix 10: the node-build-image acting-rule test lives beside the other hand-written tests; the pipeline's go.mod drops an unused require MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Item 3: seatgen -check no longer names go/seats/nodebuildimage/acts_test.go (not written by the generator); the test moves to the domain SDK's buildstep, which already reads the build seats' definitions. mesh-delivery-pipeline no longer imports meshmoduleassigner. Builds: design 47 G §324, §397 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h --- modules/mesh-delivery-pipeline/go.mod | 1 - .../acts_test.go => sdk/buildstep/seat_acts_test.go | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) rename contracts/go/seats/nodebuildimage/acts_test.go => sdk/buildstep/seat_acts_test.go (85%) diff --git a/modules/mesh-delivery-pipeline/go.mod b/modules/mesh-delivery-pipeline/go.mod index ca25c2c2..50178e66 100644 --- a/modules/mesh-delivery-pipeline/go.mod +++ b/modules/mesh-delivery-pipeline/go.mod @@ -9,7 +9,6 @@ require ( ) require ( - git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner v0.0.0-00010101000000-000000000000 git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshhandactsclient v0.0.0-00010101000000-000000000000 git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshconditions v0.0.0-00010101000000-000000000000 git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient v0.0.0-00010101000000-000000000000 diff --git a/contracts/go/seats/nodebuildimage/acts_test.go b/sdk/buildstep/seat_acts_test.go similarity index 85% rename from contracts/go/seats/nodebuildimage/acts_test.go rename to sdk/buildstep/seat_acts_test.go index 7fc457f0..848bdd93 100644 --- a/contracts/go/seats/nodebuildimage/acts_test.go +++ b/sdk/buildstep/seat_acts_test.go @@ -1,4 +1,4 @@ -package nodebuildimage +package buildstep import ( "encoding/json" @@ -8,7 +8,7 @@ import ( // The seat's `build` acting rule matches an artifact by its kind, an image having no language (design 47 G §324, §397). func TestTheSeatsBuildRuleActsOnTheKindImage(t *testing.T) { - raw, err := os.ReadFile("../../../seats/node-build-image/seat.json") + raw, err := os.ReadFile("../../contracts/seats/node-build-image/seat.json") if err != nil { t.Fatal(err) }