diff --git a/modules/mesh-config/cmd/mesh-config/proposals.go b/modules/mesh-config/cmd/mesh-config/proposals.go index 7307c49ed..d3e689dc9 100644 --- a/modules/mesh-config/cmd/mesh-config/proposals.go +++ b/modules/mesh-config/cmd/mesh-config/proposals.go @@ -580,7 +580,7 @@ func (c *Config) propose(call Call, action string) (Proposal, oc.Ask, []string, } trusted = false k, kept := s.Kept[keptKey(call.Pull, level.Node, level.Module, call.Key)] - was, had = Value{Value: k.Value}, kept + was, had = Value{Value: heldValue(k)}, kept } else { var derr error r, trusted, derr = c.declared(s, level, call.Key) diff --git a/modules/mesh-config/cmd/mesh-config/pulls.go b/modules/mesh-config/cmd/mesh-config/pulls.go index 283d99270..0051d8781 100644 --- a/modules/mesh-config/cmd/mesh-config/pulls.go +++ b/modules/mesh-config/cmd/mesh-config/pulls.go @@ -6,6 +6,7 @@ import ( "encoding/json" "errors" "fmt" + "git.novox.be/novox/mesh-contracts/go/clients/meshconfigclient" "regexp" "strconv" "strings" @@ -61,32 +62,25 @@ type PullDecl struct { At time.Time `json:"at"` } -// KeptValue is an approved held value, kept apart from the values mesh-config resolves, in this module's own layout -// (the owner's: its state `kept`), which the checker and the assigner's composition check read (novox/hq ADR 0565 §5). -type KeptValue struct { - Pull string `json:"pull"` - Head string `json:"head"` - Digest string `json:"declaration"` - Node string `json:"node"` - Module string `json:"module"` - Key string `json:"key"` - // Value is the value held, as the operator approved it. - Value json.RawMessage `json:"value"` - By string `json:"by"` - Via string `json:"via"` - Why string `json:"why"` - // Approved is who approved it, as the proposal kept it. - Approved string `json:"approved"` - Proposal string `json:"proposal"` - At time.Time `json:"at"` -} +// KeptValue is an approved held value, kept apart from the values mesh-config resolves, in the layout of the seat's state +// `kept`, which the checker and the assigner's composition check read (novox/hq ADR 0565 §5, ADR 0634 §4). +type KeptValue = meshconfigclient.Kept // keptKey is a held value's key in `kept`: the pull request's key form (pullKey), the node, the module and the config // key, joined by dots. func keptKey(pull, node, module, key string) string { - return pullKey(pull) + "." + node + "." + module + "." + key + k, _ := meshconfigclient.HeldValueKey(pull, node, module, key) // a held value of no pull request has no key, and is found by none + return k } +// heldValue is the value held, as the operator approved it, in its JSON form. +func heldValue(k KeptValue) json.RawMessage { + raw, _ := meshconfigclient.ValueOf(k) // a value that was read from JSON writes as JSON + return raw +} + +func heldAt(t time.Time) *time.Time { return &t } + func keptStateKey(k KeptValue) string { return keptKey(k.Pull, k.Node, k.Module, k.Key) } func keptLevel(k KeptValue) Level { return Level{Kind: LevelModule, Node: k.Node, Module: k.Module} } @@ -123,9 +117,9 @@ func (c *Config) readPulls(s *Sources) error { return fmt.Errorf("the pull requests' declarations cannot be read: %w", err) } if err := c.each(KeptState, func(key string, raw []byte) error { - var k KeptValue - if err := json.Unmarshal(raw, &k); err != nil || k.Pull == "" { - return nil + k, err := meshconfigclient.DecodeKept(key, raw) + if err != nil { + return nil // not a held value of the seat's layout under its own key } s.Kept[key] = k return nil @@ -176,11 +170,11 @@ func (c *Config) declarePull(pull, head string, declares map[string]map[string]D switch { case !declared: why = "the head no longer adds or changes its declaration" - case declDigest(d) != kv.Digest: + case declDigest(d) != kv.Declaration: why = "the head declares the key otherwise than the value was approved against" default: var v any - _ = json.Unmarshal(kv.Value, &v) + _ = json.Unmarshal(heldValue(kv), &v) if p := d.rule().check(v); len(p) > 0 { why = "the value no longer passes: " + strings.Join(p, "; ") } @@ -208,13 +202,13 @@ func (c *Config) declarePull(pull, head string, declares map[string]map[string]D // config-changed; what could not be done is answered, not dropped. func (c *Config) heldChange(kv KeptValue, action, why, approved string) []string { level := keptLevel(kv) - ch := Change{Action: action, Level: &level, Key: kv.Key, Value: kv.Value, By: kv.By, Via: kv.Via, Why: why, + ch := Change{Action: action, Level: &level, Key: kv.Key, Value: heldValue(kv), By: kv.By, Via: kv.Via, Why: why, Approved: approved, Pull: kv.Pull, Head: kv.Head, At: c.now(), Moved: []Moved{}} unsaid := c.record(level.ValueKey(kv.Key), ch) cause := "a value held for " + kv.Pull what := fmt.Sprintf("config %s %s of %s for %s, held for %s", action, kv.Key, kv.Module, kv.Node, kv.Pull) if action != ActionDrop { - what += " (" + string(kv.Value) + ")" + what += " (" + string(heldValue(kv)) + ")" } if err := c.Store.HandAct(what, why, cause); err != nil { unsaid = append(unsaid, "the hand-act log does not hold it: "+err.Error()) @@ -260,8 +254,8 @@ type HeldSkipped struct { } func heldValueOf(k KeptValue) HeldValue { - return HeldValue{Pull: k.Pull, Head: k.Head, Node: k.Node, Module: k.Module, Key: k.Key, Value: compact(k.Value), - Approved: k.Approved, By: k.By, Why: k.Why, At: k.At} + return HeldValue{Pull: k.Pull, Head: k.Head, Node: k.Node, Module: k.Module, Key: k.Key, Value: compact(heldValue(k)), + Approved: k.Approved, By: k.By, Why: k.Why, At: timeOf(k.At)} } // Held answers the approved held values for the pull requests asked, at the heads asked, to the controller alone: a @@ -373,13 +367,13 @@ func (c *Config) keep(p Proposal) (string, error) { "propose it again", p.Pull, p.Key) } was, had := s.Kept[keptKey(p.Pull, p.Level.Node, p.Level.Module, p.Key)] - if had != p.HadValue || valueDigest(was.Value, had) != p.WasDigest { + if had != p.HadValue || valueDigest(heldValue(was), had) != p.WasDigest { return "", fmt.Errorf("the value held for %s changed since the operator was shown the change: propose it "+ "again", p.Pull) } - kv := KeptValue{Pull: p.Pull, Head: pd.Head, Digest: p.DeclDigest, Node: p.Level.Node, Module: p.Level.Module, + kv := KeptValue{Pull: p.Pull, Head: pd.Head, Declaration: p.DeclDigest, Node: p.Level.Node, Module: p.Level.Module, Key: p.Key, Value: p.Value, By: "the operator", Via: p.From, Why: p.Why, Approved: p.Approved, Proposal: p.ID, - At: c.now()} + At: heldAt(c.now())} if err := c.Store.Put(KeptState, keptStateKey(kv), kv); err != nil { return "", fmt.Errorf("the value was not kept: %v", err) } @@ -434,7 +428,7 @@ func (c *Config) settlePull(pull string, merged bool, head string) []string { why = "the merged head is not known, so the value cannot be held to it" case !strings.EqualFold(head, kv.Head): why = fmt.Sprintf("it is held at head %s and the pull request merged at %s", short(kv.Head), short(head)) - case !known || !declared || declDigest(d) != kv.Digest: + case !known || !declared || declDigest(d) != kv.Declaration: why = "the merged head does not declare the key as the operator was shown" } if why != "" { @@ -444,7 +438,7 @@ func (c *Config) settlePull(pull string, merged bool, head string) []string { continue } vk := keptLevel(kv).ValueKey(kv.Key) - v := Value{Value: compact(kv.Value), By: kv.By, Via: kv.Via, Why: kv.Why + " (held for " + kv.Pull + + v := Value{Value: compact(heldValue(kv)), By: kv.By, Via: kv.Via, Why: kv.Why + " (held for " + kv.Pull + ", merged)", Approved: kv.Approved, At: c.now()} if err := c.Store.Put(ValuesState, vk, v); err != nil { said = append(said, fmt.Sprintf("%s of %s on %s held for %s is not written yet: %v; it is tried "+ diff --git a/modules/mesh-config/cmd/mesh-config/pulls_test.go b/modules/mesh-config/cmd/mesh-config/pulls_test.go index 3877fde41..45b16a5a4 100644 --- a/modules/mesh-config/cmd/mesh-config/pulls_test.go +++ b/modules/mesh-config/cmd/mesh-config/pulls_test.go @@ -3,6 +3,7 @@ package main import ( "encoding/json" "errors" + "git.novox.be/novox/mesh-contracts/go/clients/meshconfigclient" "strings" "testing" "time" @@ -317,8 +318,11 @@ func TestTheMergeAppliesAHeldValueAndItResolvesOnceTheTrunkDeclaresTheKey(t *tes holdIt(t, c, m, r) approved := func() string { raw, _ := m.Get(KeptState, "novox.mesh-catalog.434.ace.n8n.media-gid") - var k KeptValue - _ = json.Unmarshal(raw.Value, &k) + // the state kept is the seat's layout, under the seat's key: the checker and the assigner read it with this + k, err := meshconfigclient.DecodeKept("novox.mesh-catalog.434.ace.n8n.media-gid", raw.Value) + if err != nil || string(heldValue(k)) != "2000" || k.Declaration == "" || k.At == nil { + t.Fatalf("the held value is not the seat's: %+v, %v", k, err) + } return k.Approved }() m.events, m.handActs = nil, nil diff --git a/modules/mesh-config/go.mod b/modules/mesh-config/go.mod index cb405a36a..d2c1c718f 100644 --- a/modules/mesh-config/go.mod +++ b/modules/mesh-config/go.mod @@ -3,6 +3,7 @@ module mesh-config go 1.26.0 require ( + git.novox.be/novox/mesh-contracts/go/clients/meshconfigclient v0.0.0-00010101000000-000000000000 git.novox.be/novox/mesh-contracts/placement/go/seats/meshwants v0.0.0-00010101000000-000000000000 git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient v0.0.0-00010101000000-000000000000 git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient v0.0.0-00010101000000-000000000000 @@ -21,6 +22,7 @@ require ( ) require ( + git.novox.be/novox/mesh-contracts/go/clients/meshconfigclient v0.0.0-00010101000000-000000000000 git.novox.be/novox/mesh-contracts/go v0.0.0-20261104174344-ae5266e77639 // indirect github.com/antithesishq/antithesis-sdk-go v0.8.0-default-no-op // indirect github.com/google/go-tpm v0.9.8 // indirect