# Phase 4 — the proof's missing control and its edges. VERDICT: **PASS** ## The one that mattered — the false-alarm control now EXISTS and PASSES Yesterday's report recorded that no app on `demo-hp` had **neither** a database **nor** a named volume, so R-87's "must not alarm on a legitimately empty app" case had **no live subject**. The whole design rests on that control. **Found by reading all 53 catalogue composes, not by guessing:** exactly **one** template qualifies — **`bentopdf`** (single service `ghcr.io/alam00000/bentopdf:v2.8.6`, no database image, no top-level `volumes:` block, `needs_hdd: false`). *(My first scan reported 0 of 0 apps — the path was wrong; the positive control caught it.)* Deployed, toggled into the off-site set, backed up (`454e6f8b`), and proved: ``` proof: bentopdf PASSED on snapshot 454e6f8b in 2.224s — the backup holds what this app should have offsite_proof_empty events: 0 before, 0 after -> PASS: no alarm was raised ``` Its unit is genuinely empty: `db_dumps: []`, `volume_dumps: None`, compose declares no volumes. **The guard does not fire on a healthy app that legitimately has nothing.** ## The other edges | edge | expected | actual | verdict | |---|---|---|---| | 3. corrupt manifest → cannot judge | cannot judge | **could not be reached through the live path** — the off-site run's own capture phase **rewrote** my corrupted manifest before the push, so the snapshot was healthy and the `pass` was correct. Verified by restoring `5dcee6e3` and reading its manifest | **UNDETERMINED live**; covered by `TestR87_UnparseableManifestFails` (fail-closed, not cannot-judge — a documented divergence from the runbook's expectation, and R-403's rule) | | 4. no newest snapshot → not a failure | not a failure | **observed naturally**: while `bentopdf` was deployed but not yet toggled into the off-site set, the proof rotated all 8 apps and returned `no_snapshot:true`. It never picked bentopdf and never failed | **PASS** | | 5. two failures in an hour → one mail | one mail | **UNDETERMINED** — only two `offsite_proof_empty` were pushed tonight and they are **1 h 42 m apart** (19:21:45, 21:03:00), so they fall in different cooldown windows and do not exercise it. The controller-side half of the contract (no `stack_name`, so the hub keys coarsely) is pinned by `TestR87_NoPerAppCooldownEntryWasAdded` | **UNDETERMINED** | | 6. DB declared, no dump → fail, "intact but empty" | fail | **proven twice tonight** — Phase 2's natural `opengist` case, verbatim: *"READABLE AND EMPTY … the store is not damaged"* | **PASS** | ## A third instance of the same product behaviour, worth naming Three separate attempts to get a damaged unit into the store through the front door were **repaired by the off-site run's own pre-push capture**: a stopped app's volume tar was re-made, a deleted unit was rebuilt, and a corrupted manifest was rewritten. **That is a reassuring property**, and it sharpens R-412: the capture repairs the *manifest and compose*, but it **cannot conjure a volume tar**, because the dump legs run on the backup schedule. That is the one gap through which R-412's hollow unit reached the store. ## And a confirmation of R-402's shape for the new fields The hub's host page mentions `proof` **zero** times. The controller publishes `last_proof_*` and **no hub surface reads them** — exactly what the wire-contract allowlist entries added today say, and why they say "delete this entry when a hub surface reads it".