The provider helper reads an ended or pending assignment as a removed key (issue 1098)
mesh/delivery-group group feat/chain-on-the-bus stopped: a member was stopped
mesh/delivery stopped: the pull request closed unmerged
mesh/merge-gate head f185aedbf5d6 merged into trunk f70a99dbf485 — pass, with a note: builds mesh-sdk (+41 dependent(s)) → ace, g14, novox, shanks; no bus …
mesh/repo-check head f185aedbf5d6 merged into trunk f70a99dbf485 — pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (typescript)…
mesh/delivery-group group feat/chain-on-the-bus stopped: a member was stopped
mesh/delivery stopped: the pull request closed unmerged
mesh/merge-gate head f185aedbf5d6 merged into trunk f70a99dbf485 — pass, with a note: builds mesh-sdk (+41 dependent(s)) → ace, g14, novox, shanks; no bus …
mesh/repo-check head f185aedbf5d6 merged into trunk f70a99dbf485 — pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (typescript)…
This commit is contained in:
@@ -0,0 +1,62 @@
|
|||||||
|
package provider
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"slices"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Issue 1098: an assignment with `ended` set, or with a `state`, is not a standing assignment: the helper reads it
|
||||||
|
// as a removed key, so the entry of its consumer is removed and the adapter hears it is gone. The fields the
|
||||||
|
// controller adds change nothing.
|
||||||
|
func TestAnEndedAssignmentIsReadAsARemovedKey(t *testing.T) {
|
||||||
|
bus, m := newStandIn(), &maker{}
|
||||||
|
v := &vault{values: map[string]string{"pair/novox/postgres-database/ace/letta": "s1"}}
|
||||||
|
assign(t, bus, "ace", "letta", bind("postgres-database", "", "novox", "postgres"))
|
||||||
|
p := postgres(bus, v, m)
|
||||||
|
if _, err := p.Pass(context.Background()); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if got := bus.keys(BucketOf("postgres")); !slices.Equal(got, []string{"ace.letta.postgres-database"}) {
|
||||||
|
t.Fatalf("entries %v", got)
|
||||||
|
}
|
||||||
|
for _, mark := range []map[string]any{{"ended": "2026-10-18T01:00:00Z"}, {"state": "pending"}} {
|
||||||
|
raw := bus.get(AssignmentsBucket, "ace.letta")
|
||||||
|
var a map[string]any
|
||||||
|
if err := json.Unmarshal(raw, &a); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for k, v := range mark {
|
||||||
|
a[k] = v
|
||||||
|
}
|
||||||
|
raw, _ = json.Marshal(a)
|
||||||
|
_ = bus.Put(context.Background(), AssignmentsBucket, "ace.letta", raw)
|
||||||
|
out, err := p.Pass(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if got := bus.keys(BucketOf("postgres")); len(got) != 0 || len(out) != 1 || out[0].Status != Removed {
|
||||||
|
t.Fatalf("%v: entries %v, outcomes %+v", mark, got, out)
|
||||||
|
}
|
||||||
|
assign(t, bus, "ace", "letta", bind("postgres-database", "", "novox", "postgres"))
|
||||||
|
if _, err := p.Pass(context.Background()); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStandingIsFalseOnlyForAnEndedOrPendingValue(t *testing.T) {
|
||||||
|
for raw, want := range map[string]bool{
|
||||||
|
`{"node":"a","module":"b"}`: true,
|
||||||
|
`{"node":"a","module":"b","ended":"","state":"","x":{"y":1}}`: true,
|
||||||
|
`{"node":"a","module":"b","ended":null}`: true,
|
||||||
|
`{"node":"a","module":"b","build":"b1","commit":"c","rollout":"r","since":"t","artifact":{"name":"n"}}`: true,
|
||||||
|
`{"node":"a","module":"b","ended":"2026-10-18T01:00:00Z"}`: false,
|
||||||
|
`{"node":"a","module":"b","state":"pending"}`: false,
|
||||||
|
} {
|
||||||
|
if got := Standing([]byte(raw)); got != want {
|
||||||
|
t.Errorf("%s: %v, want %v", raw, got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -335,6 +335,9 @@ func (p *Provider) pass(ctx context.Context, assignments, entries map[string][]b
|
|||||||
var out []Outcome
|
var out []Outcome
|
||||||
unknown := map[string]bool{} // consumers whose assignment was skipped: their entries are not judged
|
unknown := map[string]bool{} // consumers whose assignment was skipped: their entries are not judged
|
||||||
for _, key := range sortedKeys(assignments) {
|
for _, key := range sortedKeys(assignments) {
|
||||||
|
if !Standing(assignments[key]) {
|
||||||
|
continue // ended or pending: read as a removed key (issue 1098)
|
||||||
|
}
|
||||||
var a planned
|
var a planned
|
||||||
if err := json.Unmarshal(assignments[key], &a); err != nil || a.Node+"."+a.Module != key {
|
if err := json.Unmarshal(assignments[key], &a); err != nil || a.Node+"."+a.Module != key {
|
||||||
o := Outcome{Key: key, Status: Failed, Why: fmt.Sprintf("the assignment %s is not the controller's, so "+
|
o := Outcome{Key: key, Status: Failed, Why: fmt.Sprintf("the assignment %s is not the controller's, so "+
|
||||||
@@ -744,3 +747,23 @@ func (b stateBus) Delete(_ context.Context, bucket, key string) error {
|
|||||||
}
|
}
|
||||||
return s.Delete(key)
|
return s.Delete(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Standing is whether an assignment's value is a standing assignment: false when `ended` is set (the module was
|
||||||
|
// taken off its node, novox/hq ADR 0408 clause 4) or `state` is not empty (a pending one). Such a value is read
|
||||||
|
// exactly as a removed key. Every other field, known or not, changes nothing (issue 1098).
|
||||||
|
func Standing(value []byte) bool {
|
||||||
|
var v struct {
|
||||||
|
Ended json.RawMessage `json:"ended"`
|
||||||
|
State json.RawMessage `json:"state"`
|
||||||
|
}
|
||||||
|
if json.Unmarshal(value, &v) != nil {
|
||||||
|
return true // not decided here: the parse that follows refuses it
|
||||||
|
}
|
||||||
|
return !fieldSet(v.Ended) && !fieldSet(v.State)
|
||||||
|
}
|
||||||
|
|
||||||
|
// fieldSet is whether a field carries something: not absent, null or empty.
|
||||||
|
func fieldSet(f json.RawMessage) bool {
|
||||||
|
s := strings.TrimSpace(string(f))
|
||||||
|
return s != "" && s != "null" && s != `""`
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user