R-195: a customer with no machine ever bound does not alarm (hub v0.92.0) + R-193/R-192 spike
gates / gates (push) Successful in 7s
gates / gates (push) Successful in 7s
Part 4 (ships): `david` — a prospective customer with hosts=0, host_deletions=0, reports=0 — e-mailed an expected_dbdump_missed ERROR at 03:00 UTC three mornings running. The existing down-skip could never cover it: it reads the staleness checker's state, which is seeded from a query over the `reports` table, so a customer that never reported has no state at all and GetState() returns "" rather than "down". store.HasEverBoundHost (hosts row OR host_deletions tombstone) is consulted once per customer at the top of the deadline loop. The discriminator is "was a host EVER bound", never "has a report arrived" — a box installed and never heard from is a real fault and keeps alarming. Fail-OPEN on a read error. Red-proof observed: removing the guard fails with `got [expected_dbdump_missed]`, verbatim the event david sent. Parts 0-3 (spike, NO production code for R-193/R-192): audits/SPIKE-offsite-credential-recovery-2026-08-04.md establishes that the one-shot provider password is the RECOVERABLE secret and the restic repository password is the irreplaceable one — and that a guest rebuild mints a fresh one, orphaning the previous off-site history. Measured without touching a box, by comparing host_escrow.restic_pw_sha256 against host_escrow_superseded: BOTH demo boxes changed (demo-hp 15 snapshots / 40.9 MB, demo-felhom 36 snapshots / 1.14 GB). demo-felhom's "lucky" 76-second recovery restored delivery and not the repository, silently, for 13h. ReissueCredentials does NOT rotate the restic password (R-39's record and two hub comments are wrong -> R-196); candidate (b) is not implementable against a zero-knowledge escrow; candidate (a) already exists as F3 and is wired to the wrong event. Ends in ranked options and an unanswered question for the operator. R-195 SHIPPED; R-196 + R-197 filed; R-192 + R-193 updated, neither closed.
This commit is contained in:
@@ -1,3 +1,48 @@
|
||||
## v0.92.0 — a customer with no machine ever bound does not alarm (2026-08-04, R-195)
|
||||
|
||||
`david` is a real prospective customer whose record was created 2026-08-01 with **no host ever bound**
|
||||
— `hosts=0`, `host_deletions=0`, `host_reports=0`, `reports=0` — and it e-mailed an
|
||||
`expected_dbdump_missed` **error** at 03:00 UTC on 2026-08-02, 08-03 and 08-04. Nothing has ever been
|
||||
expected of that customer, so the honest verdict is UNKNOWN, not missed.
|
||||
|
||||
**The mechanism is the interesting half, because the guard that should have covered this is keyed off
|
||||
the wrong thing.** `CheckBackupDeadlines` already skips nodes that are `down`, and that skip reads
|
||||
`StalenessChecker.GetState()`, whose map is seeded from `store.GetCustomers()` — **a query over the
|
||||
`reports` table**. A customer with zero reports is in no row, gets no state at all, and `GetState()`
|
||||
returns `""` rather than `"down"`. **The skip misses exactly the customer it would most obviously
|
||||
cover.** Corroborated on live data: `peti-felhom` is active with a host deleted 2026-07-15 and does
|
||||
*not* alarm, because it has 482 old reports and is therefore `down`. The backup half was already safe
|
||||
(`reportJSON == ""` → skip); the DB-dump half had no guard at all.
|
||||
|
||||
- **`store.HasEverBoundHost(customerID)`** — a live `hosts` row **OR** a `host_deletions` tombstone.
|
||||
Consulted once per customer at the top of the deadline loop; a false verdict skips the customer with
|
||||
an INFO line and its own counter in the summary.
|
||||
- **The discriminator is "was a host EVER bound", NOT "has a report arrived", and that is the whole
|
||||
design.** A box that was installed, bound and then went silent is a real fault and must keep
|
||||
alarming — it has a `hosts` row, so it is judged. Only a customer that never had a machine is
|
||||
UNKNOWN. The tombstone is included for the same reason: a customer whose host was deleted *had* one,
|
||||
and that judgement belongs to the staleness checker, not to this predicate.
|
||||
- **Fail-OPEN on a read error.** An unreadable binding must never SUPPRESS a real alarm; it logs and
|
||||
judges anyway.
|
||||
- **The anchored-verdict structure is untouched** (R-81/v0.73.0). This is the same invariant — absence
|
||||
is UNKNOWN until an anchor elapses — applied one level up, at the question of whether there is a
|
||||
subject at all.
|
||||
|
||||
**Tests, including the one that matters.** `TestCheckBackupDeadlines_BoundButNeverReported_StillAlarms`
|
||||
is a real shape — a machine installed and never phoned home — and it is indistinguishable from `david`
|
||||
on every signal except the one the guard reads. If the guard is ever "simplified" to key off report
|
||||
presence, customer age or a name pattern, it goes red. Plus a bound-then-went-quiet case, a
|
||||
deleted-host case, and the predicate's own three inputs. **Companion red-proof observed:** removing the
|
||||
guard fails `TestCheckBackupDeadlines_NeverBoundHost_Silent` with `got [expected_dbdump_missed]` —
|
||||
verbatim the event `david` sent three mornings running. Restored after.
|
||||
|
||||
**`david`'s record was not modified.** The record is correct; the alarm was what was wrong.
|
||||
|
||||
**Shipped alongside a spike that deliberately shipped NO code** —
|
||||
`documentation/audits/SPIKE-offsite-credential-recovery-2026-08-04.md` (R-193/R-192): what is one-shot
|
||||
in the offsite path, and what a guest rebuild really costs. It ends in ranked options and a question
|
||||
for the operator; R-196 and R-197 were minted from it and are not implemented here.
|
||||
|
||||
## v0.91.1 — observation may only WIDEN a tier's window, never tighten it (2026-08-03, R-86 Part 2)
|
||||
|
||||
**Found by checking v0.91.0 against the live box before trusting it, not by review.** demo-felhom's
|
||||
|
||||
Reference in New Issue
Block a user