diff --git a/internal/agent/agent.go b/internal/agent/agent.go index cefbd72..7c14c4a 100644 --- a/internal/agent/agent.go +++ b/internal/agent/agent.go @@ -671,6 +671,10 @@ func (a *Agent) baseline(ctx context.Context, module string, w wantNow, comp com a.mu.Lock() a.running[module] = delivery.Have{FP: h.FP, Composed: h.Composed, Phase: delivery.PhaseRunning} a.mu.Unlock() + // Said again, so the session holds the module and its key carries the health this engine judges, + // not the one an older engine left there (xorg's unknown on g14 and shanks, 2026-10-25). + a.env.Haves.Write(ctx, Have{Module: module, FP: h.FP, Composed: h.Composed, + Phase: string(delivery.PhaseRunning), Detail: h.Detail}) return true } return false diff --git a/internal/agent/agent_test.go b/internal/agent/agent_test.go index 7319982..8709ee6 100644 --- a/internal/agent/agent_test.go +++ b/internal/agent/agent_test.go @@ -313,6 +313,40 @@ func TestStartingOnItsOwnHaveAppliesNothing(t *testing.T) { } } +// A module found running on its own have after a restart is said again, once, through the haves: the session then holds +// it and writes the health this engine judges. Not said, its key kept the health an older engine wrote: xorg's have on +// g14 and shanks said unknown from before the engine said a module with nothing to judge ok, and the assigner held every +// module requiring x11-display back for it (2026-10-25). +func TestStartingOnItsOwnHaveSaysTheHaveAgainSoItsHealthIsThisEngines(t *testing.T) { + b := newBed(t) + fp := fpOf("a") + ck, craw, dig := composition(t, "xorg", fp, false, file("xorg", "x", "1")) + wk, wraw := wantFor(t, "xorg", fp, dig, "") + b.haves.stored = map[string]Have{"xorg": {Module: "xorg", FP: fp, Composed: dig, Phase: "running", Detail: "kept"}} + b.comps.before(ck, craw) + b.wants.before(wk, wraw) + stop := b.run() + defer stop() + eventually(t, "the have found running was not said again", func() bool { + b.haves.mu.Lock() + defer b.haves.mu.Unlock() + return len(b.haves.log) > 0 + }) + time.Sleep(50 * time.Millisecond) + b.haves.mu.Lock() + log := append([]Have(nil), b.haves.log...) + b.haves.mu.Unlock() + if len(log) != 1 { + t.Fatalf("the have was said %d times, want once: %+v", len(log), log) + } + if h := log[0]; h.Module != "xorg" || h.FP != fp || h.Composed != dig || h.Phase != string(delivery.PhaseRunning) || h.Detail != "kept" { + t.Fatalf("the have said again: %+v", h) + } + if got := b.appliedList(); len(got) != 0 { + t.Fatalf("a module that runs what the want names was applied: %v", got) + } +} + // A module whose kept resources differ from the composition is applied, and a failed apply is retried only by a change // of either key. func TestAFailedApplyIsRetriedOnlyByAChangeOfEitherKey(t *testing.T) { diff --git a/internal/link/have_test.go b/internal/link/have_test.go index a16b840..8101fd9 100644 --- a/internal/link/have_test.go +++ b/internal/link/have_test.go @@ -521,3 +521,25 @@ func TestAModuleWithNothingToJudgeIsOkOnceLookedAt(t *testing.T) { t.Fatalf("a failed module was said ok") } } + +// A have an older engine left unknown is written over with this engine's health once the module is said again, as the +// node agent says a module it finds running on its own have: a packages-only module (xorg, nothing long-running) is ok +// after a look, so the assigner no longer holds back what requires its provision (2026-10-25, g14 and shanks). +func TestAModuleSaidAgainOverAnOlderEnginesUnknownIsWrittenWithThisEnginesHealth(t *testing.T) { + l := newHaveLink() + fp := "src2:" + strings.Repeat("a", 64) + dig := "sha256:" + strings.Repeat("b", 64) + l.put(t, "n1.xorg", delivery.Have{V: delivery.Version, FP: fp, Composed: dig, Phase: delivery.PhaseRunning, + Health: delivery.HealthUnknown}) + q, _ := aHaveQueue(l) + if !q.SayHealth(context.Background(), *healthOf(map[string]string{"web": StateHealthy})) { + t.Fatal("not said") + } + if h := l.have(t, "n1.xorg"); h.Health != delivery.HealthUnknown { + t.Fatalf("a module the session does not hold was written: %+v", h) + } + q.NoteModule(context.Background(), ModuleFact{Module: "xorg", FP: fp, Composed: dig}) + if h := l.have(t, "n1.xorg"); h.Health != delivery.HealthOK || h.FP != fp || h.Composed != dig || h.Phase != delivery.PhaseRunning { + t.Fatalf("said again after a look: %+v", h) + } +}