From 1d1900c083f2bf6c98f5af7f8094c1105a07222c Mon Sep 17 00:00:00 2001 From: jochen Date: Fri, 13 Nov 2026 11:37:11 +0100 Subject: [PATCH] S3 sdk-queue-invoke: QueueInvoke, and a seat lists its states and stream kinds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Builds: design 47 G A-231, §601, A-102, A-128 (A-343, A-344) Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h --- go/catalogue/seat_from_definition.go | 6 ++++ go/catalogue/seat_from_definition_test.go | 43 +++++++++++++++++++++++ go/catalogue/seat_invokes.go | 15 ++++++++ go/catalogue/seats.go | 6 ++++ 4 files changed, 70 insertions(+) create mode 100644 go/catalogue/seat_from_definition_test.go diff --git a/go/catalogue/seat_from_definition.go b/go/catalogue/seat_from_definition.go index 5e591838..7882915b 100644 --- a/go/catalogue/seat_from_definition.go +++ b/go/catalogue/seat_from_definition.go @@ -26,10 +26,16 @@ func SeatFromDefinition(d seatdef.Definition) Seat { Optional: v.Optional, Replaces: v.Replaces}) } for _, st := range d.States { + s.States = append(s.States, st.Name) if st.View { s.Views = append(s.Views, st.Name) } } + for _, a := range d.Accepts { + if a.Stream != "" { + s.AcceptedOn = append(s.AcceptedOn, a.Stream+"."+a.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/seat_from_definition_test.go b/go/catalogue/seat_from_definition_test.go new file mode 100644 index 00000000..70da265a --- /dev/null +++ b/go/catalogue/seat_from_definition_test.go @@ -0,0 +1,43 @@ +package catalogue + +import ( + "slices" + "testing" + + "git.novox.be/novox/mesh-sdk/go/seatdef" +) + +// A seat's compiled form lists the states its definition declares and the kinds it accepts on a stream (design 47 G A-102, +// A-128); a seat without either lists none, as before. +func TestSeatFromDefinitionListsItsStatesAndStreamKinds(t *testing.T) { + s := SeatFromDefinition(seatdef.Definition{Seat: "mesh-provisioner", Scope: "mesh", + States: []seatdef.State{{Name: "plans", View: true}, {Name: "provisions"}}, + Accepts: []seatdef.Message{{Name: "step", Stream: "check"}, {Name: "step"}}}) + if !slices.Equal(s.States, []string{"plans", "provisions"}) || !slices.Equal(s.Views, []string{"plans"}) || + !slices.Equal(s.AcceptedOn, []string{"check.step"}) { + t.Errorf("states = %v, views = %v, accepted on = %v", s.States, s.Views, s.AcceptedOn) + } + bare := SeatFromDefinition(seatdef.Definition{Seat: "x", Scope: "mesh"}) + if bare.States != nil || bare.AcceptedOn != nil { + t.Errorf("a bare seat lists %v, %v", bare.States, bare.AcceptedOn) + } +} + +// A queue invoke `seat:.accept.` is split by one helper (design 47 G A-231); a verb invoke is not one. +func TestQueueInvoke(t *testing.T) { + for _, row := range []struct { + invoke, seat, queue string + ok bool + }{ + {"seat:node-build-go.accept.check", "node-build-go", "check", true}, + {"seat:x.verb", "", "", false}, + {"seat:x.accept.a.b", "", "", false}, + {"seat:x.accept.", "", "", false}, + {"node-tools.x", "", "", false}, + } { + seat, queue, ok := QueueInvoke(row.invoke) + if seat != row.seat || queue != row.queue || ok != row.ok { + t.Errorf("QueueInvoke(%q) = %q, %q, %v", row.invoke, seat, queue, ok) + } + } +} diff --git a/go/catalogue/seat_invokes.go b/go/catalogue/seat_invokes.go index d16a0b1d..71d5235f 100644 --- a/go/catalogue/seat_invokes.go +++ b/go/catalogue/seat_invokes.go @@ -18,3 +18,18 @@ func SeatInvoke(invoke string) (seat, verb string, ok bool) { seat, verb, ok = strings.Cut(rest, ".") return seat, verb, ok && seat != "" && verb != "" } + +// QueueInvoke splits an invoke `seat:.accept.`, by which an asker names a build seat's queue it asks (design 47 G +// §544, §601, A-231), into its seat and queue; ok is false for anything else. The one reader of that form: the manifest +// validator, the indexer's served-verb edge and the grants part read it here. +func QueueInvoke(invoke string) (seat, queue string, ok bool) { + seat, verb, ok := SeatInvoke(invoke) + if !ok { + return "", "", false + } + queue, isQueue := strings.CutPrefix(verb, "accept.") + if !isQueue || queue == "" || strings.Contains(queue, ".") { + return "", "", false + } + return seat, queue, true +} diff --git a/go/catalogue/seats.go b/go/catalogue/seats.go index 9c0c11c2..41630ba4 100644 --- a/go/catalogue/seats.go +++ b/go/catalogue/seats.go @@ -45,9 +45,15 @@ 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 // 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 + // AcceptedOn are the accepted kinds that ride a stream, each as `.`, which a part names in `invokes` as + // `seat:..` (design 47 G A-102, §415). Compiled from the definition's accepts. + AcceptedOn []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