The window opens while a counted node-engine reads, of some kind, none of the newest form sdk/forms knows, and closes when none does: said once as version.old-form-closed, and a node holding it seven days is old-form-held about it. It steps on a statement's change, the placement, a hand drop and each build key written, never on a timer. A retired node dropped by hand leaves the count of the forms a build key keeps (design 51). internal/versions' own Forms goes: which forms a key keeps is sdk/forms'. Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
179 lines
6.8 KiB
Go
179 lines
6.8 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, 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"`
|
|
}
|
|
|
|
// 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 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
|
|
}
|
|
|
|
// 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)
|
|
}
|