hub v0.91.0 — the staleness window learns each tier's own rhythm (R-86 Part 2)
gates / gates (push) Successful in 7s
gates / gates (push) Successful in 7s
Ships WITH agent v0.121.0, not after it. The agent now proves a tier once per
ARCHIVE GENERATION, so a weekly tier is proved weekly — in perfect health. The
flat 7-day restoreProvenStaleAfter derived its number from the 24h cadence R-86
removes, and a healthy weekly tier's proof age reaches EXACTLY 168h just before
its next proof: it sat ON the line, so any ordinary delay tipped it into a
nightly alarm about a working system.
restoreProvenWindow(tier, observed, ok):
- the tier's own archive interval, OBSERVED from reports the hub already holds
(pbs_snapshots + successful backups attributed by TARGET TYPE, slice A.4)
- x4 generations = the same tolerance the flat constant expressed
- floored at 7d (never tighter than before), capped at 12d (strictly inside the
2-week offsite retention)
- falls back to the DECLARED rhythm (26h host / 8d offsite — the thresholds the
backup-freshness checker already uses) when history is too short to observe
one; falling back to the FLOOR would recreate the false alarm on a fresh box
Kept: absence is UNKNOWN until the anchored window passes; the signal stays
edge-triggered; failed and stale remain distinct events. Every reason string now
states the window it was judged against (R-100's corollary).
Also backfills the missing v0.90.1 CHANGELOG entry (deployed since f21e7ca), and
records the operator's 2026-08-03 ruling that ep0 is Tier 2 / protected.
This commit is contained in:
@@ -1,3 +1,71 @@
|
||||
## v0.91.0 — a tier's staleness window learns the tier's own rhythm (2026-08-03, R-86 Part 2)
|
||||
|
||||
**This ships WITH the agent's v0.121.0, not after it.** The agent now proves a tier once per ARCHIVE
|
||||
GENERATION rather than on a 24h timer, so a tier backed up weekly is proved weekly — correctly, and
|
||||
in perfect health. `restoreProvenStaleAfter` was a flat 7 days, and its own comment derived that
|
||||
number from the cadence R-86 removes:
|
||||
|
||||
> *"the restore-test cadence is 24h and rotation is oldest-first across two tiers, so each tier is
|
||||
> proven roughly every 2 days. 7 days therefore tolerates ~3 consecutive missed opportunities."*
|
||||
|
||||
A weekly tier proved weekly reaches a proof age of **exactly one interval** just before its next
|
||||
proof — 168h against a 168h window, false by a hair — so it did not merely sit near that line, it sat
|
||||
ON it, and any ordinary delay (a late backup, one deferral behind a running backup) tipped it into a
|
||||
nightly alarm about a system that is working. Shipping the agent alone would have converted the
|
||||
improvement into a false alarm.
|
||||
|
||||
**The window is now per tier**, `restoreProvenWindow(tier, observed, ok)`:
|
||||
|
||||
- **the tier's own archive interval**, observed from the host-reports the hub already holds —
|
||||
`pbs_snapshots[]` plus successful `backups[]` attributed by TARGET TYPE (the slice-A.4 rule: a
|
||||
PBS-targeted vzdump appears in both arrays, and classifying by array membership would attribute an
|
||||
offsite archive to the host tier);
|
||||
- **× 4 generations** — the settle generation plus ~3 missed opportunities, deliberately the same
|
||||
tolerance the flat constant expressed. The change is to the RHYTHM, not to the patience;
|
||||
- **floored at 7 days** — the old constant kept as a floor, so no tier is judged more tightly than
|
||||
before;
|
||||
- **capped at 12 days** — strictly inside the 2-week offsite retention with two days to spare, so a
|
||||
tier is never called stale against an archive PBS has already pruned;
|
||||
- **falling back to the DECLARED rhythm** when a box's history is too short to observe one:
|
||||
`backupStaleAfter` (26h) for the host tier and `offsiteBackupStaleAfter` (8d) for the offsite tier
|
||||
— the very thresholds the backup-freshness checker already judges those tiers against. A fresh box
|
||||
with one snapshot has no observable interval, and falling back to the FLOOR there would recreate
|
||||
the false alarm on exactly the tier this task is about.
|
||||
|
||||
**Kept, because both were earned:** absence is UNKNOWN until an anchored window has passed (R-81's
|
||||
structure, untouched), and the stale signal stays edge-triggered. `restore_test_failed` and
|
||||
`restore_test_stale` remain DISTINCT — one says your recovery is broken, the other says it is
|
||||
unverified, and the second is the one that quietly becomes the first.
|
||||
|
||||
**Every reason string now states the window it was judged against** — R-100's corollary: when a
|
||||
verdict changes what it counts from, the alarm text has to change with it, or an operator reads
|
||||
"limit 168h" under a tier actually judged at 288h and dismisses a true alarm.
|
||||
|
||||
**The window READ is unchanged in cost** (14 days), which is both enough to find proof inside the
|
||||
widest window and enough to see two generations of a weekly tier.
|
||||
|
||||
## v0.90.1 — the digest's per-app lines stop repeating the filesystem figures (2026-08-03, R-182)
|
||||
|
||||
**Backfilled 2026-08-03 (R-86 session).** This version was built, deployed and recorded in `REPORT.md`
|
||||
and the R-182 row, but never given a CHANGELOG entry — and `REPORT.md` is overwritten every session,
|
||||
so the per-repo history under-reported what was actually running. The deployed image has been
|
||||
`felhom-hub:0.90.1` since `f21e7ca`.
|
||||
|
||||
Found by reading the first REAL digest, not by design: every app row ended with the same usage clause
|
||||
the mail already prints once on its own Filesystem line. On a two-app box that is untidy; down a list
|
||||
of a dozen it is the same forty characters twelve times, pushing the part that DIFFERS off a phone
|
||||
screen at 07:00 — the only moment that mail has to work.
|
||||
|
||||
The reserve's refusal message is authored for a single-app alert where naming the filesystem is
|
||||
right, so the message is unchanged and the DIGEST trims the duplicate when rendering.
|
||||
`trimRepeatedUsage` removes ONLY an exact "— <target path>:" suffix, so an unrelated reason is
|
||||
untouched and a reason that is nothing but the usage clause is left alone rather than emptied.
|
||||
|
||||
Also inverted the operator half of `TestRecoveryUnitCaptureFailed_NeverReachesTheCustomer`: it
|
||||
required the operator to be e-mailed a per-app capture failure, which was correct when that event was
|
||||
the only signal and is wrong now that it is the RECORD and the digest is the notification. The
|
||||
customer-safety claim is unchanged — R-158's guarantee MOVED, it did not weaken.
|
||||
|
||||
## v0.90.0 — a dropped notification leaves a trace, and the backup digest arrives (2026-08-03, R-182)
|
||||
|
||||
**The smallest change on the board with the largest effect on trust: `processOperator`'s cooldown no
|
||||
|
||||
Reference in New Issue
Block a user