F-delivery 7: the builder's old-form window steps by mesh-sdk's delivery.StepWindow
A read naming no node (an empty node_haves, the placement not read yet) closed the window for good (security review F3, second of a kind). The window embeds delivery.WindowHeld and steps by the shared rule, named being the placement's nodes before any is dropped. The builder's expiring keys share one state, bounds (the publish bound of F-delivery 5 renamed), for the old-form bound next. Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
This commit is contained in:
@@ -64,16 +64,12 @@ func Holding(fs forms.ArtifactForms, counted map[string][]string, dropped map[st
|
||||
return out
|
||||
}
|
||||
|
||||
// OldFormWindow is the window's record: whether it is open, who holds it and since when, and the nodes dropped by
|
||||
// hand; what builder_old_form answers and what `old-form-closed` carries when it closes.
|
||||
// OldFormWindow is the window's record: whether it is open, who holds it and since when (delivery.WindowHeld, the part
|
||||
// every window keeper shares), and the nodes dropped by hand; what builder_old_form answers and what `old-form-closed`
|
||||
// carries when it closes.
|
||||
type OldFormWindow struct {
|
||||
Open bool `json:"open"`
|
||||
Holding []string `json:"holding,omitempty"`
|
||||
Since map[string]time.Time `json:"since,omitempty"`
|
||||
Dropped []Dropped `json:"dropped,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"`
|
||||
delivery.WindowHeld
|
||||
Dropped []Dropped `json:"dropped,omitempty"`
|
||||
}
|
||||
|
||||
// Dropped is one retired node dropped from the count by hand (builder_old_form): a hand act with who and why.
|
||||
@@ -95,31 +91,14 @@ type OldFormClosedBody struct {
|
||||
// FindingAfter is how long a node holds the window open before it is the builder's finding naming the node.
|
||||
const FindingAfter = 7 * 24 * time.Hour
|
||||
|
||||
// Step moves the window to the nodes holding it at now: it answers the new window, whether it closed in this step (so
|
||||
// the builder says `old-form-closed` and its log line once), and the nodes holding it past FindingAfter (the finding,
|
||||
// cleared for every other node).
|
||||
func (w OldFormWindow) Step(holding []string, now time.Time) (next OldFormWindow, closed bool, overdue []string) {
|
||||
next = OldFormWindow{Dropped: w.Dropped}
|
||||
if len(holding) == 0 {
|
||||
next.Closed, next.LastHolding = w.Closed, w.LastHolding
|
||||
if w.Open {
|
||||
next.Closed, next.LastHolding = now.UTC(), w.Holding
|
||||
return next, true, nil
|
||||
}
|
||||
return next, false, nil
|
||||
}
|
||||
next.Open, next.Holding, next.Since = true, slices.Clone(holding), map[string]time.Time{}
|
||||
for _, n := range holding {
|
||||
since, had := w.Since[n]
|
||||
if !had {
|
||||
since = now.UTC()
|
||||
}
|
||||
next.Since[n] = since
|
||||
if now.Sub(since) >= FindingAfter {
|
||||
overdue = append(overdue, n)
|
||||
}
|
||||
}
|
||||
return next, false, overdue
|
||||
// Step moves the window by mesh-sdk's one window rule (delivery.StepWindow): named is every counted node the read named,
|
||||
// those dropped by hand included, and holding those of them holding the window (Holding). A read naming no node (no
|
||||
// placement read yet, an empty node_haves) judges nothing, so it never closes the window. It answers the new window,
|
||||
// whether it closed in this step (so the builder says `old-form-closed` and its log line once), and the nodes holding it
|
||||
// past FindingAfter (the finding, cleared for every other node).
|
||||
func (w OldFormWindow) Step(named, holding []string, now time.Time) (next OldFormWindow, closed bool, overdue []string) {
|
||||
held, closed, overdue := delivery.StepWindow(w.WindowHeld, named, holding, now, FindingAfter)
|
||||
return OldFormWindow{WindowHeld: held, Dropped: w.Dropped}, closed, overdue
|
||||
}
|
||||
|
||||
// Drop drops a retired node from the count by hand; dropping it again changes nothing.
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package versions
|
||||
|
||||
import "time"
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// **A publish that ends badly is loud** (design 51 *Health and conditions*; review round 1): the builder raises the
|
||||
// finding publish-refused when a holder says refused, and publish-unanswered when a version it built and numbered is
|
||||
@@ -8,12 +11,15 @@ import "time"
|
||||
// clears both. A seat's outcome that does not read is the finding seat-outcome-unread about the seat, cleared by its
|
||||
// next outcome that reads.
|
||||
//
|
||||
// The bound is a key of the state publish-bounds per version (Key), written with its own time to live when the build
|
||||
// is done and the version is still numbered, deleted at the holder's word; the bus's marker of its end, heard on the
|
||||
// watch, is what raises publish-unanswered while the version is still numbered. No timer.
|
||||
// The bound is a key of the builder's state bounds per version (PublishBoundKey), written with its own time to live
|
||||
// when the build is done and the version is still numbered, deleted at the holder's word; the bus's marker of its end,
|
||||
// heard on the watch, is what raises publish-unanswered while the version is still numbered. No timer.
|
||||
const (
|
||||
PublishBoundsState = "publish-bounds"
|
||||
PublishBoundsBucket = "mesh-delivery-builder_publish-bounds"
|
||||
// BoundsState is the builder's state of its expiring keys, each with its own time to live: a publish's bound
|
||||
// (PublishBoundKey) and a node's seven days holding the old form's window (OldFormBoundKey). Its end, the bus's
|
||||
// marker heard on the watch, is what acts.
|
||||
BoundsState = "bounds"
|
||||
BoundsBucket = "mesh-delivery-builder_bounds"
|
||||
// PublishBound is the build module's own wait for the holder's word (design 51 *How a publish ends*: at most
|
||||
// 10 minutes), so a version unanswered past it is one the holder never answered.
|
||||
PublishBound = 10 * time.Minute
|
||||
@@ -22,3 +28,23 @@ const (
|
||||
PublishUnanswered = "publish-unanswered"
|
||||
SeatOutcomeUnread = "seat-outcome-unread"
|
||||
)
|
||||
|
||||
// The prefixes of the keys of BoundsState.
|
||||
const (
|
||||
publishBoundPrefix = "publish."
|
||||
oldFormBoundPrefix = "old-form."
|
||||
)
|
||||
|
||||
// PublishBoundKey is the bound of name's version: `publish.<versions key>`.
|
||||
func PublishBoundKey(name, version string) string { return publishBoundPrefix + Key(name, version) }
|
||||
|
||||
// OldFormBoundKey is the bound of node's seven days holding the old form's window: `old-form.<node>`.
|
||||
func OldFormBoundKey(node string) string { return oldFormBoundPrefix + EscapeToken(node) }
|
||||
|
||||
// PublishBoundOf is the versions key a publish's bound names, and whether key is one; a key that is no publish's
|
||||
// bound is an old form's (IsOldFormBound) or nothing the builder wrote.
|
||||
func PublishBoundOf(key string) (string, bool) { return strings.CutPrefix(key, publishBoundPrefix) }
|
||||
|
||||
// IsOldFormBound says key is a node's bound of the old form's window: its end steps the window, which raises the
|
||||
// finding for every node past FindingAfter.
|
||||
func IsOldFormBound(key string) bool { return strings.HasPrefix(key, oldFormBoundPrefix) }
|
||||
|
||||
@@ -335,11 +335,12 @@ func TestTheBuildersVersions(t *testing.T) {
|
||||
t.Errorf("forms.Current, a node stating nothing and one stating form:seat-reference: Holding = %v, want a", got)
|
||||
}
|
||||
t0 := time.Unix(0, 0)
|
||||
w, closed, overdue := OldFormWindow{}.Step([]string{"c"}, t0)
|
||||
named := []string{"c", "d"}
|
||||
w, closed, overdue := OldFormWindow{}.Step(named, []string{"c"}, t0)
|
||||
if closed || len(overdue) != 0 || !w.Open {
|
||||
t.Fatalf("Step = %+v %v %v", w, closed, overdue)
|
||||
}
|
||||
w, _, overdue = w.Step([]string{"c"}, t0.Add(FindingAfter))
|
||||
w, _, overdue = w.Step(named, []string{"c"}, t0.Add(FindingAfter))
|
||||
if !reflect.DeepEqual(overdue, []string{"c"}) {
|
||||
t.Errorf("seven days held: overdue %v, want c", overdue)
|
||||
}
|
||||
@@ -353,7 +354,12 @@ func TestTheBuildersVersions(t *testing.T) {
|
||||
if _, err := w.Drop("e", "op@shanks", "", t0); err == nil {
|
||||
t.Error("a drop with no why is taken")
|
||||
}
|
||||
w, closed, _ = w.Step(nil, t0.Add(FindingAfter+time.Hour))
|
||||
// Security-upgrade F3 (review round 1), by mesh-sdk's delivery.StepWindow: a read naming no node (an empty
|
||||
// node_haves, the placement not read yet) never closes the window, and its holder's seven days still count.
|
||||
if held, shut, late := w.Step(nil, nil, t0.Add(FindingAfter+time.Minute)); shut || !held.Open || !reflect.DeepEqual(late, []string{"c"}) {
|
||||
t.Fatalf("a read naming no node: %+v closed=%v overdue %v, want the window open and c overdue", held, shut, late)
|
||||
}
|
||||
w, closed, _ = w.Step(named, nil, t0.Add(FindingAfter+time.Hour))
|
||||
if !closed || w.Open || !reflect.DeepEqual(w.LastHolding, []string{"c"}) || w.Closing().Dropped[0].Node != "d" {
|
||||
t.Errorf("closing: %+v closed=%v", w, closed)
|
||||
}
|
||||
@@ -361,7 +367,7 @@ func TestTheBuildersVersions(t *testing.T) {
|
||||
if back, err := DecodeOldFormWindow(raw); err != nil || !back.Closed.Equal(w.Closed) {
|
||||
t.Errorf("the window read back = %+v, %v", back, err)
|
||||
}
|
||||
if _, again, _ := w.Step(nil, t0.Add(FindingAfter+2*time.Hour)); again {
|
||||
if _, again, _ := w.Step(named, nil, t0.Add(FindingAfter+2*time.Hour)); again {
|
||||
t.Error("a closed window closes again: old-form-closed would be said twice")
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user