v0.238.1: the nightly backup leaves an app alone WHILE it is being updated, not only once it is held (slice 4 follow-up)
gates / gates (push) Successful in 13s

Found live in v0.238.0 Scenario F on demo-hp: during an update's 5-minute health wait the app is not
yet held, and the periodic recovery-unit capture at 10:17:09 wrote the never-started definition
(alpine:3.20) into its PRIMARY unit, 53 s before the hold landed. The Tier-2 mirror the hold names
survived only because Tier 2 runs daily; a nightly Tier 2 inside a verify window would have mirrored
the broken definition over the copy the customer is told to restore from.

backup.Manager.isHeld — consulted by the capture sweep, the Tier-2 run and the volume dump — is now
also true while a guarded update is moving the app, via SetUpdatingCheck wired in main.go to
stacks.Manager.IsUpdating. Test with positive control + red-proof; wiring pinned.

Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
2026-09-13 12:25:09 +02:00
parent 129201abab
commit cbcca03061
8 changed files with 93 additions and 4 deletions
+5
View File
@@ -554,6 +554,11 @@ func main() {
// An UNWIRED manager also refuses (fail closed); TestSlice4_UpdateGuardsAreWiredAtStartup walks
// this file for the call, because a seam built and never wired has shipped here seven times.
stackMgr.SetUpdateGuards(&updateGuardsAdapter{b: backupMgr, q: quiesceLoop})
// v0.238.1: the nightly legs (capture, Tier 2, volume dump) leave an app alone WHILE it is being
// updated, not only once it is held — found live in Scenario F, see backup.Manager.isHeld.
if backupMgr != nil {
backupMgr.SetUpdatingCheck(stackMgr.IsUpdating)
}
if n := stackMgr.ResumeInterruptedUpdates(ctx); n > 0 {
logger.Printf("[WARN] [update] resumed %d interrupted update(s)", n)
}
@@ -102,3 +102,12 @@ func TestSlice4_DriveStartGate_NamesAnUpdateHold(t *testing.T) {
t.Errorf("ok=%v why=%q", ok, why)
}
}
// v0.238.1: the backup manager must be told which apps are mid-update, or the nightly legs overwrite a
// restore point during an update's health wait (found live, Scenario F).
func TestSlice4_UpdatingCheckIsWiredAtStartup(t *testing.T) {
lines, _, _ := slice4CallLines(t)
if len(lines["SetUpdatingCheck"]) == 0 {
t.Fatal("backupMgr.SetUpdatingCheck is never called — the nightly legs cannot see an update in progress")
}
}