hub v0.77.0 — R-85 Part 2: a restore-test result becomes a SIGNAL

A failed restore-test was a [WARN] line in the ingest handler and nothing else —
no event, no notification, no gauge. True for the LOCAL tier that was already
being tested, so the loudest DR signal this system produces was inaudible.
Rotating tiers without this would only mean two tiers can fail silently
instead of one.

Two signals, deliberately NOT merged:
  restore_test_failed (error)   — a run completed and did NOT pass
  restore_test_stale (warning)  — a tier not PROVEN within its interval
Merging them collapses 'your DR is broken' into 'your DR is unverified', and
the second is the one that quietly becomes the first. The staleness wording
says 'unverified, not known-broken' and a test asserts that phrasing.

Anchored per R-81, not re-derived: a never-proven tier on a newborn box is
UNKNOWN, not FAILED, until the window elapses. This family has made the
opposite mistake three times; this monitor was written straight after the third,
so it copies R-81's structure rather than inventing a fourth shape.

restoreProvenStaleAfter = 7d is derived: oldest-first over two tiers at a 24h
cadence proves each ~every 2 days, so 7d tolerates ~3 missed opportunities and
sits inside the 2-week offsite retention.

Per-tier proof comes from the hub's retained host-report window — the agent
reports only its latest run, so the latest report alone cannot answer 'when was
the OTHER tier last proven?'. Reused R-81's mechanism instead of a wire change.

Both types registered in allowedEventTypes (R-77's inert-seam lesson) and
operator-tier only — no customerMessages entry.

FIXED a time bomb I introduced in Slice C: the restart-blind-window test
hard-coded 2026-07-18T18:31:06Z while comparing against the real clock. Harmless
under one 26h threshold; once the offsite tier got an 8-day limit it passed all
day and began failing at 18:31 UTC, exactly 8 days later. Now relative.

Red-proofs B and D observed. Full suite green (17 packages, rc=0).
This commit is contained in:
Claude Code
2026-07-26 21:08:02 +02:00
parent b6d93e3fcb
commit ce4e03dcd8
6 changed files with 671 additions and 3 deletions
+7
View File
@@ -1548,6 +1548,13 @@ func (h *Handler) handleClaimResetRequest(w http.ResponseWriter, r *http.Request
// allowedEventTypes lists all valid event_type values the Hub accepts.
var allowedEventTypes = map[string]bool{
// R-85: restore-test signals. Hub-GENERATED (source "hub"), but listed here on purpose —
// allowedEventTypes is the project's single register of legitimate event types, and R-77's
// lesson was that an event type missing from it ships as an inert seam. Neither has a
// customerMessages entry, so both stay operator-tier.
"restore_test_failed": true,
"restore_test_stale": true,
// Controller-pushed events
"controller_started": true,
"claim_lockout": true, // v0.50.0 — claim/reset code brute-force lockout tripped