diff --git a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/pass_test.go b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/pass_test.go index dd4c767f..799ef530 100644 --- a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/pass_test.go +++ b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/pass_test.go @@ -803,18 +803,23 @@ func aReleasesApproveAnswersTheMoveOfItsBuildAlone(t *testing.T) { alone bool at string // the fingerprint the have runs mergedAt string // the commit the release merged web at; "" for no merge of the release + fpSrc uint64 // the source revision at which web's fingerprint last changed yes, asked bool }{ - {"the build alone, its release approved", true, fpOld, commit, true, false}, - {"a setting changed too", false, fpOld, commit, false, true}, - {"a setting changed at the build it runs", true, fpTest, commit, false, true}, - {"no approved release carries it", true, fpOld, "", false, true}, - {"the release merged another commit", true, fpOld, strings.Repeat("2", 40), false, true}, + {"the build alone, its release approved", true, fpOld, commit, 7, true, false}, + {"a setting changed too", false, fpOld, commit, 7, false, true}, + {"a setting changed at the build it runs", true, fpTest, commit, 7, false, true}, + {"no approved release carries it", true, fpOld, "", 7, false, true}, + {"the release merged another commit", true, fpOld, strings.Repeat("2", 40), 7, false, true}, + // Review round 1: its fingerprint moved by a recompute after the release (a toolchain, a package), with no merge of + // web: stamped with that moment's revision, whose release carries nothing of web; the earlier Approve answers nothing. + {"a fingerprint moved by a recompute after the release", true, fpOld, commit, 8, false, true}, + {"a fingerprint whose revision is not known", true, fpOld, commit, 0, false, true}, } { asksKV := &memKV{values: map[string][]byte{}} p := newPasser(buckets{delivery.AssignerAsksBucket: asksKV}, time.Now()) p.logf = t.Logf - mod, _ := delivery.EncodeModule(delivery.Module{FP2: fpTest, Repository: "novox/web", Commit: commit, Src: 7, Path: delivery.RootPath}) + mod, _ := delivery.EncodeModule(delivery.Module{FP2: fpTest, Repository: "novox/web", Commit: commit, Src: 7, FPSrc: c.fpSrc, Path: delivery.RootPath}) p.w.put(delivery.ModulesBucket, "web", mod) src, _ := delivery.EncodeSource(delivery.Source{Group: "feat/web", Heads: map[string]string{"novox/web": commit}}) p.w.putEntry(delivery.SourceBucket, delivery.SourceMainKey, src, 7, time.Now()) @@ -827,6 +832,12 @@ func aReleasesApproveAnswersTheMoveOfItsBuildAlone(t *testing.T) { t.Fatal(err) } p.w.put(delivery.MergesBucket, "feat/web", merge) + other, err := delivery.EncodeMerge(delivery.Merge{Group: "feat/other", Ask: "merge.feat_other.0123456789ab", Pulls: []delivery.MergePull{ + {Head: delivery.Head{Repository: "novox/x", Number: 4, Head: strings.Repeat("4", 40)}, State: delivery.MergeMerged, Commit: commit}}}) + if err != nil { + t.Fatal(err) + } + p.w.put(delivery.MergesBucket, "feat/other", other) } have, _ := delivery.EncodeHave(delivery.Have{FP: c.at, Composed: was, Phase: delivery.PhaseWaitingYes, Health: delivery.HealthOK, Judgement: delivery.JudgedRecreates, Waits: waiting, Detail: "it recreates web.server", BuildAlone: c.alone}) diff --git a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/world.go b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/world.go index 751b6c2c..1f33e577 100644 --- a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/world.go +++ b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/world.go @@ -82,16 +82,17 @@ func (w *world) putEntry(bucket, key string, value []byte, revision uint64, at t } // release is the release whose Approve carries module's build fp (ADR 0601 §1), and whether one does: the trunk's value of -// the module names fp, the source revision it was first merged at names the release that moved it, and the gate's merge of -// that release, which it writes on the operator's Approve, holds the module's own trunk merge among its merged pull -// requests. Any of them unknown: none, and the move is asked of a person. +// the module names fp, the source revision at which that fingerprint last changed (FPSrc, never the Src of its last merge, +// which a recompute with no merge keeps) names the release that moved it, and the gate's merge of that release, which it +// writes on the operator's Approve, holds the module's own trunk merge among its merged pull requests. Any of them +// unknown: none, and the move is asked of a person. func (w *world) release(module, fp string) (string, bool) { t, ok := w.trunk()[module] - if !ok || fp == "" || t.FP2 != fp || t.Src == 0 || t.Commit == "" { + if !ok || fp == "" || t.FP2 != fp || t.FPSrc == 0 || t.Commit == "" { return "", false } w.mu.Lock() - group := w.sourceGroups[t.Src] + group := w.sourceGroups[t.FPSrc] w.mu.Unlock() key, err := delivery.MergesKey(group) if group == "" || err != nil { diff --git a/modules/mesh-delivery-indexer/internal/index/source.go b/modules/mesh-delivery-indexer/internal/index/source.go index f71a3674..b7318bf4 100644 --- a/modules/mesh-delivery-indexer/internal/index/source.go +++ b/modules/mesh-delivery-indexer/internal/index/source.go @@ -14,6 +14,13 @@ import ( // with it (Module.Src) when the head the source names for its repository is the merge commit the module's fingerprint // was computed at: from the modules bucket when the merge was heard first, and when the merge is heard after the source. +// sourceRev is the latest source revision seen, 0 before any. +func (ix *Indexer) sourceRev() uint64 { + ix.mu.Lock() + defer ix.mu.Unlock() + return ix.source.rev +} + // sourceNow is the latest source revision seen. type sourceNow struct { rev uint64 @@ -82,6 +89,9 @@ func (ix *Indexer) ApplySource(ctx context.Context, rev uint64, s delivery.Sourc return nil, false, nil } cur.Src = rev + if cur.FPSrc == 0 { + cur.FPSrc = rev // its fingerprint moved at this merge, whose revision is heard only now (ADR 0601 §1) + } data, err := delivery.EncodeModule(cur) return data, false, err }) diff --git a/modules/mesh-delivery-indexer/internal/index/source_test.go b/modules/mesh-delivery-indexer/internal/index/source_test.go index 45260ab8..8bb56ebd 100644 --- a/modules/mesh-delivery-indexer/internal/index/source_test.go +++ b/modules/mesh-delivery-indexer/internal/index/source_test.go @@ -31,7 +31,7 @@ func TestASourceRevisionStampsTheModulesOfTheRepositoriesHeadItNames(t *testing. } m, ok := moduleOf(t, mods, "a") want := before - want.Src = 7 + want.Src, want.FPSrc = 7, 7 // its fingerprint moved at that merge (ADR 0601 §1) if !ok || !reflect.DeepEqual(m, want) { t.Errorf("module a = %+v (%v), want %+v: the fingerprint and the commit as the merge wrote them, with Src 7", m, ok, want) } diff --git a/modules/mesh-delivery-indexer/internal/index/toolchains.go b/modules/mesh-delivery-indexer/internal/index/toolchains.go index 51f668e4..c8cee117 100644 --- a/modules/mesh-delivery-indexer/internal/index/toolchains.go +++ b/modules/mesh-delivery-indexer/internal/index/toolchains.go @@ -312,6 +312,11 @@ func (ix *Indexer) recomputeModules(ctx context.Context, overlay map[string]deli if cur.FP2 == r.fp && cur.None2 == r.none && reflect.DeepEqual(cur.Toolchains, r.tcs) && reflect.DeepEqual(cur.Packages, r.pkgs) { return nil, false, nil } + if cur.FP2 != r.fp { + // Moved with no merge of its own repository (a toolchain, a package, a base): the source revision of now, whose + // release does not carry it, never the Src of the merge it was last merged at (ADR 0601 §1). + cur.FPSrc = ix.sourceRev() + } cur.FP2, cur.None2, cur.Toolchains, cur.Packages = r.fp, r.none, r.tcs, r.pkgs cur.Inputs2, cur.ArtifactPaths = inputs, paths data, err := delivery.EncodeModule(cur) @@ -355,6 +360,13 @@ func (ix *Indexer) writeOverlay(ctx context.Context, overlay, finals map[string] // refused or outgrown module as accepted; only the judge changes it. next.Verdict, next.Problems, next.Unknown, next.Warnings = cur.Verdict, cur.Problems, cur.Unknown, cur.Warnings } + // The source revision at which FP2 last changed (ADR 0601 §1): this merge's when it moved it (0 until the + // source revision naming the merge is heard, which stamps it), else the one held. + if held && cur.FP2 == next.FP2 { + next.FPSrc = cur.FPSrc + } else { + next.FPSrc = next.Src + } if held && sameButWhere(cur, next) { return nil, false, nil } @@ -379,7 +391,7 @@ func (ix *Indexer) writeOverlay(ctx context.Context, overlay, finals map[string] // which only the judge writes: the module is unchanged. func sameButWhere(cur, next delivery.Module) bool { next = withV(next) - next.Commit, next.Src = cur.Commit, cur.Src + next.Commit, next.Src, next.FPSrc = cur.Commit, cur.Src, cur.FPSrc next.Verdict, next.Problems, next.Unknown, next.Warnings = cur.Verdict, cur.Problems, cur.Unknown, cur.Warnings return reflect.DeepEqual(cur, next) } diff --git a/modules/mesh-delivery-indexer/internal/index/toolchains_test.go b/modules/mesh-delivery-indexer/internal/index/toolchains_test.go index 603241dd..368f0602 100644 --- a/modules/mesh-delivery-indexer/internal/index/toolchains_test.go +++ b/modules/mesh-delivery-indexer/internal/index/toolchains_test.go @@ -113,12 +113,20 @@ func TestAgreedToolchainChangesAGoModulesReasonWithNoMerge(t *testing.T) { // 14 if m, _ := moduleOf(t, mods, "avahi"); m.None2 != delivery.ToolchainNoReport { t.Fatalf("before: none2 = %q", m.None2) } + ix.mu.Lock() + ix.source.rev = 11 // the source revision of now, whose release merged nothing of avahi's + ix.mu.Unlock() reportToolchain(t, ix, "mesh-build-go", "novox", "go", "go1.27.1") reportToolchain(t, ix, "mesh-build-go", "ace", "go", "go1.27.1") m, _ := moduleOf(t, mods, "avahi") if m.None2 != "" || m.FP2 == "" { t.Errorf("none2 = %q fp2 = %q, want a fingerprint: no builder input is waited for (ADR 0530 §1)", m.None2, m.FP2) } + // ADR 0601 §1: a fingerprint moved with no merge is stamped with the source revision of the moment, never the Src of + // the merge it was last merged at, so no earlier release is taken to carry it. + if m.FPSrc != 11 || m.Src == 11 { + t.Errorf("a fingerprint moved by a recompute: fp-src %d, src %d; want fp-src 11", m.FPSrc, m.Src) + } } func TestNodesReportingDifferentVersionsLeaveTheReasonNamingBoth(t *testing.T) { // 1444/AC6