Files
mesh-sdk/go/delivery/window.go
T
jschoubben 5e67b548a0 R1-F-sdk 4: one rule every window keeper steps by, and a read naming no node closes none
distribution's two windows and the builder's old-form window each closed for good on a read that named
no node (an empty node_haves, an unread placement): the second of a kind (security-upgrade review F3).
delivery.StepWindow over delivery.WindowHeld is the one rule both call; a read naming no node judges
nothing, as a first view's empty answer at revision 0 (ADR 0615).

Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-04 16:24:02 +01:00

65 lines
2.6 KiB
Go

package delivery
import (
"slices"
"sort"
"time"
)
// WindowHeld is the part of a window every window keeper shares (design 51: distribution's old seat name and direct
// writes, the builder's old reference form): whether it is open, the nodes holding it and since when, and when it last
// closed and who held it then. A keeper embeds it in its own record, so its JSON is the record's.
type WindowHeld struct {
Open bool `json:"open"`
Holding []string `json:"holding,omitempty"`
Since map[string]time.Time `json:"since,omitempty"`
Closed time.Time `json:"closed,omitzero"`
// LastHolding are the nodes that held it last, said when it closes.
LastHolding []string `json:"last_holding,omitempty"`
}
// StepWindow is the one rule every window keeper steps its window by (security-upgrade review F3, the second of a
// kind): named is every node the read named, dropped ones included, and holding those of them that hold the window at
// now. A read that names no node judges nothing, as a first view's empty answer at revision 0 is no empty state (ADR
// 0615): an empty node_haves or a placement not yet read never closes a window, and the window stays as it was. A read
// naming nodes, none holding, closes an open window (closed true, once); one naming a holder opens it, each holder's
// since kept from w. overdue are the holders whose since is at least findingAfter before now (the keeper's finding,
// cleared for every other node); a read naming no node keeps w's holders' overdue as they stand at now.
func StepWindow(w WindowHeld, named, holding []string, now time.Time, findingAfter time.Duration) (next WindowHeld, closed bool, overdue []string) {
now = now.UTC()
if len(named) == 0 {
next = w
for _, n := range w.Holding {
if since, ok := w.Since[n]; ok && now.Sub(since) >= findingAfter {
overdue = append(overdue, n)
}
}
return next, false, overdue
}
if len(holding) == 0 {
next = WindowHeld{Closed: w.Closed, LastHolding: w.LastHolding}
if w.Open {
next.Closed, next.LastHolding = now, slices.Clone(w.Holding)
if next.LastHolding == nil {
next.LastHolding = []string{}
}
return next, true, nil
}
return next, false, nil
}
held := slices.Clone(holding)
sort.Strings(held)
next = WindowHeld{Open: true, Holding: held, Since: map[string]time.Time{}, Closed: w.Closed, LastHolding: w.LastHolding}
for _, n := range held {
since, had := w.Since[n]
if !had {
since = now
}
next.Since[n] = since
if now.Sub(since) >= findingAfter {
overdue = append(overdue, n)
}
}
return next, false, overdue
}