diff --git a/modules/mesh-delivery-indexer/cmd/mesh-delivery-indexer/named_test.go b/modules/mesh-delivery-indexer/cmd/mesh-delivery-indexer/named_test.go index e8153b5c..2c5cfb13 100644 --- a/modules/mesh-delivery-indexer/cmd/mesh-delivery-indexer/named_test.go +++ b/modules/mesh-delivery-indexer/cmd/mesh-delivery-indexer/named_test.go @@ -74,6 +74,9 @@ func TestTheTrunkIsTheDirectoriesTheForgeListsTrees(t *testing.T) { // 1900/AC3 for _, row := range []struct{ name, answer, want string }{ {"replacing: runs old, want new and self", `{"runs":{"fp":"src2:old"},"want":{"fp":"src2:new","self":true}}`, "src2:new"}, {"a want the node does not compose", `{"runs":{"fp":"src2:old"},"want":{"fp":"src2:new"}}`, "src2:old"}, + {"replacing: the apply of the want failed or is held", `{"runs":{"fp":"src2:old"},"want":{"fp":"src2:new","self":true},"attempt":{"fp":"src2:new","held":true}}`, "src2:old"}, + {"replacing: an attempt of another build", `{"runs":{"fp":"src2:old"},"want":{"fp":"src2:new","self":true},"attempt":{"fp":"src2:older"}}`, "src2:new"}, + {"the want already runs", `{"runs":{"fp":"src2:new"},"want":{"fp":"src2:new","self":true}}`, "src2:new"}, {"a removed want", `{"runs":{"fp":"src2:old"},"want":{"fp":"src2:new","self":true,"removed":true}}`, "src2:old"}, {"no build runs yet, want self", `{"want":{"fp":"src2:new","self":true}}`, "src2:new"}, } { diff --git a/modules/mesh-delivery-indexer/cmd/mesh-delivery-indexer/ownbuild.go b/modules/mesh-delivery-indexer/cmd/mesh-delivery-indexer/ownbuild.go index cf7bb311..3491b443 100644 --- a/modules/mesh-delivery-indexer/cmd/mesh-delivery-indexer/ownbuild.go +++ b/modules/mesh-delivery-indexer/cmd/mesh-delivery-indexer/ownbuild.go @@ -30,15 +30,26 @@ func ownBuildVia(call toolCaller, node string) func(ctx context.Context) (string Self bool `json:"self"` Removed bool `json:"removed"` } `json:"want"` + // Attempt is the engine's last apply that did not end running (failed, or held), with the build it was of. + Attempt *struct { + FP string `json:"fp"` + } `json:"attempt"` } if err := json.Unmarshal(raw, &a); err != nil { return "", fmt.Errorf("the answer of %s is not a composition: %w", compositionVerb, err) } - // While the engine replaces the indexer, this process is the successor: what runs still names the old build, and the want, - // composed by the node itself (self), names this one. The start fixes that build (quick fix, ADR 0639 §7: nothing is learnt - // later and nothing waits on a timer); a process of the old build that is started again in that window would take it too. - if a.Want != nil && a.Want.Self && !a.Want.Removed && a.Want.FP != "" { - return a.Want.FP, nil + // While the engine's apply of the want is in progress (what runs still names the old build, the want names another, and no + // attempt names that want as failed or held), this process is the successor and fixes the want's build. self is the node's + // standing way of composing the module, not a mark of this process, so it is not enough alone: an old process started again + // while the want names a successor that does not run (its apply failed or is held) takes what runs. The start fixes the + // build once (quick fix, ADR 0639 §7: nothing is learnt later, nothing waits on a timer); the thorough form is the + // engine passing each process the fingerprint it starts it with. + running := "" + if a.Runs != nil { + running = a.Runs.FP + } + if w := a.Want; w != nil && w.Self && !w.Removed && w.FP != "" && w.FP != running && (a.Attempt == nil || a.Attempt.FP != w.FP) { + return w.FP, nil } if a.Runs == nil || a.Runs.FP == "" { return "", errors.New("the engine says no fingerprint of the indexer runs")