package main import ( "encoding/json" "fmt" "os" "sort" "strings" "git.novox.be/novox/mesh-sdk/go/catalogue" snapshot "git.novox.be/novox/mesh-sdk/go/facts" ) // Config values held for the keys an open pull request introduces (novox/hq ADR 0502 clause 4, issue 1826): the gate // lays each approved one over its module's values on its node in the composition with the change, and in that one // only. In the mesh as it is the key is not declared, so the value would be refused there. // heldValue is one approved held value as the build agent writes it (--held): the value of Key for Module on Node, // a machine's real name, which the snapshot holds only as a pseudonym. type heldValue struct { Pull string `json:"pull"` Head string `json:"head,omitempty"` Node string `json:"node"` Module string `json:"module"` Key string `json:"key"` Value json.RawMessage `json:"value"` Approved string `json:"approved,omitempty"` By string `json:"by,omitempty"` At string `json:"at,omitempty"` } // readHeld reads the file --held names; none named, none held. func readHeld(path string) ([]heldValue, error) { if path == "" { return nil, nil } raw, err := os.ReadFile(path) if err != nil { return nil, err } var held []heldValue if err := json.Unmarshal(raw, &held); err != nil { return nil, fmt.Errorf("the held values are not readable: %w", err) } for _, h := range held { if h.Node == "" || h.Module == "" || h.Key == "" || len(h.Value) == 0 { return nil, fmt.Errorf("a held value of %s names no node, module, key or value", h.Pull) } } return held, nil } // layHeld is the snapshot with each held value laid as the value of its key in its node's layer of its module, and what // it said of them. The snapshot given is not changed. **A held value for a machine the snapshot does not hold, or a // module the change does not define, is said and not laid**, never dropped silently; one for a module not assigned to // its machine is laid and said. The notes name machines as the snapshot does (roles and pseudonym), not by their names. func layHeld(f snapshot.Facts, held []heldValue, shelf map[string]catalogue.Manifest) (snapshot.Facts, []string) { if len(held) == 0 { return f, nil } out := f out.Machines = make([]snapshot.Machine, len(f.Machines)) copy(out.Machines, f.Machines) var notes, laid []string pulls := map[string]bool{} for _, h := range held { at := -1 pseudonym := snapshot.Pseudonym("machine", h.Node) for i, m := range out.Machines { if m.Name == pseudonym { at = i } } if at < 0 { notes = append(notes, fmt.Sprintf("the value held for %s of %s (%s=%s) is not composed: the snapshot holds no machine %s", h.Module, h.Pull, h.Key, h.Value, pseudonym)) continue } if _, defined := shelf[h.Module]; !defined { notes = append(notes, fmt.Sprintf("the value held for %s of %s (%s=%s) is not composed: the change defines no module %s", h.Module, h.Pull, h.Key, h.Value, h.Module)) continue } var value any if err := json.Unmarshal(h.Value, &value); err != nil { notes = append(notes, fmt.Sprintf("the value held for %s of %s (%s) is not composed: it is not a value: %v", h.Module, h.Pull, h.Key, err)) continue } m := &out.Machines[at] layers := make([]snapshot.Settings, len(m.Settings)) copy(layers, m.Settings) found := false for i := range layers { if layers[i].Module == h.Module { values := make(map[string]any, len(layers[i].Values)+1) for k, v := range layers[i].Values { values[k] = v } values[h.Key] = value layers[i].Values = values found = true } } if !found { layers = append(layers, snapshot.Settings{Module: h.Module, Values: map[string]any{h.Key: value}}) sort.Slice(layers, func(a, b int) bool { return layers[a].Module < layers[b].Module }) } m.Settings = layers assigned := false for _, a := range m.Assigned { assigned = assigned || a == h.Module } by := "" if h.Approved != "" { by = ", approved by " + h.Approved } line := fmt.Sprintf("%s on %s %s=%s%s", h.Module, m.Described(), h.Key, h.Value, by) if !assigned { line += " (not assigned there in the snapshot)" } laid = append(laid, line) pulls[h.Pull] = true } if len(laid) > 0 { var names []string for p := range pulls { names = append(names, p) } sort.Strings(names) notes = append([]string{fmt.Sprintf("composed with %d value(s) held for %s: %s", len(laid), strings.Join(names, ", "), strings.Join(laid, "; "))}, notes...) } return out, notes }