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
158 lines
6.4 KiB
Go
158 lines
6.4 KiB
Go
package versions
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"slices"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
|
|
"git.novox.be/novox/mesh-delivery/sdk/forms"
|
|
"git.novox.be/novox/mesh-sdk/go/delivery"
|
|
)
|
|
|
|
// The old reference form's window (design 51 *Every build keeps the old reference form*, ADR 0624 §1, ADR 0618 §2-§4,
|
|
// DLV-B9). Which forms a build key keeps is mesh-delivery's sdk/forms (Kept, Choose), the counted nodes' statements the
|
|
// builder's state engine-reads; this is only the window over them: open while a counted node-engine, present or away,
|
|
// reads of some artifact kind none of the newest form the builder knows for it, so an older form stays in new build keys
|
|
// for it; closed, said once with `old-form-closed` and a log line, when none does. It moves on what moves a statement
|
|
// (the node-engine seat's verb `manifest-reads` and its event `manifest-reads-changed`), the placement, a hand drop and
|
|
// each build key written; never on a timer.
|
|
|
|
// OldFormState is the builder's state of the window: one key, OldFormKey, an OldFormWindow. builder_old_form reads it.
|
|
const (
|
|
OldFormState = "old-form"
|
|
OldFormKey = "window"
|
|
)
|
|
|
|
// OldFormBucket is that state's bucket.
|
|
const OldFormBucket = delivery.BuilderModule + "_" + OldFormState
|
|
|
|
// OldFormHeld is the finding of a node holding the window open for FindingAfter, about that node, cleared when the
|
|
// window closes or the node no longer holds it.
|
|
const OldFormHeld = "old-form-held"
|
|
|
|
// Holding are the counted nodes, those dropped by hand left out, whose engine reads, of some artifact kind fs knows, not
|
|
// the newest form of that kind: the nodes for which an older form stays in new build keys (forms.ArtifactForms.Kept). A
|
|
// node stating nothing reads the first forms (fs.Reads), sorted.
|
|
func Holding(fs forms.ArtifactForms, counted map[string][]string, dropped map[string]bool) []string {
|
|
newest := map[string]string{} // kind -> newest form
|
|
var kinds []string
|
|
for _, f := range fs {
|
|
for _, k := range f.Kinds {
|
|
if _, ok := newest[k]; !ok {
|
|
kinds = append(kinds, k)
|
|
}
|
|
newest[k] = f.Name
|
|
}
|
|
}
|
|
var out []string
|
|
for node, stated := range counted {
|
|
if dropped[node] {
|
|
continue
|
|
}
|
|
for _, k := range kinds {
|
|
if !fs.Reads(stated, newest[k]) {
|
|
out = append(out, node)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
sort.Strings(out)
|
|
return out
|
|
}
|
|
|
|
// 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 {
|
|
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.
|
|
type Dropped struct {
|
|
Node string `json:"node"`
|
|
By string `json:"by"`
|
|
Why string `json:"why"`
|
|
At time.Time `json:"at"`
|
|
}
|
|
|
|
// OldFormClosedBody is the body of OldFormClosedEvent (design 51): when, the nodes that held it last and the nodes dropped
|
|
// by hand.
|
|
type OldFormClosedBody struct {
|
|
At time.Time `json:"at"`
|
|
LastHolding []string `json:"last_holding,omitempty"`
|
|
Dropped []Dropped `json:"dropped,omitempty"`
|
|
}
|
|
|
|
// 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 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.
|
|
func (w OldFormWindow) Drop(node, by, why string, at time.Time) (OldFormWindow, error) {
|
|
node, why = strings.TrimSpace(node), strings.TrimSpace(why)
|
|
if node == "" || why == "" {
|
|
return w, errors.New("name the retired node (node) and say why it leaves the count (why)")
|
|
}
|
|
if w.DroppedSet()[node] {
|
|
return w, nil
|
|
}
|
|
w.Dropped = append(slices.Clone(w.Dropped), Dropped{Node: node, By: by, Why: why, At: at.UTC()})
|
|
return w, nil
|
|
}
|
|
|
|
// DroppedSet is the dropped nodes, as Holding takes them.
|
|
func (w OldFormWindow) DroppedSet() map[string]bool {
|
|
out := map[string]bool{}
|
|
for _, d := range w.Dropped {
|
|
out[d.Node] = true
|
|
}
|
|
return out
|
|
}
|
|
|
|
// Closing is the event's body of a window that closed in a Step.
|
|
func (w OldFormWindow) Closing() OldFormClosedBody {
|
|
return OldFormClosedBody{At: w.Closed, LastHolding: w.LastHolding, Dropped: w.Dropped}
|
|
}
|
|
|
|
// DecodeOldFormWindow reads the state's value; none is a closed window no node holds.
|
|
func DecodeOldFormWindow(raw []byte) (OldFormWindow, error) {
|
|
var w OldFormWindow
|
|
if len(raw) == 0 {
|
|
return w, nil
|
|
}
|
|
if err := json.Unmarshal(raw, &w); err != nil {
|
|
return OldFormWindow{}, fmt.Errorf("the old-form window is not readable: %w", err)
|
|
}
|
|
return w, nil
|
|
}
|
|
|
|
// OldFormDescription is builder_old_form's description.
|
|
const OldFormDescription = "The old reference form's window (design 51 *Every build keeps the old reference form*, ADR 0624): whether it is open, " +
|
|
"the counted nodes, present or away, whose node-engine reads an older reference form alone and since when (a node holding it seven days " +
|
|
"is the builder's finding " + OldFormHeld + "), when it last closed and who held it then, and the retired nodes dropped from the count by " +
|
|
"hand. With drop (a node) and why, drops that retired node from the count by hand, a hand act the builder answers; a node still placed " +
|
|
"leaves the count by itself only when the placement no longer names it. builder_engine_reads shows what each node's engine states."
|
|
|
|
// AnswerOldForm is builder_old_form's read: the window as the state holds it.
|
|
func AnswerOldForm(r Reader) (OldFormWindow, error) {
|
|
raw, ok, err := r.Get(OldFormKey)
|
|
if err != nil || !ok {
|
|
return OldFormWindow{}, err
|
|
}
|
|
return DecodeOldFormWindow(raw)
|
|
}
|