A release writes an ask to compose; the pass says wait-ended how released once, and a wait whose grant came says how ran. The events go through Conn.EmitSeat. The late finding is the watchman's (design 47 G §269), not written here. Builds: design 47 G §276, §266, §269, G-20 A-10, A-12 Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
192 lines
6.3 KiB
Go
192 lines
6.3 KiB
Go
package grants
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
|
|
"git.novox.be/novox/mesh-contracts/identity-and-access/go/seats/meshgrants"
|
|
)
|
|
|
|
// **The granting wait** (design 47 G §276, §266, §269; G-20 A-7). The grants part holds the entries of a revision whose
|
|
// accounts its new version has not yet composed: each module the pass leaves without a grant (MissingGrants) is one held
|
|
// entry, kept in the part's own state on the bus under KeyWaits. The bound's clock, `clock-from`, is the start of this
|
|
// version's first pass, which the part knows itself, so it hears no `build.ended`; the bound is GrantingBound, a constant of
|
|
// the part. A held entry ends when its grant is there (`ran`) or when a person releases it (`released`).
|
|
|
|
// KeyWaits is the key of the part's own state user-list where it keeps the granting waits.
|
|
const KeyWaits = "waits"
|
|
|
|
// GrantingBound is §266's bound of the granting step.
|
|
const GrantingBound = 10 * time.Minute
|
|
|
|
// HeldWait is one granting wait as the part keeps it.
|
|
type HeldWait struct {
|
|
meshgrants.Wait
|
|
// Released says a person released the entry (grants_release): it is held no more.
|
|
Released bool `json:"released,omitempty"`
|
|
// Ended says wait-ended was said for the release: a released entry is said ended once.
|
|
Ended bool `json:"ended,omitempty"`
|
|
}
|
|
|
|
// WaitsState is the value of KeyWaits, by the held module.
|
|
type WaitsState struct {
|
|
V int `json:"v"`
|
|
Waits map[string]HeldWait `json:"waits"`
|
|
}
|
|
|
|
func (p *Part) readWaits() (WaitsState, error) {
|
|
st := WaitsState{V: 1, Waits: map[string]HeldWait{}}
|
|
raw, _, found, err := p.Store.Get(StateUserList, KeyWaits)
|
|
if err != nil {
|
|
return st, fmt.Errorf("its own %s %s cannot be read: %w", StateUserList, KeyWaits, err)
|
|
}
|
|
if found {
|
|
if err := json.Unmarshal(raw, &st); err != nil {
|
|
return st, fmt.Errorf("its own %s %s does not read: %w", StateUserList, KeyWaits, err)
|
|
}
|
|
if st.Waits == nil {
|
|
st.Waits = map[string]HeldWait{}
|
|
}
|
|
}
|
|
return st, nil
|
|
}
|
|
|
|
// settleWaits keeps the granting waits of this pass: a module left without a grant is a held entry, begun at this pass; one
|
|
// that has its grant is ended as `ran`. A released entry stays released while it is still left without one.
|
|
func (p *Part) settleWaits(res *Result, in Inputs, missing []string) {
|
|
if p.Store == nil {
|
|
return
|
|
}
|
|
now := p.now()
|
|
p.mu.Lock()
|
|
if p.firstPass.IsZero() {
|
|
p.firstPass = now
|
|
}
|
|
clock := p.firstPass
|
|
p.mu.Unlock()
|
|
st, err := p.readWaits()
|
|
if err != nil {
|
|
res.Errors = append(res.Errors, err.Error())
|
|
return
|
|
}
|
|
held := map[string]bool{}
|
|
for _, m := range missing {
|
|
module, _, _ := strings.Cut(m, ": ")
|
|
held[module] = true
|
|
}
|
|
changed := false
|
|
say := func(event string, w meshgrants.Wait) bool {
|
|
if p.Emit == nil {
|
|
return true
|
|
}
|
|
if err := p.Emit.EmitSeat(meshgrants.Seat, event, w); err != nil {
|
|
res.Errors = append(res.Errors, fmt.Sprintf("the seat event %s of %s could not be said: %v", event, w.Module, err))
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
for _, module := range sortedKeys(held) {
|
|
if _, ok := st.Waits[module]; ok {
|
|
continue
|
|
}
|
|
w := HeldWait{Wait: meshgrants.Wait{Bound: GrantingBound.String(), ClockFrom: clock, Fp: in.Fingerprints[module],
|
|
Module: module, Part: "mesh-grants", Since: now, Step: meshgrants.WaitStepGrant, Version: in.Fingerprints["mesh-grants"]}}
|
|
if !say(meshgrants.EventWaitHeld, w.Wait) {
|
|
continue
|
|
}
|
|
st.Waits[module] = w
|
|
changed = true
|
|
}
|
|
for _, module := range sortedKeys(st.Waits) {
|
|
w := st.Waits[module]
|
|
switch {
|
|
case w.Released && !w.Ended:
|
|
// A-12: the released entry goes on, the wait is said ended `released`, and nothing is raised late for it.
|
|
ended := w.Wait
|
|
ended.How = meshgrants.WaitHowReleased
|
|
if say(meshgrants.EventWaitEnded, ended) {
|
|
w.Ended = true
|
|
st.Waits[module] = w
|
|
changed = true
|
|
}
|
|
case !held[module]:
|
|
if !w.Released {
|
|
ended := w.Wait
|
|
ended.How = meshgrants.WaitHowRan
|
|
if !say(meshgrants.EventWaitEnded, ended) {
|
|
continue
|
|
}
|
|
}
|
|
delete(st.Waits, module)
|
|
changed = true
|
|
}
|
|
}
|
|
if !changed {
|
|
return
|
|
}
|
|
data, _ := json.Marshal(st)
|
|
p.changed(res, StateUserList, KeyWaits, data)
|
|
}
|
|
|
|
// WaitsSaid is what the tool grants_waits answers: each held granting wait, and which have passed their bound.
|
|
type WaitsSaid struct {
|
|
Waits []HeldWait `json:"waits"`
|
|
// Late is the held entries whose bound, counted from clock-from, has passed.
|
|
Late []string `json:"late,omitempty"`
|
|
}
|
|
|
|
// WaitsOf reads the granting waits (grants_waits).
|
|
func (p *Part) WaitsOf() (any, error) {
|
|
st, err := p.readWaits()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out := WaitsSaid{Waits: []HeldWait{}}
|
|
for _, module := range sortedKeys(st.Waits) {
|
|
w := st.Waits[module]
|
|
out.Waits = append(out.Waits, w)
|
|
if !w.Released && !w.ClockFrom.Add(GrantingBound).After(p.now()) {
|
|
out.Late = append(out.Late, module)
|
|
}
|
|
}
|
|
sort.Strings(out.Late)
|
|
return out, nil
|
|
}
|
|
|
|
// ReleaseWait releases one held entry by a person's act with why, recorded as a hand act through record (the seat
|
|
// mesh-hand-acts) before anything is changed (grants_release).
|
|
func (p *Part) ReleaseWait(caller, module, why string, record func(what, why string) error) (any, error) {
|
|
if strings.TrimSpace(why) == "" {
|
|
return nil, errors.New("grants_release needs why: it is recorded as a hand act")
|
|
}
|
|
st, err := p.readWaits()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
w, ok := st.Waits[module]
|
|
if !ok {
|
|
return nil, fmt.Errorf("the grants part holds no granting wait for %q: grants_waits lists them", module)
|
|
}
|
|
if record != nil {
|
|
if err := record("grants_release "+module, why); err != nil {
|
|
return nil, fmt.Errorf("the hand act could not be recorded, so nothing was released: %w", err)
|
|
}
|
|
}
|
|
w.Released, w.By, w.How = true, caller+": "+why, meshgrants.WaitHowReleased
|
|
st.Waits[module] = w
|
|
data, _ := json.Marshal(st)
|
|
if _, err := p.Store.Put(StateUserList, KeyWaits, data); err != nil {
|
|
return nil, fmt.Errorf("the release could not be kept: %w", err)
|
|
}
|
|
// The part's process says wait-ended at the pass this ask makes (only it holds the grant to publish a seat's event).
|
|
ask, _ := json.Marshal(Ask{By: caller, At: p.now()})
|
|
if _, err := p.Store.Put(StateUserList, KeyAsked, ask); err != nil {
|
|
return nil, fmt.Errorf("the release was kept, but the ask to compose could not be written: %w", err)
|
|
}
|
|
return map[string]any{"released": module, "by": w.By}, nil
|
|
}
|