The gate sits on both handlers the runner serves, a module's tools (its ledger repairs among them) and the seat verbs its modules hold, before the bundle. The same call again is judged on the router's own record of its act's newest ask: held to the warrant (operatorchannelclient.For), the option and the act it bound (Performs), within 30 minutes of the choice, and claimed by compare-and-set (Client.Take) before it runs, so one approval runs one call. A later call is told it still waits, was declined (not asked again for 10 minutes), or asks anew after expired, approved too long ago or already run. A runner's own tool marked is refused unasked; with no state approvals bound a marked call is refused and an unmarked one runs. The gate's reach (state, publish, the router's record) is the runner's own connection, in one place; it waits for the catalog half's first view on the events the runner hears. Builds: design 46 7-approval-mark §13, §14, §15, §17, §20 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
158 lines
4.0 KiB
Go
158 lines
4.0 KiB
Go
package firstview
|
|
|
|
import (
|
|
"context"
|
|
"strconv"
|
|
"time"
|
|
|
|
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
|
catalogseat "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
|
"git.novox.be/novox/mesh-sdk/go/seatdef"
|
|
|
|
"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() approval.FindingWriter {
|
|
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) (catalogseat.CatalogEntry, bool, string) {
|
|
if c.v.branches == nil {
|
|
return catalogseat.CatalogEntry{}, false, errNotFollowed.Error()
|
|
}
|
|
e, state := c.v.branches.Resolve(context.Background(), fingerprint)
|
|
return e, state == meshcatalogclient.EntryAnswered, string(state)
|
|
}
|
|
|
|
func (c approvalCatalog) Newest(module string) (catalogseat.CatalogEntry, bool) {
|
|
if c.v.branches == nil {
|
|
return catalogseat.CatalogEntry{}, false
|
|
}
|
|
e, ok, err := c.v.branches.Entry(module)
|
|
return e, ok && err == nil
|
|
}
|
|
|
|
func (c approvalCatalog) Definitions() map[string]seatdef.Definition {
|
|
if c.defs == nil {
|
|
return nil
|
|
}
|
|
return c.defs()
|
|
}
|
|
|
|
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
|
|
}
|