From 3d7d65edce4cdb0a2c85b8815ff33f7e25dc5537 Mon Sep 17 00:00:00 2001 From: jochen Date: Sun, 25 Oct 2026 15:32:35 +0100 Subject: [PATCH] Assigner: a node running an old-form build gets its first want at F once the canary runs F healthy (issue 1945) An old-form build (not src2:) has no composition, so no want can ever name what such a node runs. Design limit, decided by the lead (lead-47): its first want names the newest F, by every rule as usual - the canary (rule 3: this node is the canary, or the canary runs F healthy), everything F needs (built, not held, granted, composed) and the providers (rule 5). Otherwise nothing is written and the neither condition stays. The canary stays in force, and old-form modules (clipmenu and screen-lock on g14) leave neither list by themselves, without a hand-written want. The code already behaved so (an old-form have falls through to Holds); the case is now named in Wants, and proven: a unit test for both sides of the canary, the providers and the grant, and the integration test gives g14 F and clears its neither condition once the canary ace runs F healthy. --- .../integration_test.go | 19 +++++- .../internal/assign/wants.go | 7 +- .../internal/assign/wants_test.go | 66 +++++++++++++++++++ 3 files changed, 90 insertions(+), 2 deletions(-) diff --git a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/integration_test.go b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/integration_test.go index 241ae99..a8c4365 100644 --- a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/integration_test.go +++ b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/integration_test.go @@ -285,7 +285,8 @@ func (b *bed) get(t *testing.T, bucket, key string) []byte { // Started whole, the assigner wants F on the canary and writes no difference at all: there is no send to compare with (ADR 0539 // ยง8). 1945/AC19, AC21: shanks, which runs the older fingerprint (composed and granted) with no want, gets its first want at that // fingerprint, never at F; g14, which runs a build of the old form, gets none, so web is on neither list there and the assigner -// raises the condition naming g14, web and the canary rule. +// raises the condition naming g14, web and the canary rule; once ace runs F healthy, g14's first want names F and the condition is +// cleared. func TestWantsAreWrittenAndNoDifferenceIs(t *testing.T) { b := startBed(t, "") fNew, fOld := delivery.SourcePrefix2+strings.Repeat("b", 64), delivery.SourcePrefix2+strings.Repeat("a", 64) @@ -340,6 +341,22 @@ func TestWantsAreWrittenAndNoDifferenceIs(t *testing.T) { if e, err := b.buckets[delivery.AssignerWantsBucket].Get(b.ctx, "want.g14.web"); err == nil { t.Errorf("g14, which runs an old-form build and is held back by the canary, was given a want: %s", e.Value()) } + + // 1945/AC21: once the canary ace runs F healthy, g14's first want names F by the rules as usual, and web leaves neither list + // there by itself: the condition is cleared. + healthyF, _ := delivery.EncodeHave(delivery.Have{FP: fNew, Phase: delivery.PhaseRunning, Health: delivery.HealthOK}) + b.putJSON(t, delivery.HaveBucket, "ace.web", healthyF) + if w := b.wantNow(t, "g14", "web", "g14's first want at F once the canary runs it healthy", func(w delivery.Want) bool { return w.FP != "" }); w.FP != fNew || w.Composed != "sha256:"+strings.Repeat("c", 64) || w.Newest != "" { + t.Errorf("g14's want = %+v, want F with its composition", w) + } + cleared := false + for deadline := time.Now().Add(10 * time.Second); time.Now().Before(deadline) && !cleared; time.Sleep(100 * time.Millisecond) { + c, ok := b.conditionsSaid(t)[neither.Key] + cleared = ok && c.State == asks.StateCleared + } + if !cleared { + t.Errorf("the condition of web on neither list on g14 was not cleared once g14 got its want") + } } // 1297/B9 (the assigner's half, ADR 0528 clause 6): started whole, the assigner holds a fingerprint whose health check diff --git a/modules/mesh-delivery-assigner/internal/assign/wants.go b/modules/mesh-delivery-assigner/internal/assign/wants.go index 7ee8b50..264a2e9 100644 --- a/modules/mesh-delivery-assigner/internal/assign/wants.go +++ b/modules/mesh-delivery-assigner/internal/assign/wants.go @@ -239,7 +239,12 @@ func Wants(m Module, n Nodes, wants map[string]delivery.Want, lastHealthy map[st case !ran: case have.FP == m.FP: ok, rule, words = holds(mm, node, n, false) - case strings.HasPrefix(have.FP, delivery.SourcePrefix2) && composed && m.GrantedAt != nil && m.GrantedAt(have.FP): + case !strings.HasPrefix(have.FP, delivery.SourcePrefix2): + // An old-form build (not src2:) has no composition, so no want can ever name it: its first want names F, and only by every + // rule as usual, the canary (3) and the providers (5) included, which here hold it back. Nothing is written and the module + // stays on neither list until the canary runs F healthy; then Holds passes and the want names F (issue 1945, a design + // limit set by the lead: the canary stays in force and old-form modules leave neither list by themselves). + case composed && m.GrantedAt != nil && m.GrantedAt(have.FP): ok, target = true, have.FP } } diff --git a/modules/mesh-delivery-assigner/internal/assign/wants_test.go b/modules/mesh-delivery-assigner/internal/assign/wants_test.go index a9b943b..ebaa65f 100644 --- a/modules/mesh-delivery-assigner/internal/assign/wants_test.go +++ b/modules/mesh-delivery-assigner/internal/assign/wants_test.go @@ -690,3 +690,69 @@ func TestANewAssignmentWaitingIsNotOnNeitherList(t *testing.T) { t.Errorf("a module of the node's own composition named on neither list: %+v", out.Neither) } } + +// 1945/AC21: a node whose have names an old-form build (no fingerprint of the new form, so no composition of it) can never be +// named in a want at what it runs; its first want names F, under the canary rule as usual and with everything F needs and the +// providers rule. Given the canary runs F healthy, shanks's first want names F; given it does not yet, shanks gets no want and +// stays on neither list. The old-form node that is itself the canary gets F once its providers run healthy. +func TestAnOldFormNodeGetsItsFirstWantAtFOnlyUnderTheCanary(t *testing.T) { + oldForm := func(n Nodes, node string) { + n.Haves[node]["screen-lock"] = delivery.Have{FP: "build-1792260876956849831", Phase: delivery.PhaseRunning, Health: delivery.HealthUnknown} + } + providersHealthy := func(n Nodes) { + for _, node := range []string{"g14", "shanks"} { + n.Haves[node]["xorg"] = delivery.Have{FP: fpOf('x'), Phase: delivery.PhaseRunning, Health: delivery.HealthOK} + } + } + // the canary g14 runs F healthy: shanks's first want names F + m, n := x11() + providersHealthy(n) + oldForm(n, "shanks") + n.Haves["g14"]["screen-lock"] = delivery.Have{FP: fNew, Phase: delivery.PhaseRunning, Health: delivery.HealthOK} + out := Wants(m, n, nil, nil) + if w := out.Wants["shanks"]; w.FP != fNew || w.Composed != dig(fNew) || w.Newest != "" || w.Rule != 0 { + t.Errorf("canary healthy on F: shanks's want = %+v, want F", w) + } + if _, ok := out.Neither["shanks"]; ok { + t.Errorf("canary healthy on F: shanks still on neither list: %+v", out.Neither) + } + // the canary does not run F healthy yet: no want, and shanks stays on neither list by rule 3 + m, n = x11() + providersHealthy(n) + oldForm(n, "shanks") + n.Haves["g14"]["screen-lock"] = delivery.Have{FP: fNew, Phase: delivery.PhaseRunning, Health: delivery.HealthUnknown} + out = Wants(m, n, nil, nil) + if w, ok := out.Wants["shanks"]; ok { + t.Errorf("canary not healthy on F: shanks got a want: %+v", w) + } + if h, ok := out.Neither["shanks"]; !ok || h.Rule != delivery.RuleCanary { + t.Errorf("canary not healthy on F: shanks on neither list = %+v (named %v), want rule 3", h, ok) + } + // the canary healthy on F but the providers not healthy on shanks: rule 5 still holds it back + m, n = x11() + oldForm(n, "shanks") + n.Haves["g14"]["screen-lock"] = delivery.Have{FP: fNew, Phase: delivery.PhaseRunning, Health: delivery.HealthOK} + out = Wants(m, n, nil, nil) + if w, ok := out.Wants["shanks"]; ok { + t.Errorf("providers not healthy: shanks got a want: %+v", w) + } + if h := out.Neither["shanks"]; h.Rule != delivery.RuleProviders { + t.Errorf("providers not healthy: shanks on neither list = %+v, want rule 5", h) + } + // F not granted: nothing, even with the canary healthy + m, n = x11() + providersHealthy(n) + oldForm(n, "shanks") + n.Haves["g14"]["screen-lock"] = delivery.Have{FP: fNew, Phase: delivery.PhaseRunning, Health: delivery.HealthOK} + m.Granted = false + if w, ok := Wants(m, n, nil, nil).Wants["shanks"]; ok { + t.Errorf("F not granted: shanks got a want: %+v", w) + } + // the old-form node is the canary itself: F once its providers run healthy + m, n = x11() + providersHealthy(n) + oldForm(n, "g14") + if w := Wants(m, n, nil, nil).Wants["g14"]; w.FP != fNew || w.Composed != dig(fNew) { + t.Errorf("old-form canary: g14's want = %+v, want F", w) + } +}