Keep a check build whose key is unchanged instead of building it again
mesh/merge-gate head 1666747a2246 merged into trunk 04730b54a8f2 — pass: builds build-agent, mesh-controller → ace, g14, novox, shanks; no bus step; every …
mesh/repo-check head 1666747a2246 merged into trunk 04730b54a8f2 — pass: its merge-check.sh passed
mesh/delivery superseded: a newer head of the same pull request

A fresh check asked when only the trunk moved rebuilt every image, though
the reuse key said nothing it is built from changed: a controller check
took about 12 minutes instead of 5.5. The check now keeps a build kept
under its key when a verdict of the build seat names that key and every
digest, names it as the build's own check so a walk takes it, and the
cleanup keeps it while any check names it (novox/hq issue 703).
This commit is contained in:
2026-10-14 09:41:39 +02:00
parent 04730b54a8
commit 1666747a22
11 changed files with 328 additions and 22 deletions
+32 -1
View File
@@ -374,7 +374,7 @@ func build(ctx context.Context, run Runner, publish Publisher,
// only when asked — a check, a dry run, `build --ref` and `rebuild` never are. Its key is taken by the same
// steps a build takes, stopped before anything is built; what it takes is copied by digest.
if reuse != nil && reuse.keyFirst {
_, key, err := keyFor(ctx, run, publish, manifest, within, workspace, commit, credentials, artifacts, args,
keyed, key, err := keyFor(ctx, run, publish, manifest, within, workspace, commit, credentials, artifacts, args,
held, npmrcPath, npmrc, seatBases, src, say)
if err == nil && key.Key == "" {
err = fmt.Errorf("%w: %s", errNoKey, key.Why)
@@ -383,6 +383,11 @@ func build(ctx context.Context, run Runner, publish Publisher,
say("reuse", "not built for the merge: %v", err)
return Result{Commit: commit, NoKey: err.Error()}, err
}
// **A check keeps a build of the same key a verdict names** (novox/hq issue 703): asked again when only
// the trunk moved, it builds nothing the earlier check built of an unchanged source.
if kept := keptCheckBuild(ctx, *reuse, manifest.Module, key.Key, len(artifacts), say); kept != nil {
built, src, reused = kept.Made, keyed, kept
}
} else if reuse != nil && reuse.Scratch != nil {
taken, keyed, why := takeACheckBuild(ctx, run, publish, *reuse, manifest, within, workspace, commit,
credentials, artifacts, args, held, npmrcPath, npmrc, seatBases, src, onTrunk, say)
@@ -492,6 +497,32 @@ func takeACheckBuild(ctx context.Context, run Runner, publish Publisher, reuse R
return taken, keyed, ""
}
// keptCheckBuild is the build a check keeps instead of building (novox/hq issue 703): the one kept under the
// module's key, when a verdict of the build seat names its key and every digest — the record alone is never trusted
// (ADR 0350 §2) — and it holds every artifact the module declares. Nil, and the check builds, otherwise.
func keptCheckBuild(ctx context.Context, reuse Reuse, module, key string, artifacts int,
say func(step, format string, args ...any)) *Reused {
if reuse.Scratch == nil || len(reuse.Named) == 0 {
return nil
}
found, ok, err := reuse.Scratch.Find(ctx, module, key)
switch {
case err != nil:
say("reuse", "the scratch namespace could not be read, so the check builds: %v", err)
return nil
case !ok:
return nil
case unnamed(reuse.Named, module, key, found) != "":
say("reuse", "a build is kept under %s and no verdict names it with its digests: built", twelve(key[3:]))
return nil
case len(found.Made) != artifacts:
return nil
}
kept := &Reused{From: found, Made: found.Made}
say("reuse", "kept, not built: %s", strings.Replace(kept.Words(), "reused from", "the build of", 1))
return kept
}
// keyFor takes a build's reuse key by the steps a build takes, each artifact stopped before anything is built or
// published (ADR 0350 §2): its own copy of the source inputs, so the build's are untouched.
func keyFor(ctx context.Context, run Runner, publish Publisher, manifest catalogue.Manifest,