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
+16 -2
View File
@@ -367,7 +367,7 @@ func (m *Manager) RunTier2(stackName string) error {
}
// Class-driven legs (classified) / resolver legs (legacy) + loud gap warnings (§7-H).
legs, missingLegs, warns := m.tier2CaptureSetFull(stackName, sourceNsRoot)
legs, missingLegs, warns, classified := m.tier2CaptureSetFull(stackName, sourceNsRoot)
// Two sizes: full = unit + all legs; state-only = unit + mandatory legs (the SSD ceiling).
unitSize := dirSizeBytes(unitDir)
@@ -488,8 +488,22 @@ func (m *Manager) RunTier2(stackName string) error {
filepath.Join(destBase, filepath.FromSlash(lg.DestRel)))
}
// HUNT-2 H2 (R-1252): a copy D25 keeps stays kept even when its leg leaves the declared set — its record is the
// household's only handle on it, and only their „Eltávolítás" removes it.
legRels = append(legRels, m.keptCopyRels(destBase)...)
// Reconcile stale dest dirs (a bind removed / re-classed excluded), then write the marker LAST.
m.tier2Reconcile(destBase, legRels)
// R-1252: ONLY on a valid classification. A rejected or absent block yields the legacy legs, and reconciling
// against them deleted every copy of the household's files (9202, 2026-10-24). Nothing is reconciled that night;
// a REJECTED block is said on the app's status and to the operator.
if classified {
m.tier2Reconcile(destBase, legRels)
} else if why := m.backupBlockRejected(stackName); why != "" {
m.logger.Printf("[WARN] [backup] Tier 2 %s: the backup block is REJECTED (%s) — nothing reconciled, every existing copy on the second drive is KEPT (R-1252)", stackName, why)
warns = append(warns, m.note("note.tier2.settings_unreadable_kept"))
m.notifyTier2SettingsUnreadable(stackName, why)
} else if m.isDebug() {
m.logger.Printf("[DEBUG] [backup] Tier 2 %s: no backup classification — nothing reconciled (R-1252)", stackName)
}
if err := os.WriteFile(markerPath, []byte(tier2LayoutVersion), 0644); err != nil {
m.logger.Printf("[WARN] [backup] Tier 2 %s: layout marker write failed (restore will refuse until next run): %v", stackName, err)
}