package backup import ( "sync" "time" ) // R-1252 (review round 2, HUNT-2 H1): a rejected backup block never deletes second-drive copies. RunTier2 reconciles // only on a valid classification (tier2CaptureSetFull's `classified`); for a REJECTED block it says so on the app's // status (note.tier2.settings_unreadable_kept) and tells the operator once a day through the existing operator-only // digest (backup_run_failures, leg tier2-settings-unreadable — no new event type, so no hub change). Pinned by // r1252_rejected_block_keeps_copies_test.go. type tier2UnreadableWatch struct { mu sync.Mutex notify func(app, why string) sent map[string]string // app → the UTC day it was last sent now func() time.Time } // SetTier2SettingsUnreadableNotify wires the operator signal (main.go → notifier). INIT-ONLY. func (m *Manager) SetTier2SettingsUnreadableNotify(fn func(app, why string)) { m.tier2Unreadable.mu.Lock() m.tier2Unreadable.notify = fn m.tier2Unreadable.mu.Unlock() } // backupBlockRejected asks the stack provider why the app's backup block was rejected ("" = valid or absent). func (m *Manager) backupBlockRejected(stack string) string { if r, ok := m.stackProvider.(interface{ BackupBlockRejected(string) string }); ok { return r.BackupBlockRejected(stack) } return "" } // notifyTier2SettingsUnreadable sends the operator signal at most once per app per UTC day. func (m *Manager) notifyTier2SettingsUnreadable(app, why string) { w := &m.tier2Unreadable w.mu.Lock() now := time.Now if w.now != nil { now = w.now } day := now().UTC().Format("2006-01-02") if w.notify == nil || w.sent[app] == day { w.mu.Unlock() return } if w.sent == nil { w.sent = map[string]string{} } w.sent[app] = day fn := w.notify w.mu.Unlock() fn(app, why) }