R-1252 (P2): a rejected or absent backup block never deletes second-drive copies — reconcile only on a valid classification; a rejected block is said on the app's status and once a day to the operator (backup_run_failures); kept copies stay kept (H2); Remove drops the kept records it deleted (H9). Commit only.
gates / gates (push) Successful in 1m18s

This commit is contained in:
2026-10-24 20:07:16 +02:00
parent 062f9b41d9
commit 518a13592d
16 changed files with 400 additions and 6 deletions
@@ -509,3 +509,14 @@ func TestR1251_MainWiresThePutBackWait(t *testing.T) {
t.Errorf("func main() must resume the waiting put-backs after Recover (at %d, Recover at %d)", i, rec)
}
}
// R-1252: the second-drive copy asks the stack adapter (by an optional interface) whether a block was REJECTED. An
// adapter that lost the method would make every rejected block read as absent — quiet, with no alarm. Pinned at
// compile time here; the decision is tested in internal/backup.
var _ interface{ BackupBlockRejected(string) string } = (*stackAdapter)(nil)
func TestR1252_MainWiresTheUnreadableSettingsSignal(t *testing.T) {
if indexOfCall(callsInMain(t, mainBody(t)), "SetTier2SettingsUnreadableNotify") < 0 {
t.Fatal("func main() no longer wires SetTier2SettingsUnreadableNotify — a rejected block would keep the copies silently")
}
}
+16
View File
@@ -1437,6 +1437,17 @@ func main() {
"the app is not protected on that tier. Check for a hold on a running app (R-704's shape) or a skipped dump.",
app, tier), d)
})
// R-1252: an app's backup block is REJECTED — the second-drive copy kept every copy that night instead of
// reconciling against the legacy legs (which deleted the household's file copies on 9202). Same operator-only
// digest, its own leg; the backup package sends it once per app per day.
backupMgr.SetTier2SettingsUnreadableNotify(func(app, why string) {
d := notify.BackupRunFailuresDetails{RunKind: "tier2", Attempted: 1, Failed: 1,
Apps: []notify.RunFailureDetail{{App: app, Leg: "tier2-settings-unreadable", Reason: why}}}
notifier.NotifyBackupRunFailures(fmt.Sprintf(
"Second-drive copy KEPT, settings unreadable: the backup block of %s is rejected (%s). The night copy deleted nothing "+
"and every existing copy stays, but new folders are not copied until the block is fixed (R-1252). Fix the template.",
app, why), d)
})
backupMgr.SetOffboxGapNotify(func(gaps map[string][]string) {
apps := make([]string, 0, len(gaps))
for app := range gaps {
@@ -3093,6 +3104,11 @@ func (a *stackAdapter) GetStackClassifiedBinds(name string) ([]backup.Classified
return a.mgr.ClassifiedBinds(name)
}
// BackupBlockRejected (R-1252) lets the second-drive copy tell a rejected backup block from an absent one.
func (a *stackAdapter) BackupBlockRejected(name string) string {
return a.mgr.BackupBlockRejected(name)
}
func (a *stackAdapter) ListDeployedStacks() []backup.StackSummary {
var result []backup.StackSummary
for _, s := range a.mgr.GetStacks() {