Compute from a build of build-agent only once a toolchain is read, so a restart drops no fingerprint; fixtures as live (issue 1727)
mesh/merge-gate head 61fea8314fe8 merged into trunk 318064ce206d — pass: builds mesh-delivery-indexer → novox; no bus step; every machine composes with the…
mesh/repo-check head 61fea8314fe8 merged into trunk 318064ce206d — pass: its merge-check.sh passed
mesh/delivery delivered

This commit is contained in:
2026-10-21 16:45:51 +02:00
parent 5f9ed2b886
commit 61fea8314f
3 changed files with 47 additions and 10 deletions
@@ -271,7 +271,7 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro
known.Delete(e.Key())
}
if ix := started.Load(); ix != nil && strings.HasPrefix(e.Key(), index.BuilderBuiltBy+".build.") {
if err := ix.Recompute(ctx); err != nil {
if err := ix.RecomputeForBuild(ctx); err != nil {
logger.Printf("computing the new-form fingerprints again after %s: %v", e.Key(), err)
}
}
@@ -284,10 +284,6 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro
go func() { errs <- follow(ctx, w, each) }()
ix := &index.Indexer{Store: kvStore{candidates}, Modules: kvStore{modules}, Manifests: kvStore{manifests}, Builds: known, Logf: logger.Printf}
started.Store(ix)
if err := ix.Recompute(ctx); err != nil { // the builds heard before this, which moved no one
logger.Printf("computing the new-form fingerprints at start: %v", err)
}
// The gate's source bucket (mesh-delivery-gate_source) comes with the gate (release 3e, novox/hq ADR 0450 §1): its key
// main is the mesh's source revision, which stamps the modules (issue 1344). Where it does not exist yet the indexer does
// not wait for it (that needs a timer, ADR 0437 §8): it says so, and is started again with the gate.
@@ -313,6 +309,12 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro
if err := tools.start(); err != nil {
return err
}
// Only now, with the toolchains read, are the builds heard to compute from (a build before it would write every Go
// module as having no toolchain reported, and back); the builds heard until now are computed once.
started.Store(ix)
if err := ix.RecomputeForBuild(ctx); err != nil {
logger.Printf("computing the new-form fingerprints at start: %v", err)
}
// A build module says each time it wrote its toolchains key: its stream, and the indexer's durable consumer on it, are
// made by the controller as the forge's are. Where the stream is not there yet the indexer says so and is started again
// with the build module, as with the gate's source.
@@ -397,3 +397,18 @@ func (ix *Indexer) builderInput() (src, none string) {
}
return "", fmt.Sprintf("%s, which builds it, has no successful trunk build that recorded a source fingerprint", BuilderBuiltBy)
}
// RecomputeForBuild is Recompute for a build of BuilderBuiltBy heard on the builds bucket, or the one at start (issue 1727).
// While the indexer has read no toolchain at all (it has just started and the toolchains buckets are not settled), every
// Go module would be written as having no toolchain reported and then back: it computes nothing then, and the settling of
// the toolchains computes them.
func (ix *Indexer) RecomputeForBuild(ctx context.Context) error {
ix.mu.Lock()
none := len(ix.toolchains) == 0
ix.mu.Unlock()
if none {
ix.logf("the builds changed before any toolchain was read: the new-form fingerprints are computed when they are")
return nil
}
return ix.Recompute(ctx)
}
@@ -46,7 +46,11 @@ const (
)
func agentBuild(id, src, at string) [2]any {
raw := `{"id":"` + id + `","repository":"` + agentCloneURL + `","ref":"main","module":"build-agent","commit":"cada7e3c",` +
commit := "cada7e3c"
if id == "build-1792587792086548572" {
commit = "f10fbda1"
}
raw := `{"id":"` + id + `","repository":"` + agentCloneURL + `","ref":"main","module":"build-agent","commit":"` + commit + `",` +
`"path":"modules/build-agent","at":"` + at + `","source-fingerprint":"` + src + `","source-inputs":` + aInputs + `}`
return [2]any{delivery.BuildKey("build-agent", id), []byte(raw)}
}
@@ -63,10 +67,7 @@ func newFormIndexer(t *testing.T, mods ...mod) (*Indexer, *memStore) {
if err := ix.Apply(context.Background(), f); err != nil {
t.Fatal(err)
}
// build-agent's value of today's form, from a merge of the controller (modules.build-agent holds its fingerprint).
if err := ix.Apply(context.Background(), mergedMsg(buildAgentRepo, map[string]string{"modules/build-agent": "agent-tree-1"})); err != nil {
t.Fatal(err)
}
// build-agent holds no fingerprint of today's form, as live (no merge of the controller is applied; issue 1727).
return ix, store
}
@@ -189,6 +190,25 @@ func TestBasesAndTheBuilderAreComputedBeforeTheModulesTakingThem(t *testing.T) {
}
}
func TestABuildHeardBeforeAnyToolchainIsReadDropsNoFingerprint(t *testing.T) { // 1727 (review: a restart flapped every FP2)
ix, mods := newFormIndexer(t, builderModules(t)...)
reportToolchain(t, ix, "mesh-build-go", "novox", "go", "go1.27.1")
before, _ := moduleOf(t, mods, "mesh-tools")
if before.FP2 == "" {
t.Fatalf("mesh-tools has no fp2 to begin with: %+v", before)
}
// a restarted indexer has read no toolchains bucket yet
ix.mu.Lock()
ix.toolchains = nil
ix.mu.Unlock()
if err := ix.RecomputeForBuild(context.Background()); err != nil {
t.Fatal(err)
}
if m, _ := moduleOf(t, mods, "mesh-tools"); m.FP2 != before.FP2 || m.None2 != "" {
t.Errorf("mesh-tools: fp2 = %q none2 = %q, want fp2 %q kept", m.FP2, m.None2, before.FP2)
}
}
func TestTheBuilderHasNoFingerprintWithoutATrunkBuildOfBuildAgentAndSaysSo(t *testing.T) { // 1727
ix, mods := newFormIndexer(t, builderModules(t)...)
reportToolchain(t, ix, "mesh-build-go", "novox", "go", "go1.27.1")