docs(report): STOP-1 evidence — both legs passed; P1 answered; new R-55 finding

This commit is contained in:
2026-07-21 12:57:27 +02:00
parent 984ea8c8bd
commit 1dad3c97fd
+78 -5
View File
@@ -128,13 +128,86 @@ promoted into `documentation/PROMPT-TEMPLATE.md` §10 (Part-5 rider).
---
## 6. Live validation
## 6. Live validation — STOP-1 RAN, both legs PASSED (operator-present)
**None claimed for R-51/R-52.** The three legs are destructive and operator-present. The available
method here is endpoint-level (no browser on DooPlex); these legs additionally need a container kill
and a guest reboot on the production demo box, so they are STOP-1, not a CC action.
Method: the exact endpoints the UI calls, driven from inside guest 9201 through traefik
(`--resolve felhom.demo-felhom.eu:443:127.0.0.1`, session cookie + CSRF token), plus container-level
truth from `docker ps -a` and the controller's own log. No browser (none on DooPlex). The session
cookie jar was deleted from the guest afterwards.
What IS verified without the box: the full green gate, all four design gates
**Deploy first, per the resolved ordering:** the operator saved the floor → `0.156.0`, the box
self-updated, and the R-23 datapoint came free —
`[selfupdate] Post-update startup: update successful (0.155.0 → 0.156.0)` then
`Current version 0.156.0 is up to date`: **one swap, no re-fire.**
### Leg A — R-51, the dead primary (12:50:40 → 12:51:52 CEST)
| Time | Event |
|---|---|
| 12:50:40 | `docker stop immich-server``exited | restart-policy=unless-stopped`; the three helpers stay `running` — the exact F4 shape |
| **12:50:53** | **stack state = `degraded`** (13 s). `immich-server -> exited | Exited (143)`, three helpers `running`. This read `running` for 18 hours on 2026-07-20 |
| 10:51:11Z | **exactly ONE** `Event pushed: app_start_failed (warn) — Telepített alkalmazás nem fut: Immich` — single-fire verified by `grep -c`, = 1 |
| — | Dashboard: banner *"Telepített alkalmazás nem fut: Immich (degraded)"* + link *Rendszermonitor*, and the new „Részlegesen leállt" label present in the rendered page |
| 12:51:37 | `docker start immich-server` |
| 12:51:52 | state back to `running`; **banner self-cleared** (state-based, as designed) |
The `(degraded)` in the banner is the English-state wart predicted in §7 — visible now, pre-existing,
still a copy decision rather than a bug.
### Leg B — R-52, the boot orphans (12:53:07 CEST reboot)
Fixture: `docker stop` on **bookstack** (2 containers) and **calibre-web** (containers left in
place); **UI Stop** on **immich** via `POST /api/stacks/immich/stop` (`compose down` → 0 containers).
Then `pct reboot 9201`.
```
10:53:22Z [bootrecon] Boot reconciliation: 1 boot-orphaned app(s) found: [bookstack] — up to 2 attempt(s)
10:53:28Z [bootrecon] attempt 1/2: started "bookstack" (took 6.0s)
10:53:28Z [bootrecon] complete: 1 app(s) recovered in 1 attempt(s): [bookstack]
```
**Zero `app_start_failed`** since the reboot — a successful recovery inside the boot grace is silent,
which was the design claim. And the quiet path is observable on the healthy box too: the earlier
0.156.0 boot logged `Boot reconciliation: no boot-orphaned apps (nothing to start)`, so "nothing to
do" and "never ran" are distinguishable in production, not just in a test.
### P1 — answered, and the F5 hypothesis CONFIRMED
bookstack carries `restart=unless-stopped`. The Docker daemon came up at ~10:53:15Z. The container's
`StartedAt` is **`10:53:28.05Z` — the exact moment `bootrecon`'s `StartStack` returned.** Docker's
own restart policy did **not** resurrect it: a container stopped before shutdown is recorded
user-stopped and stays down across the reboot. Only R-52 brought it back. That is the F5 hypothesis
the audit could not test, settled with direct evidence and at zero extra cost.
### The finding this leg produced — new R-55 (it is the opposite of what the leg set out to prove)
Leg B was designed to show that a customer's deliberate Stop survives a reboot. **It does not — for
any drive-backed app.** immich, stopped from the UI seconds earlier, came back **running**:
```
10:53:18Z [gate] boot …: live bind confirmed — recreating drive-backed app immich (state=stopped) onto /mnt/felhom-drives/hdd_1
10:53:18Z [stacks] Stopping stack: immich → Starting stack: immich
```
`internal/web/intermediary.go`'s boot bind gate selects on `cfg.Deployed && HDD_PATH` alone and runs
`StopStack` + `StartStack` on every match; the stack's `state` is passed in **for the log line only**
and never consulted. calibre-web went the same way; bookstack, not drive-backed, fell through to
R-52 — which is why the sweep found exactly one orphan.
**R-52 is not implicated.** Its own gate behaved exactly as specified: immich, at zero containers,
was never a candidate. But two things follow and need an operator ruling rather than a drive-by fix
(the gate exists to fix a real bind-ordering bug and must keep doing that):
1. A customer's Stop is silently undone on drive-backed apps at every reboot.
2. **R-52's practical scope on a real box is narrower than the brief assumed** — the gate reaches
every drive-backed app first, so the boot-orphan sweep only ever sees the remainder.
Filed as **R-55**; likely shape is that the gate should recreate only apps that were RUNNING at
shutdown, i.e. the same running/stopped distinction R-52 already draws.
## 6b. What was NOT claimed live
**Nothing beyond the above.** What is verified without the box: the full green gate, all four design gates
(`template_id_gate.py`, `emoji_gate.py`, `native_confirm_gate.py`, `offbox_rename_gate.py` — all
OK), and the image build+push.