Keep main's rule for which haves run inside the node's own composition
mesh/delivery merged: source.main moved to novox/mesh-host@ffa63d25a066
mesh/check being carried to 76c7ddfc5227 on ffa63d25a066 | from a4713e2d28d7

Review round 1: a have naming no build in a phase but running is also written for a
module that runs inside the node's own (waiting for a yes), so taking it out would
drop its resources before the yes. The fp the held have now keeps already fixes
postgres (issue 1289); a test drives an interrupted backup end to end.

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
This commit is contained in:
2026-10-31 16:38:05 +01:00
parent 39365c62c9
commit 76c7ddfc52
3 changed files with 22 additions and 9 deletions
+21
View File
@@ -591,6 +591,27 @@ func TestADataModuleIsBackedUpFirstAndHeldWhenItFails(t *testing.T) {
return h.Phase == string(delivery.PhaseRunning) && h.FP == fp2 && h.Composed == dig2
})
// The node agent stopped while the backup ran, the module known only from its have: the held have names what runs
// and says it was interrupted (postgres on novox, 2026-10-31, held naming nothing).
ib := newBed(t)
ib.comps.before(ck1, craw1)
ib.comps.before(ck2, craw2)
iwk, iwraw := wantFor(t, "db", fp2, dig2, delivery.BackupAgain)
ib.wants.before(iwk, iwraw)
ib.haves.stored = map[string]Have{"db": {Module: "db", FP: fp1, Composed: dig1, Phase: string(delivery.PhaseRunning)}}
began := make(chan struct{})
ib.env.Backup = func(ctx context.Context, _ string) Backup {
close(began)
<-ctx.Done()
return Backup{Words: "the backup's ask was cancelled"}
}
stopI := ib.run()
<-began
stopI()
if h := ib.haves.last("db"); h.Phase != string(delivery.PhaseHeld) || h.FP != fp1 || h.Composed != dig1 || h.Detail != BackupInterrupted {
t.Errorf("held after an interrupted backup: %+v", h)
}
// At a start: a backup the node agent's own stop interrupted (postgres on novox, 2026-10-31) is no failure, so it is
// backed up again and applied while the want asks a backup; a backup that failed stays held for the operator.
for _, row := range []struct {
+1 -5
View File
@@ -618,11 +618,7 @@ func (c *Composer) takeModuleHave(u agent.Update) bool {
// A have that names nothing it runs, failed (the node could not compose a module it never ran: NotComposed), is
// no module run inside the node's own composition: taken as one, it was composed from its manifest past its
// build (issue 1126, review round 1 of the builds).
// Nor is one in any phase but running that names nothing (held by a backup the node agent's restart
// interrupted: postgres on novox, 2026-10-31, refused for its secret reader). Running with nothing named is
// a module of the node's own (mesh-wireguard).
runsNothing := h.FP == "" && h.Composed == "" &&
(h.Phase == delivery.PhaseFailed || h.Build == "" && h.Phase != "" && h.Phase != delivery.PhaseRunning)
runsNothing := h.Phase == delivery.PhaseFailed && h.FP == "" && h.Composed == ""
in = h.Phase != delivery.PhaseRemoved && !runsNothing && !strings.HasPrefix(h.FP, delivery.SourcePrefix2)
}
}
-4
View File
@@ -1760,10 +1760,6 @@ func TestAHaveSaysWhetherItsModuleRunsInsideTheNodesOwn(t *testing.T) {
{"running with no fingerprint (mesh-wireguard)", delivery.Have{Phase: delivery.PhaseRunning, Health: delivery.HealthOK}, true, false},
{"held today", delivery.Have{FP: "build-0123abcd", Composed: "sha256:" + strings.Repeat("c", 64), Phase: delivery.PhaseHeld,
Health: delivery.HealthWaits, Detail: "held: a required config value is unset"}, true, true},
// postgres on novox (2026-10-31): held by a backup the node agent's restart interrupted, its have naming nothing
// it runs; counted in, it was composed from its manifest past its build and refused for its secret reader.
{"held, naming nothing it runs", delivery.Have{Phase: delivery.PhaseHeld, Health: delivery.HealthWaits,
Detail: "the node agent was stopped while the backup ran"}, false, true},
} {
t.Run(row.name, func(t *testing.T) {
c := New(Env{Node: "n1"})