A release answers only the build whose fingerprint it moved: the indexer stamps the source revision at which a fingerprint last changed, a recompute with no merge the revision of its moment, and the assigner reads that stamp, never the Src a recompute keeps (ADR 0601 §1; review round 1, finding 1)

This commit is contained in:
2026-11-03 16:44:47 +01:00
parent ea5be10532
commit a41d2851c7
6 changed files with 55 additions and 13 deletions
@@ -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})
@@ -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 {
@@ -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
})
@@ -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)
}
@@ -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)
}
@@ -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