diff --git a/internal/agent/agent.go b/internal/agent/agent.go index 7b6494c..33be705 100644 --- a/internal/agent/agent.go +++ b/internal/agent/agent.go @@ -1577,6 +1577,11 @@ func (a *Agent) waitsForYes(ctx context.Context, module string, w wantNow, comp r, ran := a.running[module] if ran && r.Composed == comp.Dig && r.Phase != delivery.PhaseRemoved { a.mu.Unlock() + // Composed back to what runs: a have still waiting for a yes for a move since gone says running again, or it waits + // for a yes nobody needs (issue 774, 2026-10-31: four providers stayed waiting-yes once their consumers composed). + if h, ok := a.env.Haves.Read(ctx, module); ok && h.Phase == string(delivery.PhaseWaitingYes) { + a.writeHave(ctx, Have{Module: module, FP: r.FP, Composed: r.Composed, Phase: string(delivery.PhaseRunning), Sealed: h.Sealed}) + } return false } from, known := a.runningComposition(module) diff --git a/internal/agent/agent_test.go b/internal/agent/agent_test.go index 9ec0741..dc5768d 100644 --- a/internal/agent/agent_test.go +++ b/internal/agent/agent_test.go @@ -1432,10 +1432,27 @@ func TestTheNodesOwnCompositionIsAppliedWhenItDiffersAndARecordMoveWaitsForAYes( raw, _ := json.Marshal(map[string]any{"id": module + ".server", "type": "container", "module": module, "name": module, "image": image}) return raw } - dk, draw, _ := unsigned("db", fp, container("db@sha256:"+strings.Repeat("1", 64))) + dk, draw, ddig := unsigned("db", fp, container("db@sha256:"+strings.Repeat("1", 64))) b.comps.put(dk, draw) want("db", fp, "") eventually(t, "the record module's first composition was not applied", func() bool { return len(b.appliedList()) == 4 }) + // A revision at the same build that recreates the container waits for a yes; composed back to what runs, the have + // says running again, not waiting-yes for a move nobody needs (issue 774, 2026-10-31: ace.postgres, novox.postgres, + // novox.keycloak and novox.mongodb stayed waiting-yes after their providers' received files went back). + _, drawMoved, ddigMoved := unsigned("db", fp, container("db@sha256:"+strings.Repeat("7", 64))) + b.comps.put(dk, drawMoved) + eventually(t, "db's revision did not wait for a yes", func() bool { + h := b.haves.last("db") + return h.Phase == string(delivery.PhaseWaitingYes) && h.Waits == ddigMoved + }) + b.comps.put(dk, draw) + eventually(t, "db's have stays waiting-yes after its composition went back to what runs", func() bool { + h := b.haves.last("db") + return h.Phase == string(delivery.PhaseRunning) && h.Composed == ddig && h.Waits == "" + }) + if got := len(b.appliedList()); got != 4 { + t.Fatalf("a composition equal to what runs was applied again: %d applies", got) + } fpB := fpOf("b") dk2, draw2, ddig2 := unsigned("db", fpB, container("db@sha256:"+strings.Repeat("2", 64))) // rec is a record module this agent has run nothing of since its start (the switch and every delivery of the