R-100: offsite staleness counts from the last SUCCESS (hub v0.80.0)

isStale counted from last_run, written unconditionally on failure, so a nightly-failing
tier read as fresh forever. Now anchored on last_success with an explicit legacy degrade
(logged once) and the never-ran branch untouched. emitStale states the real reason.
This commit is contained in:
2026-07-28 13:17:04 +02:00
parent 6369570e8d
commit b505ee9125
4 changed files with 405 additions and 16 deletions
@@ -148,6 +148,24 @@ Kept so the old environment can be revived; **not the current setup**.
- `claude-in-chrome` browser automation WAS available there (attaching only to sessions started
after the bridge connected).
### Presence is not success
A timestamp recording an **attempt** must never be read as evidence of a **result**. Where a status
field travels alongside a timestamp, the verdict consults both — or the timestamp records only
successes.
| # | instance | what happened |
|---|---|---|
| 1 | **F-CRIT-2** | a phantom snapshot's ctime set tier freshness — an aborted 1-byte upload made the tier look backed up |
| 2 | **R-100** | `LastRun` is written on failure, so a nightly-failing offsite tier kept the staleness clock fresh forever |
Both were found by asking of a timestamp: *what exactly must have happened for this to be set?* If the
answer is "we tried", it cannot answer "did it work".
Corollary, from R-100's fix: when a verdict changes which field it counts from, **the alarm text has to
change with it**. Leaving the message reading `last run 8h ago` while alarming on a six-day-old success
turns a true alarm into one the operator dismisses.
### A comment asserting an invariant needs a test pinning it, or it is a wish
**Six instances in this project have shipped guarantees the code did not provide** — each survived