Files
mesh-delivery/modules/mesh-grants/internal/grants/waits.go
T
jschoubben c1a1b027b3 GR10 (A-12, A-10): the grants part says wait-held and wait-ended as its seat's events, and a release ends the wait as released
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
2026-11-11 21:45:03 +01:00

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
}