Main reverted G's half merge (#189, reverted by8afc3ebin #191) and then took the reader release's S1-S5, which copied G's code. G's form holds where they differ (process_bus.go, seats.go, state.go and their tests); the release's kept names stay so main's callers keep building: SeatViewPrefix beside SeatReadPrefix, and QueueInvoke's empty strings on not-ok (with its test TestQueueInvoke). The tree differs from G's headf30dee54only by those and the release's two tests (objects cap option, seat states and stream kinds). Builds: design 47 G (the milestone's sdk part, as atf30dee54) Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
101 lines
3.4 KiB
Go
101 lines
3.4 KiB
Go
package approval
|
|
|
|
import (
|
|
"fmt"
|
|
"time"
|
|
)
|
|
|
|
// UnreadableAfter is how long the state approvals fails before the runner's finding says so (§19).
|
|
const UnreadableAfter = 5 * time.Minute
|
|
|
|
// unreadableKey is the bound key whose expiry raises `approvals-unreadable` (lead answer A-157).
|
|
func (g *Gate) unreadableKey() string { return g.d.Node + ".unreadable." + StateName }
|
|
|
|
// held says why no marked call can be asked now, or "" (§19): no module holds operator-channel, as the assigner's view
|
|
// of the seat's holders says, or that view is not read.
|
|
func (g *Gate) held() string {
|
|
if g.d.Channel == nil {
|
|
return "the tool runner reaches no operator channel to ask on"
|
|
}
|
|
if g.d.Holder == nil && g.d.Holding {
|
|
return "" // a process holder reads no view of the seats' holders: an ask nobody takes is said by its gate-unanswered (design 47 G §677)
|
|
}
|
|
if g.d.Holder == nil {
|
|
return "the tool runner reads no view of the seats' holders, so it cannot tell that a module holds " + g.d.Channel.Seat()
|
|
}
|
|
on := g.d.Channel.Seat()
|
|
ok, err := g.d.Holder(on)
|
|
switch {
|
|
case err != nil:
|
|
return fmt.Sprintf("whether a module holds %s cannot be read: %v", on, err)
|
|
case !ok:
|
|
return "no module holds " + on + ", so nobody can be asked"
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// stateFailed notes that the state approvals could not be read or written: no marked call runs on this machine until it
|
|
// can, the failed read or write is made again at the next marked call and at the next membership change (A-157), and
|
|
// five minutes on the runner's finding says it (§19).
|
|
func (g *Gate) stateFailed(err error) {
|
|
g.mu.Lock()
|
|
first := g.failingSince.IsZero()
|
|
if first {
|
|
g.failingSince = g.d.Now()
|
|
}
|
|
g.mu.Unlock()
|
|
if first {
|
|
g.d.Logf("[approval] the state %s on %s cannot be read or written (%v): no marked call runs here until it can", StateName, g.d.Node, err)
|
|
if g.d.Bounds != nil && !g.d.Holding {
|
|
if e := g.d.Bounds.CreateTTL(g.unreadableKey(), map[string]string{"since": g.d.Now().UTC().Format(time.RFC3339)}, UnreadableAfter); e != nil {
|
|
g.d.Logf("[approval] the bound of the state %s's failure is not kept: %v", StateName, e)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// stateRead notes that the state approvals was read or written: a failure noted before ends, its finding cleared.
|
|
func (g *Gate) stateRead() {
|
|
g.mu.Lock()
|
|
was := !g.failingSince.IsZero()
|
|
g.failingSince = time.Time{}
|
|
g.mu.Unlock()
|
|
if !was {
|
|
return
|
|
}
|
|
if g.d.Bounds != nil && !g.d.Holding {
|
|
_ = g.d.Bounds.Delete(g.unreadableKey())
|
|
}
|
|
g.finding(StateName, KindApprovalsUnreadable, false, "the state "+StateName+" is read again", "")
|
|
}
|
|
|
|
// failing says the state approvals is failing now.
|
|
func (g *Gate) failing() bool {
|
|
g.mu.Lock()
|
|
defer g.mu.Unlock()
|
|
return !g.failingSince.IsZero()
|
|
}
|
|
|
|
// StateAgain makes the failed read of the state approvals again: at the next membership change the runner hears (A-157).
|
|
func (g *Gate) StateAgain() {
|
|
if g.d.State == nil || !g.failing() {
|
|
return
|
|
}
|
|
if _, err := g.d.State.Keys(); err != nil {
|
|
return
|
|
}
|
|
g.stateRead()
|
|
}
|
|
|
|
// unreadableExpired is the bound key of a failing state expiring: the finding raised while the state still fails.
|
|
func (g *Gate) unreadableExpired() {
|
|
g.mu.Lock()
|
|
since := g.failingSince
|
|
g.mu.Unlock()
|
|
if since.IsZero() {
|
|
return
|
|
}
|
|
g.finding(StateName, KindApprovalsUnreadable, true, fmt.Sprintf("the state %s on %s cannot be read or written since %s: no marked call "+
|
|
"runs here until it can", StateName, g.d.Node, since.UTC().Format(time.RFC3339)), "approvals unreadable on "+g.d.Node)
|
|
}
|