seat list: the bus of a process on its own credential (a verb called through its connection, the seat's events as handlers of the module's event hub), so the checker and the grants part read the list as every part does (B7e; issue 1966)

Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
This commit is contained in:
2026-11-05 15:35:19 +01:00
parent 4111794690
commit 9489385165
2 changed files with 90 additions and 0 deletions
+44
View File
@@ -0,0 +1,44 @@
package seatlist
import (
"encoding/json"
"fmt"
"strings"
"github.com/nats-io/nats.go/jetstream"
"git.novox.be/novox/mesh-sdk/go/seatdef"
)
// EventHandlers is where a process registers the handler of a subject on the module's one pull of its durable consumer
// (process.EventHub). Register every handler, then start the hub.
type EventHandlers interface {
Handle(pattern string, handle func(jetstream.Msg)) error
}
type processBus struct {
call func(string, any) (json.RawMessage, error)
hub EventHandlers
}
// ProcessBus is the bus of a process on its own credential: a verb of the seat is called through its connection
// (process.Conn.Call), and the seat's events are heard as handlers of the module's event hub, on the seat's own subject
// (`mesh.seat.<seat>.event.<event>`). The process starts the hub after the reader is started, so every handler is registered.
func ProcessBus(call func(string, any) (json.RawMessage, error), hub EventHandlers) Bus {
return processBus{call: call, hub: hub}
}
func (b processBus) Ask(key string, body any) (json.RawMessage, error) { return b.call(key, body) }
func (b processBus) Subscribe(pattern string, handle func(key string, body json.RawMessage) error) error {
seat, event, ok := strings.Cut(pattern, ".")
if !ok || seat == "" || event == "" {
return fmt.Errorf("%q is no seat event's pattern, <seat>.<event>", pattern)
}
return b.hub.Handle(seatdef.EventSubject(seat, event), func(m jetstream.Msg) {
// A reader that could not take the event says so in its status and reads again at the next event or by hand
// (Reload): the message is not delivered again for it.
_ = handle(pattern, m.Data())
_ = m.Ack()
})
}
+46
View File
@@ -0,0 +1,46 @@
package seatlist
import (
"encoding/json"
"testing"
"github.com/nats-io/nats.go/jetstream"
)
type hubStub map[string]func(jetstream.Msg)
func (h hubStub) Handle(pattern string, f func(jetstream.Msg)) error { h[pattern] = f; return nil }
type msgStub struct {
jetstream.Msg
data []byte
acked *int
}
func (m msgStub) Data() []byte { return m.data }
func (m msgStub) Ack() error { *m.acked++; return nil }
func TestAProcessBusAsksByKeyAndHearsTheSeatsEventsOnTheHub(t *testing.T) {
var asked string
hub := hubStub{}
bus := ProcessBus(func(key string, body any) (json.RawMessage, error) { asked = key; return json.RawMessage(`{}`), nil }, hub)
if _, err := bus.Ask(Key(), nil); err != nil || asked != Key() {
t.Fatalf("asked %q, %v", asked, err)
}
var got []string
if err := bus.Subscribe(Pattern(ChangedEvent), func(key string, body json.RawMessage) error { got = append(got, key+" "+string(body)); return nil }); err != nil {
t.Fatal(err)
}
h := hub["mesh.seat."+Seat+".event."+ChangedEvent]
if h == nil {
t.Fatalf("handlers on %v, want the seat's own subject", hub)
}
acked := 0
h(msgStub{data: []byte(`{"seat":"x"}`), acked: &acked})
if len(got) != 1 || got[0] != Pattern(ChangedEvent)+` {"seat":"x"}` || acked != 1 {
t.Errorf("heard %v, acked %d", got, acked)
}
if err := bus.Subscribe("nodot", nil); err == nil {
t.Error("a pattern that names no seat event was taken")
}
}