package versions import ( "encoding/json" "errors" "fmt" "slices" "sort" "strings" "time" "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds" "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds/forms" "git.novox.be/novox/mesh-delivery/sdk/window" ) // 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 assignments, 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 = builds.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 (window.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 { window.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 (window.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 // assignments 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 := window.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 assignments 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) }