hub v0.76.0 — R-82 Slice C: tier-aware backup thresholds
R-81 merged every backup signal into one 'newest' against a single 26h limit. backupStaleAfter's own comment recorded why that stops being right under a weekly offsite tier. Each tier is now judged against its own threshold; R-81's structure (three verdicts, anchored absence, distinct reasons) and its boundary test are preserved intact. - offsiteBackupStaleAfter = 8d (7d cadence + headroom); backupStaleAfter keeps 26h and now names the HOST tier only - splitTiers / assessTier / newestBackupEvidenceByTier Slice-A.4 rule implemented: a PBS-targeted vzdump appears in BOTH arrays, so classification is by TARGET TYPE (target_id -> storage_targets[].name -> type), never by array membership — otherwise a PBS backup makes a stale host tier look fresh. storage_targets is used rather than pbs_dr.storage_id because the latter is null on a box with a PBS storage but no DR descriptor. A tier is only judged when the box HAS it, else every box without an offsite tier would alarm once the anchor elapsed — R-81's mistake one level down. With neither tier identifiable (old agent) the pre-Slice-C path runs unchanged. Intended behaviour change: a 30h offsite snapshot no longer alarms. Three fixtures asserted the merged threshold; each still asserts an alarm at the correct limit. No assertion was weakened. RECORDED LIMITATION: the hub infers 'PBS => weekly' from storage type. defaultBackupTarget is felhom-pbs, so a box that never sets local_backup_target would run PBS as its DAILY tier and be judged against 8 days — 7 days of blindness. No box is in that shape today; the real fix is the agent reporting per-tier cadences. Own task. Red-proof observed. Replayed live: demo-felhom OK, demo-hp UNKNOWN (defers correctly), drill-r50 MISSED (true positive). No customer email would be sent.
This commit is contained in:
@@ -1,5 +1,73 @@
|
||||
# Felhom Hub — Changelog
|
||||
|
||||
## v0.76.0 — R-82 Slice C: tier-aware backup thresholds (2026-07-26)
|
||||
|
||||
R-81 merged every backup signal into one "newest" and judged it against a single 26 h limit. That
|
||||
was right while a box had exactly one whole-guest tier. `backupStaleAfter`'s own comment recorded
|
||||
why it stops being right:
|
||||
|
||||
> *"The moment PBS moves to a WEEKLY cadence, a perfectly healthy weekly snapshot is >26h old six
|
||||
> days in seven and this constant alarms on it."*
|
||||
|
||||
Each tier is now judged against **its own** threshold. **R-81's structure is preserved intact** —
|
||||
three-valued verdicts, absence anchored at first contact, one distinct reason per failure mode — and
|
||||
its boundary test is untouched.
|
||||
|
||||
### Added
|
||||
- **`offsiteBackupStaleAfter = 8d`** (7-day cadence + a day of headroom — the same ratio 26 h gives
|
||||
a 24 h cadence). `backupStaleAfter` keeps 26 h and now names the **host** tier only.
|
||||
- **`splitTiers`** — classifies a report's evidence into host and offsite tiers.
|
||||
- **`assessTier`** — R-81's exact logic, parameterised by tier and threshold.
|
||||
- **`newestBackupEvidenceByTier`** — the retained-window scan, per tier.
|
||||
|
||||
### The Slice-A.4 rule, implemented
|
||||
A PBS-targeted vzdump appears in **both** `backups[]` (as a `Backup` with `target_id:"felhom-pbs"`)
|
||||
and `pbs_snapshots[]` (enumerated independently from PBS). Classification is therefore by **TARGET
|
||||
TYPE** — `target_id` → `storage_targets[].name` → `.type == "pbs"` — **never by array membership**.
|
||||
Getting that wrong would let a PBS backup make a **stale host tier look fresh**, silently losing the
|
||||
daily tier's alarm. Pinned by `TestTierAware_PBSTargetedVzdumpIsNotHostEvidence`.
|
||||
`storage_targets` is used rather than `pbs_dr.storage_id` because the latter is null on a box that
|
||||
has a PBS storage but no DR descriptor yet (drill-r50 was exactly that shape).
|
||||
|
||||
### A tier is only judged when the box HAS it
|
||||
`expected` gates each tier on a configured storage of that kind, or evidence for it. Without that,
|
||||
every box without an offsite tier would alarm as soon as the anchor elapsed — the
|
||||
absence-is-not-failure mistake R-81 exists to prevent, re-introduced one level down. When NEITHER
|
||||
tier is identifiable (an old agent reporting no `storage_targets` and no `target_id`) the pre-Slice-C
|
||||
combined path runs unchanged, so nothing regresses on a fleet mid-upgrade.
|
||||
|
||||
### Changed behaviour (intended)
|
||||
A 30 h-old offsite snapshot no longer alarms — under a weekly tier it is healthy. Three existing
|
||||
fixtures asserted the old merged threshold; each still asserts an alarm, now at the correct limit
|
||||
(9 days for offsite, 30 h for host). **No assertion was weakened to make the code pass.**
|
||||
|
||||
### ⚠️ Recorded limitation — the hub infers cadence from storage TYPE
|
||||
"PBS ⇒ weekly" is an inference, not a fact the box tells us. `defaultBackupTarget` is `"felhom-pbs"`,
|
||||
so a box that never sets `local_backup_target` would run PBS as its **daily primary** tier and the
|
||||
hub would judge it against 8 days — **seven days of blindness**. No box is in that shape today (both
|
||||
demo boxes set `local`, and the installer pins it), but it is a latent mis-classification of exactly
|
||||
the kind that became R-80. The real fix is the agent reporting each tier's **actual cadence** in the
|
||||
host-report; own task.
|
||||
|
||||
### Tests
|
||||
Full suite green (17 packages). Red-proof observed: giving `tierOffsite` the host threshold — i.e.
|
||||
restoring the merged limit — fails the 6-day case with
|
||||
`offsite tier: newest backup is 144h0m0s old (limit 26h0m0s)`, verbatim the cry-wolf this slice
|
||||
removes. Restored.
|
||||
|
||||
**Replayed against the live hub DB** through the real store queries:
|
||||
|
||||
```
|
||||
demo-felhom host=07-26T14:38Z offsite=07-26T12:21Z -> OK
|
||||
demo-hp host=07-26T07:06Z offsite=none -> UNKNOWN (offsite watched 119h of a 192h grace)
|
||||
drill-r50 host=none offsite=not expected -> MISSED (host tier, no evidence in 29h)
|
||||
```
|
||||
|
||||
**No customer email would be sent by this deploy.** demo-felhom is clean; demo-hp defers correctly
|
||||
and will alarm in ~3 days if its offsite tier stays empty (the true R-82 finding, arriving on
|
||||
schedule); drill-r50's alarm is a true positive and it has no customer channel.
|
||||
|
||||
|
||||
## v0.75.0 — R-81: "no signal" is not "bad signal" — the backup deadline check is ANCHORED (2026-07-26)
|
||||
|
||||
The third instance of one bug class, fixed as a class. `expected_backup_missed` fired on
|
||||
|
||||
Reference in New Issue
Block a user