diff --git a/modules/mesh-delivery-assigner/internal/assign/wants.go b/modules/mesh-delivery-assigner/internal/assign/wants.go index f49a6ee..3c25317 100644 --- a/modules/mesh-delivery-assigner/internal/assign/wants.go +++ b/modules/mesh-delivery-assigner/internal/assign/wants.go @@ -307,7 +307,22 @@ func Wants(m Module, n Nodes, wants map[string]delivery.Want, lastHealthy map[st } next = delivery.Want{FP: before.FP, Composed: before.Composed, Newest: m.FP, Rule: rule, Words: words} if before.FP == m.FP { + // Held at F by the rules: a new revision of F's composition waits for them, as a new fingerprint does (ADR 0539 ยง5). next = delivery.Want{FP: m.FP, Composed: before.Composed} + } else if d, there := m.composition(node, before.FP); there && d.Digest != "" && d.Digest != before.Composed { + // Held back at an earlier fingerprint whose kept composition the controller composed again with today's config + // (novox/hq ADR 0545): the want follows its new revision, so the node does not run that build with the config of + // the moment it was made (on g14 a capacity of 0 after a rollback). Asked as any move of a record module is. + at := m + at.FP = before.FP + running, ran := n.Haves[node][m.Name] + need, wait := at.mustAsk(node, before, true, ran, running.FP, running.Composed == "", d) + if need != nil { + out.Record = append(out.Record, *need) + } + if !wait { + next.Composed = d.Digest + } } } if !had || !delivery.SameWant(normal(before), normal(next)) { diff --git a/modules/mesh-delivery-assigner/internal/assign/wants_test.go b/modules/mesh-delivery-assigner/internal/assign/wants_test.go index 9372367..5e9295c 100644 --- a/modules/mesh-delivery-assigner/internal/assign/wants_test.go +++ b/modules/mesh-delivery-assigner/internal/assign/wants_test.go @@ -221,6 +221,25 @@ func TestARevisionAtTheSameFingerprintIsAWantByTheSameRules(t *testing.T) { if w, ok := out.Wants["ace"]; ok && w.Composed == revised { t.Errorf("a revision was written while a rule holds: %+v", w) } + + // A want held back at an earlier fingerprint follows the new revision of that fingerprint's kept composition, which the + // controller composed again with today's config (novox/hq ADR 0545: on g14 a capacity of 0 after a rollback), while + // the rules still hold F; for a record module whose revision recreates a container, only after the ask. + out = Wants(m, n, wantedOld(), nil) + if w := out.Wants["g14"]; w.FP != fOld || w.Composed != revised || w.Newest != fNew { + t.Errorf("g14, held back at its earlier fingerprint, wants %+v; want it at that fingerprint naming the revised digest", w) + } + m.Composed = func(node, fp string) (delivery.ComposedDigest, bool) { + return delivery.ComposedDigest{Digest: revised, Judgement: delivery.JudgedRecreates, Policy: delivery.PolicyRecord, + Against: dig(fOld), Words: "recreates"}, true + } + out = Wants(m, n, wantedOld(), nil) + if w := out.Wants["g14"]; w.Composed == revised { + t.Errorf("a recorded module's revision that recreates a container was written with no ask: %+v", w) + } + if !slices.ContainsFunc(out.Record, func(r RecordNeed) bool { return r.Node == "g14" && r.Digest == revised }) { + t.Errorf("the revision was not asked: %+v", out.Record) + } } // 1906/D2: a set-back names the composition of the fingerprint it sets back to, and when that is gone the want stays and says so.