A tool's callers list named modules ("node-tools", "mesh-healers"). A-295: an entry `seat:<seat>`
allows a module caller whose module holds that seat, as a holder function the part is given answers
it from what it last heard of the assigner's seat-holders view, so no call is made per check.
VerbCallers.AllowedWith and seatlist.RefusalWith/ToolsWith take the function; Allowed, Refusal and
Tools keep their form (no seat entry allows anyone without one); a seatlist.Part is given it by
Holders.
Builds: design 47 G A-210, A-295
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
140 lines
4.9 KiB
Go
140 lines
4.9 KiB
Go
package seatlist
|
|
|
|
import (
|
|
"errors"
|
|
"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"
|
|
)
|
|
|
|
// A part that reads the seat list wires its reader one way (novox/hq ADR 0636 §1, §7): started from the bootstrapped
|
|
// definitions, what it holds given to catalogue at once and at every change, read again when the part's path for the
|
|
// seat's events becomes live, and its two tools `<prefix>_seat_list` and `<prefix>_seat_list_again` served over whatever
|
|
// reader the part holds at the call. Part is that wiring, so no part writes it again; the part names its tools' prefix
|
|
// and the callers that may read again, which are its own to give (stdio.VerbCallers' entries).
|
|
|
|
// Part is a part's reader of the seat list, set once Follow starts it; before, the list counts as unread and the part
|
|
// acts on nothing new.
|
|
type Part struct {
|
|
prefix string
|
|
callers []string
|
|
reader atomic.Pointer[Reader]
|
|
holders atomic.Pointer[func(seat string) []string]
|
|
}
|
|
|
|
// Holders gives the part the function that answers a seat's holders, by which its callers' entries `seat:<seat>` are
|
|
// resolved when a call is checked (design 47 G A-295): the part's follower of the assigner's seat-holders view.
|
|
func (p *Part) Holders(h func(seat string) []string) { p.holders.Store(&h) }
|
|
|
|
func (p *Part) holdersOf(seat string) []string {
|
|
if h := p.holders.Load(); h != nil && *h != nil {
|
|
return (*h)(seat)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// NewPart is the wiring of a part whose tools are named `<prefix>_seat_list` and `<prefix>_seat_list_again`, the second
|
|
// answered only to callers.
|
|
func NewPart(prefix string, callers []string) *Part {
|
|
return &Part{prefix: prefix, callers: append([]string(nil), callers...)}
|
|
}
|
|
|
|
// Follow starts the reader over bus from the bootstrapped definitions: what it holds is given to catalogue at once and at
|
|
// every change, and changed, when given, is called after each change (the part acts again). A reader that cannot
|
|
// subscribe is said through logf and still held, from the bootstrap. It returns the reader.
|
|
func (p *Part) Follow(bus Bus, changed func(), logf func(string, ...any)) *Reader {
|
|
reader, err := Start(Config{Bus: bus, Bootstrap: Bootstrapped(),
|
|
OnChange: func(seats []catalogue.Seat) {
|
|
catalogue.UseSeats(seats)
|
|
if changed != nil {
|
|
changed()
|
|
}
|
|
}})
|
|
if err != nil && logf != nil {
|
|
logf("%s cannot follow its seat list, so it acts only on the seats the bootstrapped definitions name: %v", p.prefix, err)
|
|
}
|
|
if reader != nil {
|
|
catalogue.UseSeats(reader.Seats())
|
|
p.reader.Store(reader)
|
|
}
|
|
return reader
|
|
}
|
|
|
|
// Reader is the reader Follow started, or nil before.
|
|
func (p *Part) Reader() *Reader {
|
|
if p == nil {
|
|
return nil
|
|
}
|
|
return p.reader.Load()
|
|
}
|
|
|
|
// Definition is the seat-definition function process.Conn.AnswerSeat takes, written once for every holder (design 47 G
|
|
// A-278, A-186): the definition of seat on the list the part follows, read at each call through reader (a Part's
|
|
// Reader, or a part's own reader), none until the list is read and none for a seat the list does not hold.
|
|
func Definition(reader func() *Reader, seat string) func() (seatdef.Definition, bool) {
|
|
return func() (seatdef.Definition, bool) {
|
|
r := reader()
|
|
if r == nil || !r.Status().Heard {
|
|
return seatdef.Definition{}, false
|
|
}
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
d, ok := r.defs[seat]
|
|
return d, ok
|
|
}
|
|
}
|
|
|
|
// MayAct is the reader's MayAct, false before it is started.
|
|
func (p *Part) MayAct(seats ...string) bool {
|
|
r := p.Reader()
|
|
return r != nil && r.MayAct(seats...)
|
|
}
|
|
|
|
// Heard says the seat has answered the reader (Status's Heard), false before it is started.
|
|
func (p *Part) Heard() bool {
|
|
r := p.Reader()
|
|
return r != nil && r.Status().Heard
|
|
}
|
|
|
|
// Health is why the list is not read, or empty when it is.
|
|
func (p *Part) Health() string {
|
|
r := p.Reader()
|
|
if r == nil {
|
|
return "the seat list reader has not started yet"
|
|
}
|
|
return r.Status().Health()
|
|
}
|
|
|
|
// EventsLive is called when the part's path for the seat's events is heard: while the list is unread it is read now, on a
|
|
// goroutine of its own (Reader.ReloadIfUnread).
|
|
func (p *Part) EventsLive() {
|
|
if r := p.Reader(); r != nil {
|
|
r.ReloadIfUnread()
|
|
}
|
|
}
|
|
|
|
// ToolNames are the two tools' names.
|
|
func (p *Part) ToolNames() (read, again string) {
|
|
return p.prefix + "_seat_list", p.prefix + "_seat_list_again"
|
|
}
|
|
|
|
// Tools are the reader's two tools (Tools) over whatever reader the part holds at the call: before Follow, each says the
|
|
// reader has not started.
|
|
func (p *Part) Tools() []stdio.Tool {
|
|
read, again := p.ToolNames()
|
|
shape := ToolsWith(&Reader{}, read, again, p.callers, p.holdersOf)
|
|
for i := range shape {
|
|
run := i
|
|
shape[i].Run = func(args map[string]any) (any, error) {
|
|
r := p.Reader()
|
|
if r == nil {
|
|
return nil, errors.New("the seat list reader has not started yet: the runtime has not served this module")
|
|
}
|
|
return ToolsWith(r, read, again, p.callers, p.holdersOf)[run].Run(args)
|
|
}
|
|
}
|
|
return shape
|
|
}
|