RR2-5: the reader reads a seat-named view in consumes and a seat's views
mesh/delivery merged: source.main moved to novox/mesh-sdk@e42b9491c931
mesh/delivery-group group fix/47-G-reader-release-2 merged: every member is merged
mesh/check checking 09d6070ebd86 on e42b9491c931 | novox/mesh-sdk#188 in group fix/47-G-reader-release-2

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
This commit is contained in:
2026-11-12 03:09:33 +01:00
parent 98f22ffe72
commit 09d6070ebd
4 changed files with 57 additions and 0 deletions
+30
View File
@@ -5,6 +5,8 @@ import (
"slices" "slices"
"strings" "strings"
"testing" "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): // 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:<seat>.views.<state>`, 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)
}
}
+5
View File
@@ -25,6 +25,11 @@ func SeatFromDefinition(d seatdef.Definition) Seat {
Input: seatdef.ResolveRefs(v.Input, d.Defs), Output: seatdef.ResolveRefs(v.Answer, d.Defs), Input: seatdef.ResolveRefs(v.Input, d.Defs), Output: seatdef.ResolveRefs(v.Answer, d.Defs),
Optional: v.Optional, Replaces: v.Replaces}) 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 { for _, r := range d.Receives {
rv := Receivable{Kind: r.Kind, Comment: r.Comment, Dirs: r.Dirs, Offered: r.Offered} rv := Receivable{Kind: r.Kind, Comment: r.Comment, Dirs: r.Dirs, Offered: r.Offered}
if r.Shape != nil { if r.Shape != nil {
+3
View File
@@ -45,6 +45,9 @@ type Seat struct {
// Serves carries each verb in full — name, description, schema — because a role's tools are the // 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). // mesh's to define and an agent's to call (novox/hq ADR 0132, design 33 §2).
Serves []Verb Serves []Verb
// Views are the names of the states the seat offers as views, which a module follows by `consumes`
// `seat:<seat>.views.<state>` (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 // 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 // 0212): each kind is text in the tool's own grammar, placed by the holder with
// ${contribution:<seat>:<kind>}. Compiled, never stored: like the protocol, it is the mesh's // ${contribution:<seat>:<kind>}. Compiled, never stored: like the protocol, it is the mesh's
+19
View File
@@ -78,6 +78,25 @@ func (s StateDeclaration) MarshalJSON() ([]byte, error) {
return json.Marshal(plain(s)) return json.Marshal(plain(s))
} }
// SeatViewPrefix opens a consumed view of a seat, `seat:<seat>.views.<state>` (design 47 G §616).
const SeatViewPrefix = "seat:"
// SeatViewConsume splits a consumed view of a seat, `seat:<seat>.views.<state>`, into the seat and the state; ok is false for a
// consumed entry that is not of this kind (an event, `<emitter>.<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:<seat>.views.<state>", 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. // 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) { func ReadState(read string) (module, local string, err error) {
at := strings.LastIndex(read, ".") at := strings.LastIndex(read, ".")