From 91b1719af98cca22596b273de5d98ce9fd0edc2b Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 12 Nov 2026 02:17:45 +0100 Subject: [PATCH] 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) + } + } }