Judge a PC's backup by its stalest kind, not its newest

This commit is contained in:
2026-08-25 13:56:53 -04:00
parent f1e265190e
commit fa856c1f94
2 changed files with 120 additions and 1 deletions

View File

@@ -1079,10 +1079,52 @@ def _attach_backup_state(facts, hostnames):
rows.sort(key=lambda revision: (revision.lastseenat is not None,
revision.lastseenat or _EPOCH),
reverse=True)
# Reduce to the FRESHEST revision per (host, kind), then judge a host by its
# STALEST kind.
#
# Until part markers, a PC reported exactly one kind and "newest wins" was
# the same thing as "the truth". A marker bay reports two - ntlars for the
# machine's DNC settings and partmarker for the controller's patterns - and
# newest-wins then means the daily ntlars post keeps the badge green while
# the marker backup has been failing for a month. The failure would show
# only on that marker's own asset page, which is not where anyone looks to
# find out that something stopped.
#
# So: a host is healthy when EVERYTHING it backs up is current, and the
# badge names the kind that is not. Freshest-per-kind first, because
# lastseenat only moves on the newest revision of a chain - an older
# revision of the same kind keeps its original value and would otherwise
# read as stale for ever.
#
# KNOWN CONSEQUENCE: a device retired from a PC leaves its last revision
# behind, so that host stays stale until the revision is pruned or removed.
# That is the honest failure direction - it complains about a backup that
# genuinely is not running any more, rather than staying quiet about one.
freshest = {}
for revision in rows:
key = (revision.sourcehostname or '').lower()
if key not in facts:
continue
kindkey = (key, revision.backupkind)
if kindkey not in freshest:
# rows are already newest-confirmed-first, so the first is freshest.
freshest[kindkey] = revision
stalest = {}
for (key, _kind), revision in freshest.items():
current = stalest.get(key)
if current is None:
stalest[key] = revision
continue
# None (never confirmed) is worse than any date.
if revision.lastseenat is None:
stalest[key] = revision
elif current.lastseenat is not None and revision.lastseenat < current.lastseenat:
stalest[key] = revision
for key, revision in stalest.items():
entry = facts.get(key)
# First row per host wins.
if entry and entry['backuplastseen'] is None:
entry['backupkind'] = revision.backupkind
entry['backuplastseen'] = (