CAMPAIGN-11: R-228 (set-aside history invisible) + campaign doc F7/F9/F10/F11 sections

The move-aside is correct and verified byte-for-byte; what follows it is not.
settings.json records orphaned_renamed_to and OrphanedRenamedTo has zero
references in any template or handler — 12.5 MB of deliberately retained
customer data at a path the box knows and never shows. Fifth instance of the
project's seam-built-but-never-wired pattern.

Campaign document gains the F7/F9/F10/F11 write-ups: F7 split (set-aside PASS,
afterwards FAIL), F9 partial (R-215's gate proven live on a narrower shape),
F10 NOT INJECTED (harness — three self-healing attempts), F11 PASS.

No product code changed.
This commit is contained in:
2026-08-05 23:59:09 +02:00
parent f88faad50b
commit ce6fafa14f
2 changed files with 89 additions and 9 deletions
@@ -4,8 +4,8 @@
supersession) PASSED on its central question. Phase 2 (eleven injected faults) and Phase 4 (an
unattended soak) ran overnight on 2026-08-05/06 and are reported here for the first time.**
**Findings: R-214 … R-223 from Phases 1 and 3** (seven since fixed and shipped), **plus R-224 … R-227
from Phase 2.** **Three suspicions investigated — two confirmed, one DISPROVED.** **Four harness
**Findings: R-214 … R-223 from Phases 1 and 3** (seven since fixed and shipped), **plus R-224 … R-228
from Phase 2.** **Three suspicions investigated — two confirmed, one DISPROVED.** **Five harness
faults, separated from the product's.**
Evidence: `../tests/campaign11-evidence-2026-08-05/``journal.md` (Phases 0, 1, 3) and
@@ -155,13 +155,66 @@ was real; **every control that failed is reported as a failed control, not as a
| **F4** | agent stopped | **M2**, the R-216 fix under pressure | **FAIL** — M4. → **R-224** |
| **F5** | store unreachable after a successful unlock | unlock counts; „could not be read", not „opened with content" | **PASS** — R-217's fix holds |
| **F6** | „Most nem", return later | entry point survives; unlock still works | **PASS** |
| **F7** | set aside, then a change of mind | set aside, never deleted; honest afterwards | *(see §4.7)* |
| **F7** | set aside, then a change of mind | set aside, never deleted; honest afterwards | **SPLIT** — set-aside **PASS**, verified byte-for-byte; the afterwards **FAIL****R-228** |
| **F8** | controller restarted mid-unlock | no half-state; the screen says which | **PARTIAL** — no half-state ✅, but a raw English `Bad Gateway`**R-227** |
| **F9** | a box that never had off-site | nothing, incl. a direct `GET /recovery` | *(see §4.9)* |
| **F10** | app with a mandatory data path missing | run reports `incomplete`, names the app, digest carries it | *(see §4.10)* |
| **F11** | box offline a whole reporting window | hub and box agree once it returns | *(see §4.11)* |
| **F9** | a box that never had off-site | nothing, incl. a direct `GET /recovery` | **PARTIAL PASS** — R-215's gate proven live on a narrower shape; the literal precondition was not staged |
| **F10** | app with a mandatory data path missing | run reports `incomplete`, names the app, digest carries it | **NOT INJECTED** — harness. Three attempts, each self-healed |
| **F11** | box offline a whole reporting window | hub and box agree once it returns | **PASS** — both directions fired, each with an operator mail |
*(F7, F9, F10 and F11 are written up in the sections below; the table is the index.)*
### F7 — the set-aside, and the afterwards
**The move-aside is exactly what it claims.** Both confirmations state the consequences first
(*„félretesszük — nem töröljük"*, *„a gép új, üres mentési tárolót kezd"*, *„ez az oldal többé nem
jelenik meg"*), and the result measured against the Storage Box:
```
BEFORE /home/felhom-repo mtime 13:13 snapshot f3d9cd67 du -s 12535
AFTER /home/felhom-repo.orphaned-20260805 mtime 13:13 snapshot f3d9cd67 du -s 12535 ← untouched
/home/felhom-repo mtime 21:32 (empty) ← fresh
```
**Nothing deleted.** Then the customer changes their mind — and finds nothing: `GET /recovery` 302s,
`POST /recovery/unlock` 302s in 0.028 s with no message, and `/backups/remote` never mentions the
set-aside history at all, though the box records its exact path. → **R-228**
### F9 — what was and was not proven
The literal precondition (a box that never had off-site backups) needs a rebuild, which the brief
forbids before Phase 4, so it was **not staged**. But **the assertion that failed in Phase 1 was
tested and passes**: Phase 1's defect was that `GET /recovery` never asked the predicate. After F7
drives `recoveryOffer()` false, `GET /recovery`**302 `/backups/remote`**. **R-215's fix is proven
live.** The `GetHubEscrowIdentityPresent()==false` arm remains covered by tests only.
### F10 — not injected, and why that is a harness result
Three attempts, each with a control confirming the directory was genuinely absent — and each
self-healed before capture: (1) the app recreated it; (2) `docker stop` was undone by **the
controller's own monitor restarting the stack**; (3) with the stack stopped through the controller
API, the directory reappeared at **21:51:38.65**, coincident with the run's own start. All three runs
reported `ok` with `1 mandatory path(s)`, and R-203's stat-gap correctly never fired, because by the
time `os.Stat` ran the path existed.
**The state F10 describes is not reachable on a deployed app of this kind.** Recorded as **harness,
not product**.
**One observation kept, with its evidence:** at capture the directory held only a recreated
`metadata.db` and **not** the customer's `F10-SENTINEL.txt`, and the run still said `ok`. The verdict
is about a path's *presence*, not its *content* — correct as designed, and it means an `ok` off-site
run can immediately follow the loss of everything that path contained.
### F11 — the dead-man's switch, both ways
```
23:28:11 ok → stale (host_stale) + Operator email SENT
23:29:59 Received report from c11 ← the box returns unaided
23:30:11 stale → ok (host_recovered) + Operator email SENT
23:30:11 customer mail skipped — no unanswered customer down mail (pairing miss)
```
The customer mail was correctly **withheld** by the pairing gate under a real outage. **I5 holds**
box and hub agree after the return, judged after a full report cycle rather than from one read.
**Not reached:** `STALE → DOWN` (>1 h); the outage was ended once both transitions had fired because
Phase 4 needed the venue back.
### The timing discriminator used throughout
@@ -297,6 +350,32 @@ nothing about whether the key was installed. **I3** — *"every refusal names a
on, in Hungarian, with no raw error."* Low severity (the window is ~1 s wide) and recorded rather than
inflated.
### R-228 — the set-aside history becomes invisible the moment it is set aside
The move-aside is correct and was verified byte-for-byte (§F7). What follows it is not.
```json
"orphaned_renamed_to": "/home/felhom-repo.orphaned-20260805"
```
The box records the exact path. And:
```
grep -rn "OrphanedRenamedTo" internal/web/templates/ internal/web/*.go → (no hits)
```
The field is **written and read by nobody**. `/backups/remote` after the set-aside contains no
occurrence of the path, „félretéve", „régi előzmény" or any equivalent — with instrument controls
passing (`felhom-repo` → 2, „letétbe helyezve" → 1), so the page and the matcher both work.
> **12.5 MB of the customer's deliberately retained data sits at a path the box knows and never
> shows.** Its only mention is a flash message on the redirect, gone on the next click.
**The project's own "seam built but never wired" pattern** — the fifth recorded instance — landing on
the one promise the set-aside screen makes. The fix is a plain statement that an earlier history is
set aside and not deleted; **it must not promise the history can be reopened**, because R-222 means it
cannot be, and that is exactly the conditional promise R-202's gate exists to prevent.
---
## 6. What behaved correctly