W8.2: the seat-list wiring is mesh-sdk's seatlist.Part; seatlist.go is deleted

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ

Builds: design 47 G §204
This commit is contained in:
2026-11-11 14:42:03 +01:00
parent 58b3b030db
commit ac2c4d3e20
3 changed files with 28 additions and 94 deletions
+25 -3
View File
@@ -40,6 +40,7 @@ import (
stdio "git.novox.be/novox/mesh-sdk/go"
"git.novox.be/novox/mesh-sdk/go/announce"
"git.novox.be/novox/mesh-sdk/go/process"
"git.novox.be/novox/mesh-sdk/go/seatdef"
"git.novox.be/novox/mesh-sdk/go/seatlist"
"git.novox.be/novox/mesh-sdk/go/views"
@@ -148,8 +149,8 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro
}}
f.Part.Bindings.Start(ctx, f.Part)
// The seat list: read over the connection and the hub, given to catalogue at every change, and a pass composes again then.
gate := &seatGate{}
gate.follow(seatlist.ProcessBus(conn.Call, hub), f.Wake, logger.Printf)
gate := newSeatGate()
gate.Follow(seatlist.ProcessBus(conn.Call, hub), f.Wake, logger.Printf)
f.Part.SeatList = gate
f.Part.Say = sayCond
fv.Start(ctx, f.Wake)
@@ -220,7 +221,7 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro
if err := hub.Start(ctx, js, account); err != nil {
return err
}
gate.eventsLive()
gate.EventsLive()
hears, err := grants.OwnHears(ownStore{conn}, account.Node)
if err != nil {
return err
@@ -292,3 +293,24 @@ func hearRunning(ctx context.Context, account process.Account, js jetstream.JetS
hearingRunning.cc = cc
return nil
}
// seatGate is the part's seat-list wiring (mesh-sdk seatlist.Part, ADR 0636 §1, §7) that also says the definitions the
// list holds, which the part makes each kept event's stream from.
type seatGate struct{ *seatlist.Part }
// seatListAgainCallers may read the seat list again (ADR 0636 §7): every operator's session and the healers. A call through
// the mesh MCP server is proved only as its machine's tool runner (<node>.node-tools, issue 627), so node-tools stands for an
// operator's session until a session is proved (issue 1966).
var seatListAgainCallers = []string{stdio.AnyPerson, "node-tools", "mesh-healers"}
func newSeatGate() *seatGate {
return &seatGate{seatlist.NewPart("grants", seatListAgainCallers)}
}
// Definitions are the seat definitions the reader holds; none before it has started.
func (g *seatGate) Definitions() map[string]seatdef.Definition {
if r := g.Reader(); r != nil {
return r.Definitions()
}
return nil
}
@@ -1,88 +0,0 @@
package main
import (
"errors"
"mesh-grants/internal/grants"
"sync/atomic"
stdio "git.novox.be/novox/mesh-sdk/go"
"git.novox.be/novox/mesh-sdk/go/catalogue"
"git.novox.be/novox/mesh-sdk/go/seatdef"
"git.novox.be/novox/mesh-sdk/go/seatlist"
)
// The grants part composes from the seat list it reads (novox/hq ADR 0635 §1, ADR 0636 §1, D-30): through the seat
// mesh-catalog's verb seat-definitions and its two events, from the bootstrapped definitions until the seat has answered. What
// the reader holds is given to catalogue at every change, which composes every membership and grant, and a module that names a
// seat the list does not yet is held back, as a manifest waiting for a judge is, and what runs is composed from its running
// build's manifest. The process reads over its connection and the event hub; the tools' bundle over the runtime.
// seatListTools are the reader's two tools (ADR 0636 §7): every operator's session and the healers may read again. A call
// through the mesh MCP server is proved only as its machine's tool runner (<node>.node-tools, issue 627), so node-tools
// stands for an operator's session until a session is proved (issue 1966).
var seatListAgainCallers = []string{stdio.AnyPerson, "node-tools", "mesh-healers"}
// seatGate is the reader the part gates by, set once it is started; before, it acts on nothing new.
type seatGate struct {
reader atomic.Pointer[seatlist.Reader]
}
func (g *seatGate) MayAct(seats ...string) bool {
r := g.reader.Load()
return r != nil && r.MayAct(seats...)
}
// Definitions are the seat definitions the reader holds: what the part makes each kept event's stream from.
func (g *seatGate) Definitions() map[string]seatdef.Definition {
if r := g.reader.Load(); r != nil {
return r.Definitions()
}
return nil
}
// follow starts the reader over bus: what it holds is given to catalogue at once and at every change, and changed is called
// then (a pass composes again).
func (g *seatGate) follow(bus seatlist.Bus, changed func(), logf func(string, ...any)) *seatlist.Reader {
reader, err := seatlist.Start(seatlist.Config{Bus: bus, Bootstrap: seatlist.Bootstrapped(),
OnChange: func(seats []catalogue.Seat) {
catalogue.UseSeats(seats)
if changed != nil {
changed()
}
}})
if err != nil {
logf("the grants part cannot follow its seat list, so it composes only the modules the bootstrapped seats name: %v", err)
}
if reader != nil {
catalogue.UseSeats(reader.Seats())
g.reader.Store(reader)
}
return reader
}
// eventsLive is called when the part's own consumer on EVENTS is heard. A seat that was silent at the part's start may have said it
// speaks before that consumer existed, which starts at the stream's end, so its event is never heard: while the list is unread the
// part reads it now (ADR 0636 §1), on a goroutine of its own so the pass is not held; the reader changes the catalogue and wakes
// the part when the seat answers, and stays unread, to be read at the seat's next event, when it does not.
func (g *seatGate) eventsLive() {
if r := g.reader.Load(); r != nil {
r.ReloadIfUnread()
}
}
// tools are the two tools over whatever reader the gate holds at the call.
func (g *seatGate) tools() []stdio.Tool {
names := []string{grants.ToolSeatList, grants.ToolSeatListAgain}
shape := seatlist.Tools(&seatlist.Reader{}, names[0], names[1], seatListAgainCallers)
for i := range shape {
run := i
shape[i].Run = func(args map[string]any) (any, error) {
r := g.reader.Load()
if r == nil {
return nil, errors.New("the seat list reader has not started yet: the runtime has not served this module")
}
return seatlist.Tools(r, names[0], names[1], seatListAgainCallers)[run].Run(args)
}
}
return shape
}
+3 -3
View File
@@ -266,14 +266,14 @@ func recordHandAct(what, why string) error {
// serveTools serves the tools to the runtime until it stops.
func serveTools() error {
tv := &toolViews{}
gate := &seatGate{}
gate := newSeatGate()
p := &grants.Part{Read: runtimeReader{views: tv}, Call: runtimeBus{}, Store: runtimeStore{}, Emit: runtimeBus{}, SeatList: gate}
// The reader asks through the runtime, which serves once Serve has its channel.
go func() {
<-stdio.Served()
gate.follow(seatlist.StdioBus(), nil, func(f string, a ...any) { fmt.Fprintf(os.Stderr, "mesh-grants: "+f+"\n", a...) })
gate.Follow(seatlist.StdioBus(), nil, func(f string, a ...any) { fmt.Fprintf(os.Stderr, "mesh-grants: "+f+"\n", a...) })
}()
tools := append(toolsOf(p), gate.tools()...)
tools := append(toolsOf(p), gate.Tools()...)
for i := range tools {
run := tools[i].Run
tools[i].Run = func(a map[string]any) (any, error) { tv.fresh(); return run(a) }