From 31fe1f1adb418b25c63c627329308f9c76ea9c96 Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 12 Nov 2026 01:18:57 +0100 Subject: [PATCH] Fix 9.7c: a read of a seat holder's state is its own kind, seat:. Builds: design 47 G A-128 (A-116) Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h --- go/catalogue/seat_from_definition.go | 3 +++ go/catalogue/seats.go | 3 +++ go/catalogue/state.go | 19 +++++++++++++++++++ go/catalogue/state_test.go | 26 ++++++++++++++++++++++++++ 4 files changed, 51 insertions(+) diff --git a/go/catalogue/seat_from_definition.go b/go/catalogue/seat_from_definition.go index 1acc52c6..38994426 100644 --- a/go/catalogue/seat_from_definition.go +++ b/go/catalogue/seat_from_definition.go @@ -25,6 +25,9 @@ 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 { + s.States = append(s.States, 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 aa225667..ca1fe51e 100644 --- a/go/catalogue/seats.go +++ b/go/catalogue/seats.go @@ -46,6 +46,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 + // States are the names of the state the seat's holder keeps, which a module reads by `seat:.` (design 47 G + // A-116, A-128). Compiled from the definition's states. + States []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 daf041a9..3608fad9 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)) } +// SeatReadPrefix opens a read of the state a seat's holder keeps: `seat:.` (design 47 G A-116, A-128). +const SeatReadPrefix = "seat:" + +// SeatRead splits a read of a seat holder's state, `seat:.`, into the seat and the local name; ok is false for a +// read that is not of this kind (a module's `.`, which ReadState splits), and err says why a `seat:` read is +// malformed. The holder is not in it: it is resolved where the read is made (seatholder), so a new holder is read with no +// change of the manifest. +func SeatRead(read string) (seat, local string, ok bool, err error) { + rest, isSeat := strings.CutPrefix(read, SeatReadPrefix) + if !isSeat { + return "", "", false, nil + } + at := strings.LastIndex(rest, ".") + if at <= 0 || at == len(rest)-1 || !stateName.MatchString(rest[:at]) || !stateName.MatchString(rest[at+1:]) { + return "", "", true, fmt.Errorf("%q does not name a seat and its state: a read of a seat holder's state is seat:.", read) + } + return rest[:at], rest[at+1:], 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, ".") diff --git a/go/catalogue/state_test.go b/go/catalogue/state_test.go index a5549cc1..f6dc005c 100644 --- a/go/catalogue/state_test.go +++ b/go/catalogue/state_test.go @@ -4,6 +4,8 @@ import ( "encoding/json" "strings" "testing" + + "git.novox.be/novox/mesh-sdk/go/seatdef" ) // A module declares the state it keeps and the state it reads (novox/hq ADR 0201), a bucket by its @@ -67,3 +69,27 @@ func TestAStateMayHaveKeysThatExpireOneByOne(t *testing.T) { t.Fatalf("read-only given twice: %v", err) } } + +// A read of a seat holder's state is its own kind, `seat:.` (design 47 G A-128), and a seat's compiled form +// carries the names of the states its definition declares. +func TestSeatRead(t *testing.T) { + for _, row := range []struct { + read, seat, local string + ok, bad bool + }{ + {"seat:mesh-provisioner.plans", "mesh-provisioner", "plans", true, false}, + {"mesh-provisioner.plans", "", "", false, false}, + {"seat:mesh-provisioner", "", "", true, true}, + {"seat:.plans", "", "", true, true}, + {"seat:Mesh.plans", "", "", true, true}, + } { + seat, local, ok, err := SeatRead(row.read) + if seat != row.seat || local != row.local || ok != row.ok || (err != nil) != row.bad { + t.Errorf("SeatRead(%q) = %q, %q, %v, %v", row.read, seat, local, ok, err) + } + } + s := SeatFromDefinition(seatdef.Definition{Seat: "mesh-provisioner", Scope: "mesh", States: []seatdef.State{{Name: "plans"}, {Name: "provisions"}}}) + if len(s.States) != 2 || s.States[0] != "plans" || s.States[1] != "provisions" { + t.Errorf("the seat's states = %v", s.States) + } +}