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
65 lines
2.6 KiB
Go
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
|
|
}
|