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 }