Files
mesh-tools/node-tools/internal/firstview/approval.go
T
jschoubben d350c98823 Join 11: the merged heads compile where no area's fix is owed
The join's importers name the delivery repository's moved contracts and sdk
(MV1), node-tools' tests use mesh-sdk's gate, the duplicate node-engine claim
rule of the reader release gives way to G's, and the assigner names its first
watched branch, never a constant. The seat holders' AnswerSeat callers are
area 6's.

Builds: design 47 G join 11 (merge of fix round 11)

Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 10:41:44 +01:00

197 lines
5.0 KiB
Go

package firstview
import (
"context"
"strconv"
"strings"
"time"
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/clients/meshcatalogclient"
"git.novox.be/novox/mesh-sdk/go/approval"
"git.novox.be/novox/mesh-sdk/go/seatdef"
runnerapproval "github.com/novox/mesh-tools/node-tools/internal/approval"
)
// Findings is the runner's held finding writer, the one the gate writes its findings through too (design 46 7-approval-mark
// §4, §16, §19); nil when it was not made.
func (v *Views) Findings() runnerapproval.HeldWriter {
if v.deps.Findings == nil {
return nil
}
return v.deps.Findings
}
// ApprovalCatalog is the catalog half as the gate reads its marks (design 46 7-approval-mark §5): the entries the branch
// follower holds, an entry by fingerprint asked of `mesh-catalog.entry` when no branch lists it, and the seat definitions
// the seat list holds (defs).
func (v *Views) ApprovalCatalog(defs func() map[string]seatdef.Definition) approval.Catalog {
return approvalCatalog{v: v, defs: defs}
}
type approvalCatalog struct {
v *Views
defs func() map[string]seatdef.Definition
}
func (c approvalCatalog) Ready() error {
if c.v.branches == nil {
return errNotFollowed
}
return c.v.branches.Ready()
}
func (c approvalCatalog) Revision() string {
if c.v.branches == nil {
return ""
}
for _, branch := range c.v.branches.Followed() {
if cat, ok := c.v.branches.Catalog(branch); ok {
if half, ok := cat.View(); ok {
return strconv.FormatInt(half.Revision.Revision, 10)
}
}
}
return ""
}
func (c approvalCatalog) Served(fingerprint string) (approval.Entry, bool, string) {
if c.v.branches == nil {
return approval.Entry{}, false, errNotFollowed.Error()
}
e, state := c.v.branches.Resolve(context.Background(), fingerprint)
return runnerapproval.Entry(e), state == meshcatalogclient.EntryAnswered, string(state)
}
func (c approvalCatalog) Newest(module string) (approval.Entry, bool) {
if c.v.branches == nil {
return approval.Entry{}, false
}
e, ok, err := c.v.branches.Entry(module)
return runnerapproval.Entry(e), ok && err == nil
}
func (c approvalCatalog) Definitions() map[string]seatdef.Definition {
if c.defs == nil {
return nil
}
return c.defs()
}
func (c approvalCatalog) Entries() []approval.Entry {
halves, err := c.v.Halves()
if err != nil {
return nil
}
var out []approval.Entry
for _, half := range halves {
for _, e := range half.Entries {
out = append(out, runnerapproval.Entry(e))
}
}
return out
}
type notFollowed struct{}
func (notFollowed) Error() string { return "the catalog half is not followed by this runner" }
var errNotFollowed error = notFollowed{}
// heardOne wakes every wait on the picture.
func (v *Views) heardOne() {
v.mu.Lock()
defer v.mu.Unlock()
if v.heard != nil {
close(v.heard)
}
v.heard = make(chan struct{})
}
// heardNext is closed at the next event heard.
func (v *Views) heardNext() <-chan struct{} {
v.mu.Lock()
defer v.mu.Unlock()
if v.heard == nil {
v.heard = make(chan struct{})
}
return v.heard
}
// WaitCatalog waits within the bound for the catalog half's first view of every watched branch (design 46 7-approval-mark
// §19), waking at each event the runner hears and at each first view answered; nil once it is read, else why not.
func (v *Views) WaitCatalog(within time.Duration) error {
c := approvalCatalog{v: v}
timer := time.NewTimer(within)
defer timer.Stop()
for {
next := v.heardNext()
err := c.Ready()
if err == nil {
return nil
}
var ready []<-chan struct{}
if v.branches != nil {
for _, branch := range v.branches.Followed() {
if cat, ok := v.branches.Catalog(branch); ok {
select {
case <-cat.Ready(): // read already: waits on nothing
default:
ready = append(ready, cat.Ready())
}
}
}
}
if !waitAny(next, timer.C, ready) {
return c.Ready()
}
}
}
// waitAny waits for next or one of ready, true, or for the bound, false.
func waitAny(next <-chan struct{}, bound <-chan time.Time, ready []<-chan struct{}) bool {
woke := make(chan struct{}, 1)
stop := make(chan struct{})
defer close(stop)
for _, r := range ready {
go func(r <-chan struct{}) {
select {
case <-r:
woke <- struct{}{}
case <-stop:
}
}(r)
}
select {
case <-next:
case <-woke:
case <-bound:
return false
}
return true
}
// OnSeatHolder calls f whenever the assigner's seat-holders view changes for the seat (lead answer A-157: the gate asks
// mesh-hand-acts.warranted again when the hand-act seat's holder is heard again).
func (v *Views) OnSeatHolder(seat string, f func()) {
v.mu.Lock()
defer v.mu.Unlock()
if v.onKey == nil {
v.onKey = map[string][]func(){}
}
k := "seat-holders " + seat
v.onKey[k] = append(v.onKey[k], f)
}
// heardKey calls the hearers of a key of an owner's state just changed: a seat's holders, under the seat's own key and
// its `<seat>.<node>` keys.
func (v *Views) heardKey(state, key string) {
seat, _, _ := strings.Cut(key, ".")
v.mu.Lock()
fs := append([]func(){}, v.onKey[state+" "+seat]...)
v.mu.Unlock()
for _, f := range fs {
go f()
}
}