hub v0.75.0: R-81 — "no signal" is not "bad signal" (anchor the backup deadline check)
Third instance of one class (hub v0.12.0, v0.73.0, this), fixed as a class. On 2026-07-26 03:00 UTC expected_backup_missed fired on demo-felhom, demo-hp and drill-r50 at once; the demo-felhom one reached the CUSTOMER channel claiming "newest backup is 176h0m0s old". Nothing was wrong — three vzdump archives were on disk. Cause: the agent backup store is in-memory, so the R-50 fleet restart emptied `backups` until the next run, and the hub read empty as "no backup exists". - assessBackupFreshness returns OK/UNKNOWN/MISSED instead of `missed bool`; absence is UNKNOWN until it outlives an anchored window. Still pure. - store.GetHostReportsSince + monitor.newestBackupEvidence read the hubs own retained history (bounded 7-day lookback, early-exit on fresh evidence) — "when did I last SEE evidence of a backup?" The anchor was free: the hub already retains 90 days. No agent change, no new persisted state. - store.GetFirstHostReportAt anchors absence at first contact, reusing the existing 26h threshold as the grace (no new knob, the v0.73.0 shape). - Deferrals logged + counted; reason strings kept distinct. - backupStaleAfter untouched; landmine recorded (a weekly PBS snapshot would alarm six days in seven) and owned by R-82. Tests 493->508. Red-proofs A/B/C observed and restored; A reproduces the live message verbatim. Replayed the real 03:00 reports (600/417/77 rows): all three now silent. Source: documentation/audits/DIAG-backup-missed-2026-07-26.md
This commit is contained in:
@@ -171,6 +171,12 @@ func TestCheckBackupDeadlines_DbDumpHalfPreserved(t *testing.T) {
|
||||
}
|
||||
|
||||
// TestAssessBackupFreshness exercises the pure freshness policy directly.
|
||||
//
|
||||
// R-81: every case here passes a ZERO backupEvidence — no hub-history evidence and no
|
||||
// first-contact anchor. That is deliberate: it pins the latest-report-only behaviour
|
||||
// unchanged, and the "no snapshots and no backups" row exercises the UNANCHORED absence
|
||||
// branch (zero anchor → fail toward visibility, the v0.73.0 legacy-shape precedent).
|
||||
// The anchored branches have their own named tests below.
|
||||
func TestAssessBackupFreshness(t *testing.T) {
|
||||
now := time.Date(2026, 6, 16, 3, 0, 0, 0, time.UTC)
|
||||
at := func(d time.Duration) string { return now.Add(d).Format(time.RFC3339) }
|
||||
@@ -192,9 +198,9 @@ func TestAssessBackupFreshness(t *testing.T) {
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
got := assessBackupFreshness(c.report, now)
|
||||
if got.missed != c.wantMissed {
|
||||
t.Fatalf("missed=%v want=%v (reason=%q)", got.missed, c.wantMissed, got.reason)
|
||||
got := assessBackupFreshness(c.report, backupEvidence{}, now)
|
||||
if got.missed() != c.wantMissed {
|
||||
t.Fatalf("missed=%v want=%v (reason=%q)", got.missed(), c.wantMissed, got.reason)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user