From 09d6070ebd865a8bdefb74f525c4940de8819dae Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 12 Nov 2026 03:09:33 +0100 Subject: [PATCH] RR2-5: the reader reads a seat-named view in consumes and a seat's views MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit G's builder and checker follow the assigner's views by the seat, seat:mesh-module-assigner.views.assignments; SeatViewConsume splits that form, and a seat compiled from its definition names the states it offers as views, which the validator checks the form against. Builds: design 47 G §621, §616, §699 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h --- go/catalogue/reader_release_test.go | 30 ++++++++++++++++++++++++++++ go/catalogue/seat_from_definition.go | 5 +++++ go/catalogue/seats.go | 3 +++ go/catalogue/state.go | 19 ++++++++++++++++++ 4 files changed, 57 insertions(+) diff --git a/go/catalogue/reader_release_test.go b/go/catalogue/reader_release_test.go index 0a4f4e5..4413b13 100644 --- a/go/catalogue/reader_release_test.go +++ b/go/catalogue/reader_release_test.go @@ -5,6 +5,8 @@ import ( "slices" "strings" "testing" + + "git.novox.be/novox/mesh-sdk/go/seatdef" ) // A manifest carrying the words design 47 G writes is read whole by the reader release (design 47 G §340, §409): @@ -69,3 +71,31 @@ func TestTheSecondReaderReleaseReadsGsManifestWords(t *testing.T) { } } } + +// A consumed view of a seat, `seat:.views.`, is read apart from a consumed event, and a seat's compiled form +// names the states its definition offers as views (design 47 G §616, §699, §621). +func TestTheSecondReaderReleaseReadsASeatNamedView(t *testing.T) { + for _, row := range []struct { + consume, seat, state string + ok, bad bool + }{ + {"seat:mesh-module-assigner.views.assignments", "mesh-module-assigner", "assignments", true, false}, + {"mesh-delivery-assigner.views.assignments", "", "", false, false}, + {"seat:mesh-module-assigner.assignments", "", "", true, true}, + {"seat:.views.assignments", "", "", true, true}, + } { + seat, state, ok, err := SeatViewConsume(row.consume) + if seat != row.seat || state != row.state || ok != row.ok || (err != nil) != row.bad { + t.Errorf("SeatViewConsume(%q) = %q, %q, %v, %v", row.consume, seat, state, ok, err) + } + } + m, err := decodeForTest([]byte(`{"module":"m","version":"1.0","consumes":["seat:mesh-module-assigner.views.assignments"]}`)) + if err != nil || len(m.UnknownField()) != 0 || m.Consumes[0] != "seat:mesh-module-assigner.views.assignments" { + t.Errorf("a seat-named view in consumes read as %v, %v", m.Consumes, err) + } + s := SeatFromDefinition(seatdef.Definition{Seat: "mesh-module-assigner", Scope: "mesh", + States: []seatdef.State{{Name: "assignments", View: true}, {Name: "placement"}}}) + if !slices.Equal(s.Views, []string{"assignments"}) { + t.Errorf("the seat's views = %v", s.Views) + } +} diff --git a/go/catalogue/seat_from_definition.go b/go/catalogue/seat_from_definition.go index 1acc52c..5e59183 100644 --- a/go/catalogue/seat_from_definition.go +++ b/go/catalogue/seat_from_definition.go @@ -25,6 +25,11 @@ func SeatFromDefinition(d seatdef.Definition) Seat { Input: seatdef.ResolveRefs(v.Input, d.Defs), Output: seatdef.ResolveRefs(v.Answer, d.Defs), Optional: v.Optional, Replaces: v.Replaces}) } + for _, st := range d.States { + if st.View { + s.Views = append(s.Views, st.Name) + } + } for _, r := range d.Receives { rv := Receivable{Kind: r.Kind, Comment: r.Comment, Dirs: r.Dirs, Offered: r.Offered} if r.Shape != nil { diff --git a/go/catalogue/seats.go b/go/catalogue/seats.go index 17a21e1..9c0c11c 100644 --- a/go/catalogue/seats.go +++ b/go/catalogue/seats.go @@ -45,6 +45,9 @@ type Seat struct { // Serves carries each verb in full — name, description, schema — because a role's tools are the // mesh's to define and an agent's to call (novox/hq ADR 0132, design 33 §2). Serves []Verb + // Views are the names of the states the seat offers as views, which a module follows by `consumes` + // `seat:.views.` (design 47 G §616, §699). Compiled from the definition's states marked `view`. + Views []string // Receives is what other modules may contribute to the seat's holder, by kind (novox/hq ADR // 0212): each kind is text in the tool's own grammar, placed by the holder with // ${contribution::}. Compiled, never stored: like the protocol, it is the mesh's diff --git a/go/catalogue/state.go b/go/catalogue/state.go index fc6a289..6e53af5 100644 --- a/go/catalogue/state.go +++ b/go/catalogue/state.go @@ -78,6 +78,25 @@ func (s StateDeclaration) MarshalJSON() ([]byte, error) { return json.Marshal(plain(s)) } +// SeatViewPrefix opens a consumed view of a seat, `seat:.views.` (design 47 G §616). +const SeatViewPrefix = "seat:" + +// SeatViewConsume splits a consumed view of a seat, `seat:.views.`, into the seat and the state; ok is false for a +// consumed entry that is not of this kind (an event, `.`), and err says why a `seat:` entry is malformed. The +// holder is not in it: it is resolved where the view is followed, so a new holder is followed with no change of the +// manifest (design 47 G §616, §621). +func SeatViewConsume(consume string) (seat, state string, ok bool, err error) { + rest, isSeat := strings.CutPrefix(consume, SeatViewPrefix) + if !isSeat { + return "", "", false, nil + } + seat, view, named := strings.Cut(rest, ".views.") + if !named || !stateName.MatchString(seat) || !stateName.MatchString(view) { + return "", "", true, fmt.Errorf("%q does not name a seat and its view: a consumed view of a seat is seat:.views.", consume) + } + return seat, view, true, nil +} + // ReadState splits a read into the owning module and the local name, or says why it is not one. func ReadState(read string) (module, local string, err error) { at := strings.LastIndex(read, ".")