Move no module whose fingerprint is unchanged, and let a key's build stand on the held build-agent build of the same digests at the switch (issue 1851, A9)

This commit is contained in:
2026-10-22 19:05:04 +02:00
parent 7743bd0d4b
commit 9da79ef8c3
8 changed files with 208 additions and 189 deletions
-15
View File
@@ -132,18 +132,3 @@ func TestACheckIsHandedOnlyItsOwnHeadsBuilds(t *testing.T) {
t.Fatalf("verdicts that could not be read named %+v", got)
}
}
// A walk's merged heads are the forge's word for each merge; a merge announced without its head, or a pull request
// merged at two heads in one walk, has none, and its walk takes no check's build (novox/hq issue 703).
func TestAWalksMergedHeadsAreTheForgesWord(t *testing.T) {
merge := func(number int, head string) inventory.PlanMerge {
return inventory.PlanMerge{Repository: "novox/shop", Number: number, Head: head}
}
p := &inventory.Plan{Delivery: &inventory.PlanDelivery{Merges: []inventory.PlanMerge{
merge(7, "aaaaaaaaaaaa1"), merge(8, ""), merge(9, "bbbbbbbbbbbb"), merge(9, "cccccccccccc")}}}
heads := mergedHeadsOf(p)
shop := func(n int) link.PullRef { return link.PullRef{Owner: "novox", Repo: "shop", Number: n} }
if heads[shop(7)] != "aaaaaaaaaaaa1" || heads[shop(8)] != "" || heads[shop(9)] != "" {
t.Fatalf("merged heads %v", heads)
}
}
@@ -190,3 +190,33 @@ func TestAKeysRecordKeepsTheKeepSetAndReadsItsManifest(t *testing.T) {
t.Fatalf("the key's record does not read its manifest's context: %s", text)
}
}
// A9 (ADR 0453 §6, ADR 0499 consequences): at the switch, a key's build whose every artifact has the digest of the
// build-agent build the mesh holds (`src1:`) is no move: it is registered with that build's artifacts, and a walk sends
// nothing for it.
func TestAKeyWithTheHeldBuildsDigestsIsNoMove(t *testing.T) {
open := theCatalogue(t)
ctx := t.Context()
inv := open.inventory
withTrunkList(t, true)
keys, trunk := aKeyFor("sudo", "6", fpOf('6'), "novox/mesh-catalog", "modules/sudo", "c0ffee0123456789")
k := keys.keys["sudo"]
held := inventory.Build{ID: "build-sudo-old", Module: "sudo", Repository: "novox/mesh-catalog", Path: "modules/sudo",
Commit: "0ld0ld0ld0ld0ld0", SourceFingerprint: "src1:" + strings.Repeat("9", 64),
Made: []inventory.Artifact{{Name: "a", Kind: "archive", Reference: k.Build.Artifacts[0].Reference}}}
held.Manifest, _ = json.Marshal(catalogue.Manifest{Module: "sudo", Version: "held"})
if err := inv.RecordBuild(ctx, held); err != nil {
t.Fatal(err)
}
_, kept, err := takeInKey(ctx, inv, keys, keyTake{Module: "sudo", Trunk: trunk, Ask: registrationAsk{Merge: trunk.Commit}})
if err != nil {
t.Fatal(err)
}
same, err := inv.SameSourceCommits(ctx, "sudo", kept.ID)
if err != nil || !same["0ld0ld0ld0ld0ld0"] {
t.Fatalf("A9: the key's build does not stand on the held build of the same digests: %v %v", same, err)
}
if shelf, _ := inv.Catalogue(ctx); shelf["sudo"].Version != "held" {
t.Fatalf("A9: sudo is registered as %q, not with the held build's artifacts", shelf["sudo"].Version)
}
}
-23
View File
@@ -451,29 +451,6 @@ var checkedBuildsOf = func(ctx context.Context, pulls []link.PullRef) ([]link.Ch
return link.ReadCheckBuilds(ctx, js, buildSeatHeld(ctx), pulls, time.Now().Add(-builder.CheckBuildsKept))
}
// mergedHeadsOf are the head commits the walk's merges merged, by pull request (novox/hq issue 703): a pull request
// merged twice in one walk, or whose head the announcement did not say, has none, and takes no check's build.
func mergedHeadsOf(p *inventory.Plan) map[link.PullRef]string {
heads := map[link.PullRef]string{}
if p.Delivery == nil {
return heads
}
seen := map[link.PullRef]bool{}
for _, m := range p.Delivery.Merges {
pull, ok := pullOf(m)
if !ok {
continue
}
if seen[pull] && heads[pull] != m.Head {
heads[pull] = ""
continue
}
seen[pull] = true
heads[pull] = m.Head
}
return heads
}
// pullOf is a merge's pull request: owner and repository, the last two parts of however the repository is spelled
// (owner/repo, a path on the git seat, a URL), and its number. False when the merge names none.
func pullOf(m inventory.PlanMerge) (link.PullRef, bool) {
+56 -77
View File
@@ -8,45 +8,22 @@ import (
"strings"
"github.com/novox/mesh-controller/internal/inventory"
"github.com/novox/mesh-controller/internal/link"
)
// **A module moves only when its program changes** (novox/hq issue 721). A merge check builds every module the
// delivery plan moves and compares each with the build the mesh holds of it; a walk is cut without a module whose
// check found its whole image the same as that build, while the mesh still holds it, and says so among the modules
// it left unchanged. A merge of one sentence in a package a program imports but never reaches moves no build of it.
// **A module moves only when its source changes** (novox/hq issue 721; from R4e by its fingerprint, ADR 0453 §6, ADR
// 0499; issue 1851, A9). A walk is cut without a module whose fingerprint at the commit the walk carries is the
// fingerprint of the build the mesh holds of it, and says so among the modules it left unchanged: a merge of a file no
// module reads moves nothing. A build the mesh holds of the old form (build-agent's `src1:`) has no fingerprint to
// compare: the module stays in the walk, and the rule of the standing build (equal digests, no move) judges it once its
// key is taken.
// sameAsRunning are the modules of a walk its checks found the same as the build the mesh holds, each with what its
// check said. **Only when every pull request the walk answers was checked at the head it merged, and every one of
// those checks built the module and found it the same as the build the mesh still holds**: a merge the walk answers
// without a pull request, a check of another head, a check that built it differently, or a held build that moved
// since, leaves the module in the walk.
func sameAsRunning(plan *inventory.Plan, builds []link.CheckedBuild, held map[string]string) map[string]string {
if plan.Delivery == nil || len(plan.Delivery.Merges) == 0 {
return nil
}
for _, m := range plan.Delivery.Merges {
if _, ok := pullOf(m); !ok {
return nil
}
}
pulls := pullsOfWalk(plan)
heads := mergedHeadsOf(plan)
// sameByFingerprint are the modules whose fingerprint now (`src2:`, the indexer's trunk value at the walk's commit) is
// the fingerprint of the build the mesh holds, each with why; one whose either is unknown, or of the old form, is not.
func sameByFingerprint(now, held map[string]string) map[string]string {
out := map[string]string{}
for name := range plan.Modules {
said := ""
for _, pull := range pulls {
b, ok := checkedBuildOf(builds, pull, heads[pull], name)
if !ok || !stillHeld(b.Built, name, held) {
said = ""
break
}
if said == "" {
said = b.Built.Same
}
}
if said != "" {
out[name] = said
for m, fp := range now {
if strings.HasPrefix(fp, "src2:") && held[m] == fp {
out[m] = fmt.Sprintf("unchanged: its fingerprint %s is the build the mesh holds", short(fp))
}
}
if len(out) == 0 {
@@ -55,65 +32,67 @@ func sameAsRunning(plan *inventory.Plan, builds []link.CheckedBuild, held map[st
return out
}
// checkedBuildOf is the newest build a check of the pull request at head made of the module; false when none did.
func checkedBuildOf(builds []link.CheckedBuild, pull link.PullRef, head, module string) (link.CheckedBuild, bool) {
if head == "" {
return link.CheckedBuild{}, false
// heldFingerprint is the source fingerprint of the build the mesh holds of a module: the newest build of the commit
// it is registered at.
func heldFingerprint(ctx context.Context, inv *inventory.Inventory, module string) (string, error) {
src, err := inv.SourceOf(ctx, module)
if err != nil {
return "", err
}
for _, b := range builds {
if b.Pull == pull && b.Built.Module == module && link.SameHead(b.Head, head) {
return b, true
fps, err := inv.BuildSourceFingerprints(ctx, module)
if err != nil {
return "", err
}
for commit, fp := range fps {
if sameCommitID(commit, src.BuiltFrom) {
return fp, nil
}
}
return link.CheckedBuild{}, false
return "", nil
}
// stillHeld is whether a check found every artifact of the module the same as a build the mesh still holds.
func stillHeld(b link.CheckBuilt, module string, held map[string]string) bool {
if b.Same == "" || b.NotBuilt != "" || len(b.Made) == 0 || len(b.SameAs) != len(b.Made) {
return false
}
for _, m := range b.Made {
was := pinnedDigest(b.SameAs[m.Name])
if was == "" || was != pinnedDigest(held[module+"/"+m.Name]) {
return false
}
}
return true
}
// pinnedDigest is a reference's pinned digest, empty when it is not pinned.
func pinnedDigest(ref string) string {
if i := strings.LastIndex(ref, "@"); i >= 0 {
return ref[i+1:]
}
return ""
}
// leaveSameAsRunning takes from a walk being cut every module its checks found the same as the build the mesh holds
// (sameAsRunning), and every dependent in the walk only for one of those (withDependentsOf): what was taken, with why. **Anything not read leaves the walk as it was**, said.
// leaveSameAsRunning takes from a walk being cut every module whose fingerprint is unchanged (sameByFingerprint), and
// every dependent in the walk only for one of those (withDependentsOf): what was taken, with why. **Anything not read
// leaves the walk as it was**, said.
func leaveSameAsRunning(ctx context.Context, inv *inventory.Inventory, plan *inventory.Plan, moved []string,
edges []inventory.Edge) map[string]string {
if plan.Delivery == nil || len(plan.Modules) == 0 {
if len(plan.Modules) == 0 {
return nil
}
pulls := pullsOfWalk(plan)
if len(pulls) == 0 {
return nil
}
builds, err := checkedBuildsOf(ctx, pulls)
entries, err := inv.Catalogued(ctx)
if err != nil {
fmt.Printf(" the checks' builds could not be read, so every module moves: %v\n", err)
fmt.Printf(" the catalogue could not be read, so every module moves: %v\n", err)
// empty-on-error: said above; nothing left out of the walk is the safe answer, every module moves as before
return nil
}
held, err := inv.RegisteredHeld(ctx)
keys, closeKeys, err := openPlanKeys()
if err != nil {
fmt.Printf(" what the mesh holds could not be read, so every module moves: %v\n", err)
fmt.Printf(" the indexer's fingerprints could not be read, so every module moves: %v\n", err)
// empty-on-error: said above; nothing left out of the walk is the safe answer, every module moves as before
return nil
}
same := withDependentsOf(sameAsRunning(plan, builds, held), plan, moved, edges)
defer closeKeys()
byName := map[string]inventory.Entry{}
for _, e := range entries {
byName[e.Manifest.Module] = e
}
now, held := map[string]string{}, map[string]string{}
for m := range plan.Modules {
e, known := byName[m]
if !known || isBuildersOwn(m) {
continue
}
v, err := keys.ModuleAt(ctx, m, plan.CommitOn(e.Source.Repository, followedBranch(e.Source.Ref)))
if err != nil || v.FP2 == "" {
continue // not written yet, or none: the module moves, and its key is waited for in the walk
}
fp, err := heldFingerprint(ctx, inv, m)
if err != nil {
continue
}
now[m], held[m] = v.FP2, fp
}
same := withDependentsOf(sameByFingerprint(now, held), plan, moved, edges)
if len(same) == 0 {
return nil
}
@@ -3,45 +3,38 @@
package main
import (
"context"
"strings"
"testing"
"time"
"git.novox.be/novox/mesh-sdk/go/catalogue"
"github.com/novox/mesh-controller/internal/inventory"
"github.com/novox/mesh-controller/internal/link"
)
func TestAWalkWhoseEveryBuildCameOutTheSameIsDone(t *testing.T) {
// R4e A9 (ADR 0453 §6, ADR 0499; issue 1851), against the store and a real bus: a merge of a file no module reads moves
// nothing. Its module's fingerprint at the merge, as the indexer wrote it, is the fingerprint of the build the mesh holds,
// so the walk is cut done with nothing to walk, and its record names the module unchanged and why — the walk
// mesh-delivery reads as delivered, never stranded; no build is asked.
func TestAWalkWhoseEveryFingerprintIsUnchangedIsDone(t *testing.T) {
open := windowed(t)
endsItsLine(t)
asksRecorded(t)
noBuildAsked(t)
b := aBuilderOnTheBus(t)
ctx := t.Context()
inv := open.inventory
if err := inv.RecordBuild(ctx, inventory.Build{ID: "build-one", Repository: "novox/one", Ref: "main",
Module: "one", Commit: "c0", On: "builder", Asked: t0.Add(-time.Hour),
Made: []inventory.Artifact{{Name: "one", Kind: "image", Reference: "store/one@sha256:aaaa"}},
Sources: []inventory.BuildSource{{Paths: []string{"main.go", "go.mod"}}}}); err != nil {
fp := fpOf('5')
if err := inv.RecordBuild(ctx, inventory.Build{ID: "key-held", Repository: "novox/one", Ref: "main",
Module: "one", Commit: "c0", On: "ace", SourceFingerprint: fp,
Made: []inventory.Artifact{{Name: "one", Kind: "image", Reference: "store/one@sha256:aaaa"}}}); err != nil {
t.Fatal(err)
}
// What runs: the module registered with that build's image (RegisteredHeld reads the module table).
if err := inv.RegisterModule(ctx, catalogue.Manifest{Module: "one", Version: "1", Resources: []map[string]any{
{"id": "app", "type": "container", "name": "one", "image": "store/one@sha256:aaaa"}}},
inventory.Source{Repository: "novox/one", Ref: "main", BuiltFrom: "c0", Asked: time.Now()}); err != nil {
t.Fatal(err)
}
pull := link.PullRef{Owner: "novox", Repo: "one", Number: 7}
was := checkedBuildsOf
checkedBuildsOf = func(context.Context, []link.PullRef) ([]link.CheckedBuild, error) {
return []link.CheckedBuild{{Pull: pull, Head: "headaaaaaaaa", Built: link.CheckBuilt{Module: "one", Key: "k",
Made: []link.MadeArtifact{{Name: "one", Kind: "image", Reference: "scratch/one@sha256:bbbb"}},
Same: "unchanged: its program is the same as running build one aaaa",
SameAs: map[string]string{"one": "10.0.0.1:5000/one@sha256:aaaa"}}}}, nil
}
t.Cleanup(func() { checkedBuildsOf = was })
code := repoMerge("one", "one1aaaaaaaa", t0)
code.Number, code.HeadSHA = 7, "headaaaaaaaa1111"
b.indexed(t, "one", "1", "novox/one", "", code.Commit, fp)
hear(t, open, code, t0.Add(time.Second))
cutAt(t, open, t0.Add(2*time.Minute))
ws, _ := walks(t, open)
@@ -52,7 +45,7 @@ func TestAWalkWhoseEveryBuildCameOutTheSameIsDone(t *testing.T) {
if w.State != inventory.PlanDone || len(w.Tiers) != 0 || len(w.Modules) != 0 {
t.Fatalf("the walk reads %s, tiers %v, modules %v; want done with nothing to walk", w.State, w.Tiers, w.Modules)
}
if w.Delivery == nil || !strings.Contains(w.Delivery.Unchanged["one"], "its program is the same as running build") {
t.Fatalf("the walk does not say one matched what runs: %+v", w.Delivery)
if w.Delivery == nil || !strings.Contains(w.Delivery.Unchanged["one"], "is the build the mesh holds") {
t.Fatalf("the walk does not say one's fingerprint is unchanged: %+v", w.Delivery)
}
}
+12 -52
View File
@@ -5,66 +5,26 @@ import (
"testing"
"github.com/novox/mesh-controller/internal/inventory"
"github.com/novox/mesh-controller/internal/link"
)
// TestAWalkLeavesOutAModuleWhoseProgramDidNotChange is novox/hq issue 721: mesh-controller #335 changed one sentence
// that mesh-builder never reaches; its check found build-agent the same as the build the mesh holds, so the walk moves
// mesh-controller alone and says build-agent is unchanged.
func TestAWalkLeavesOutAModuleWhoseProgramDidNotChange(t *testing.T) {
pull := link.PullRef{Owner: "novox", Repo: "mesh-controller", Number: 335}
head := "34050dc6aaaabbbbccccdddd"
aPlan := func() *inventory.Plan {
return &inventory.Plan{Tiers: [][]string{{"mesh-controller", "build-agent"}},
Modules: map[string]*inventory.PlanModule{"mesh-controller": {}, "build-agent": {}},
Delivery: &inventory.PlanDelivery{Merges: []inventory.PlanMerge{{Repository: "novox/mesh-controller",
Commit: "c2ec0a60", Number: 335, Head: head}}}}
}
held := map[string]string{"build-agent/agent": "store/build-agent@sha256:aaaa",
"mesh-controller/controller": "store/mesh-controller@sha256:cccc"}
builds := []link.CheckedBuild{
{Pull: pull, Head: head[:12], Built: link.CheckBuilt{Module: "build-agent", Key: "k1",
Made: []link.MadeArtifact{{Name: "agent", Kind: "image", Reference: "scratch/build-agent@sha256:bbbb"}},
Same: "unchanged: its program is the same as running build agent aaaa",
SameAs: map[string]string{"agent": "10.0.0.1:5000/build-agent@sha256:aaaa"}}},
{Pull: pull, Head: head[:12], Built: link.CheckBuilt{Module: "mesh-controller", Key: "k2",
Made: []link.MadeArtifact{{Name: "controller", Kind: "image", Reference: "scratch/mesh-controller@sha256:dddd"}}}},
}
p := aPlan()
same := sameAsRunning(p, builds, held)
if len(same) != 1 || same["build-agent"] == "" {
// TestAWalkLeavesOutAModuleWhoseFingerprintDidNotChange is novox/hq issue 721 on fingerprints (R4e A9, ADR 0453 §6):
// mesh-controller #335 changed one sentence that mesh-builder never reaches; the indexer's fingerprint of build-agent at
// the merge is the fingerprint of the build the mesh holds, so the walk moves mesh-controller alone and says build-agent
// is unchanged. A held build of the old form, or a fingerprint not written, leaves the module in the walk.
func TestAWalkLeavesOutAModuleWhoseFingerprintDidNotChange(t *testing.T) {
a, b := "src2:"+strings.Repeat("a", 64), "src2:"+strings.Repeat("b", 64)
now := map[string]string{"build-agent": a, "mesh-controller": b, "old": a, "unwritten": ""}
held := map[string]string{"build-agent": a, "mesh-controller": a, "old": "src1:" + strings.Repeat("a", 64)}
same := sameByFingerprint(now, held)
if len(same) != 1 || !strings.Contains(same["build-agent"], "is the build the mesh holds") {
t.Fatalf("the walk leaves out %v, want build-agent alone", same)
}
p := &inventory.Plan{Tiers: [][]string{{"mesh-controller", "build-agent"}},
Modules: map[string]*inventory.PlanModule{"mesh-controller": {}, "build-agent": {}}}
withhold(p, []string{"build-agent"}, nil)
if len(p.Tiers) != 1 || len(p.Tiers[0]) != 1 || p.Tiers[0][0] != "mesh-controller" {
t.Fatalf("the walk moves %v, want mesh-controller alone", p.Tiers)
}
// **The held build moved since the check**: what it was compared with no longer runs.
moved := map[string]string{"build-agent/agent": "store/build-agent@sha256:eeee"}
if same := sameAsRunning(aPlan(), builds, moved); len(same) != 0 {
t.Fatalf("a module compared with a build the mesh no longer holds is left out: %v", same)
}
// **A check of another head** says nothing of this merge.
other := aPlan()
other.Delivery.Merges[0].Head = "ffffffffffffffff"
if same := sameAsRunning(other, builds, held); len(same) != 0 {
t.Fatalf("a check of another head left out %v", same)
}
// **Another pull request the walk answers** that did not find it the same leaves it in.
two := aPlan()
two.Delivery.Merges = append(two.Delivery.Merges, inventory.PlanMerge{Repository: "novox/mesh-catalog",
Commit: "1234", Number: 9, Head: "abcdefabcdefabcdef"})
if same := sameAsRunning(two, builds, held); len(same) != 0 {
t.Fatalf("a module another merge moves is left out: %v", same)
}
// **A merge with no pull request** leaves the walk as it is.
direct := aPlan()
direct.Delivery.Merges[0].Number = 0
if same := sameAsRunning(direct, builds, held); len(same) != 0 {
t.Fatalf("a walk answering a merge without a pull request left out %v", same)
}
}
// TestADependentOfABaseThatCameOutTheSameIsLeftOut is ADR 0267 rule 3 (novox/hq issue 607): a dependent is in a walk
+47 -1
View File
@@ -2,6 +2,8 @@ package inventory
import (
"context"
"encoding/json"
"strings"
"github.com/novox/mesh-controller/internal/chain"
)
@@ -43,6 +45,11 @@ func (i *Inventory) sourceRows(ctx context.Context, module string) (map[string][
// standing is, of a module's builds newest first, the index of the build that stands for the build at
// `from`: the oldest of the unbroken run of builds behind it with its source fingerprint.
//
// **Across the switch to the new form** (novox/hq ADR 0453 §6, ADR 0499; R4e A9): a build of the new form (`src2:`, taken
// from the builder's key) continues the run of the build before it of the old form (`src1:` or none, build-agent's)
// when every artifact of the two has the same digest: the fingerprints cannot be compared, the programs can, and equal
// digests are no move. Past it the run goes on by the old fingerprint.
func standing(builds []sourceRow, from int) int {
at := from
fp := builds[from].fingerprint
@@ -50,14 +57,53 @@ func standing(builds []sourceRow, from int) int {
return at
}
for j := from + 1; j < len(builds); j++ {
if builds[j].fingerprint != fp || builds[j].failedGate {
if builds[j].failedGate {
break
}
if builds[j].fingerprint != fp {
if !newForm(fp) || newForm(builds[j].fingerprint) || !sameDigests(builds[at].made, builds[j].made) {
break
}
fp = builds[j].fingerprint
}
at = j
}
return at
}
// newForm says a source fingerprint is of the new form (ADR 0453 §6).
func newForm(fp string) bool { return strings.HasPrefix(fp, "src2:") }
// sameDigests says two builds' artifacts, as recorded (JSON of []Artifact), are the same programs: the same names, each
// with the same digest, wherever each was pushed. A build naming an artifact with no digest is never the same.
func sameDigests(a, b []byte) bool {
digests := func(raw []byte) map[string]string {
var made []Artifact
if json.Unmarshal(raw, &made) != nil || len(made) == 0 {
return nil
}
out := map[string]string{}
for _, m := range made {
d := digestIn(m.Reference)
if d == "" {
return nil
}
out[m.Name] = d
}
return out
}
da, db := digests(a), digests(b)
if da == nil || db == nil || len(da) != len(db) {
return false
}
for name, d := range da {
if db[name] != d {
return false
}
}
return true
}
// StandingBuild is the build that stands for a module's build (novox/hq issue 280): the oldest build of
// the unbroken run of builds with its source fingerprint, at or before it — itself when its fingerprint
// is empty or it starts the run. Answers its id and the manifest it was recorded with; empty when the
@@ -0,0 +1,49 @@
package inventory
import (
"encoding/json"
"testing"
)
func madeOf(t *testing.T, refs map[string]string) []byte {
t.Helper()
var made []Artifact
for name, ref := range refs {
made = append(made, Artifact{Name: name, Kind: "bundle", Reference: ref})
}
raw, err := json.Marshal(made)
if err != nil {
t.Fatal(err)
}
return raw
}
// R4e A9 (ADR 0453 §6, ADR 0499): at the switch, a build taken from the builder's key (`src2:`) whose every artifact has
// the digest of the build-agent build before it (`src1:`) is no move: it stands on that build, wherever each was pushed.
// One digest that differs is a move, as is a build of the old form after one of the new.
func TestAKeysBuildWithTheOldBuildsDigestsIsNoMove(t *testing.T) {
const d1, d2 = "sha256:1111111111111111111111111111111111111111111111111111111111111111",
"sha256:2222222222222222222222222222222222222222222222222222222222222222"
old := sourceRow{id: "build-1", fingerprint: "src1:aa", made: madeOf(t, map[string]string{
"tools": "artifact-store://x/tools/blobs/" + d1, "img": "artifact-store://x/img@" + d2})}
key := sourceRow{id: "key-bb-1", fingerprint: "src2:bb", made: madeOf(t, map[string]string{
"tools": "artifact-store://x/tools/blobs/" + d1, "img": "registry.internal:5000/x/img@" + d2})}
if at := standing([]sourceRow{key, old}, 0); at != 1 {
t.Fatalf("the key's build with the same digests stands on %d, want the old build", at)
}
moved := key
moved.made = madeOf(t, map[string]string{"tools": "artifact-store://x/tools/blobs/" + d2, "img": "x/img@" + d2})
if at := standing([]sourceRow{moved, old}, 0); at != 0 {
t.Fatalf("a key's build with another digest stands on %d, want itself (a move)", at)
}
// Two key builds of one fingerprint are one run, as two builds of one fingerprint always were.
again := key
again.id = "key-bb-2"
if at := standing([]sourceRow{again, key, old}, 0); at != 2 {
t.Fatalf("a run of one new fingerprint on an old build of the same digests stands on %d", at)
}
// An old build after a new one never continues the new one's run.
if at := standing([]sourceRow{old, key}, 0); at != 0 {
t.Fatalf("an old build continues a new one's run: %d", at)
}
}