Files
mesh-delivery/modules/mesh-delivery-builder/internal/versions/oldform.go
T
jschoubben 0dc0cf9c9a DLV-B9: the old reference form's window over sdk/forms, kept in old-form, read and dropped by builder_old_form
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
2026-11-04 12:55:45 +01:00

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)
}