From 714d5bce0920fe587b1fadaa6296c641c08e5127 Mon Sep 17 00:00:00 2001 From: kisfenyo Date: Wed, 16 Sep 2026 21:20:31 +0200 Subject: [PATCH] =?UTF-8?q?REPORT:=20v0.245.0=20=E2=80=94=20the=20recovery?= =?UTF-8?q?-code=20ask,=20red-proofs=20and=20the=20two-box=20live=20valida?= =?UTF-8?q?tion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS --- REPORT.md | 84 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 45 insertions(+), 39 deletions(-) diff --git a/REPORT.md b/REPORT.md index c7321e7..bca792d 100644 --- a/REPORT.md +++ b/REPORT.md @@ -1,57 +1,63 @@ -# REPORT — controller v0.244.0: the backup page stops promising what it does not hold +# REPORT — controller v0.245.0: the household is asked for the recovery code -**2026-09-16.** Three defects the 2026-09-16 drill measured on a fresh box, all in the same family: -the product said a thing that was not true about a customer's data. +**2026-09-16.** R-543. Off-site backup is ON by default and does not RUN until the household creates +its recovery code. **The pause is the design and is untouched here** — the escrow is zero-knowledge, +their code is the only key, and a run without one would write a copy nobody could ever open. What +was missing is that nothing asked them, while the page promised the very copy that had never run. ## What shipped -**R-537 — the label is now per tier.** `BackupContents` was one string, computed from the app's shape -(`HasHDDData || HasVolumeData → "Adatok"`) and rendered on the Tier-1, Tier-2 and Tier-3 rows alike. -One string cannot be true for three tiers that capture different things: a Tier-1 unit holds compose -+ app.yaml + DB dumps + volume tars and has **no file-copy step at all** (`CaptureRecoveryUnit`; -`RecoveryManifest` has no field for one), so for the four class-A apps the customer's own files are -carried by Tier 2 and Tier 3 and by nothing else. The row now carries `Tier1Contents`, -`Tier23Contents` and `DriveFilesNote`, and the template renders the right one per row. +**The reminder bar.** While the off-site tier is configured and its escrow state is not `escrowed`, +every authenticated page carries „A távoli mentés szünetel, amíg nem hozod létre a helyreállítási +kódot." linking `/backup/escrow`. It is the **R-241 bar, second instance** — same session-cookie +dismissal („Most nem"), back at the next visit, gone for good when escrowed. No second banner system +was built. -**R-538 — the restore refuses instead of lying.** `RestoreFromRecoveryUnitAt` now returns -`*ErrUnitLacksFileLegs` **before the lock, before the stack is stopped and before any volume is -replaced**, when the app declares drive-side file legs the unit cannot hold. The web handler turns -that into a Hungarian sentence naming the route that CAN return the files — the off-site wizard's -„Teljes visszaállítás (fájlok + adatbázis)", or the second drive's „Fájlok visszaállítása" — and says -plainly when no copy exists. `UnitRestoreOptions{AcceptMissingFiles}` is the explicit, separately -worded second step; it is a per-call argument and never a field on the Manager. +*Where it hangs, and why it matters:* `executeTemplate` (server.go), the single render choke point, +not the three `addRecoveryBanner` call sites. A per-handler helper would have covered the pages +someone remembered — the seam-built-but-never-wired shape this repo has shipped four times. The +login and claim pages render through `ExecuteTemplate` directly and never pass through it; a session +check (`hasAdminSession`) additionally keeps it off the public guest share page. -*Why the refusal runs that early:* in the measured failure the replayed database also stopped -referencing the app's own wastebasket, which still held every byte on the drive. A refusal that has -already stopped the app would have destroyed the customer's last route while declining to help. - -**R-536 — „telepítve" now means installed.** The API emits `app_deploy_started` beside its 202; -`app_deployed` moved to the async path's own end via `stacks.SetDeployDoneHook`, and -`app_deploy_failed` (warning) replaces the silence an interrupted install used to get. The -accept-time `app.yaml` is deliberately kept on failure: it is the crash-safe record written with -`Deployed:false`, it holds the settings the customer typed, and the state every surface reads is -`not_deployed`. - -Also folded in: the pending „0 B" whole-system tile fix (R-517 follow-up). +**The tier-1 sentence renders by state.** v0.244.0 fixed the label (R-537) and put a new promise in +its place: „a távoli másolat … védi", printed from the app's shape alone. `driveFilesNoteFor` now +takes `tier3State`'s own vocabulary — `active` → „védi"; `escrow_pending` → „**védené** — a távoli +mentés a helyreállítási kód létrehozásáig szünetel" + the route; no off-site and no second drive → +„nincs másolat" + both ways out. One source of state, so the row and the sentence cannot disagree. ## Red-proofs — each seen failing, then passing | fix | break | what failed | |---|---|---| -| R-537 label | restore the app-shaped label | „the Tier-1 label claims it holds the app's data: `Konfig + Adatok`" | -| R-538 refusal | disable the guard | „a restore that cannot return the files must refuse; got err=``" | -| R-536 accept-time | put `NotifyAppDeployed` back beside the 202 | „the deploy handler announces an INSTALLED app at accept time" | -| R-536 completion | remove the success-path hook | „the deploy ended and nothing was told about it" | +| the bar | delete the one `addEscrowBanner` line from `executeTemplate` | „/dashboard does not tell the household the off-site copy is PAUSED" — **and the same on /launcher** | +| the sentence | compute it from the app's shape, as v0.244.0 did | „the sentence claims the files ARE protected while the copy is paused", quoting the exact v0.244.0 wording | -Each test also carries a negative control: an app whose data really is in the captured volumes keeps -its „Adatok" and is not refused. +Negative controls: an escrowed box is not nagged; an unconfigured box is not nagged; an app with no +drive-side files gets no sentence in any state. The banner fixture asserts `OffboxConfigured()` +itself, so it cannot pass by silently losing its precondition. + +## Live validation (endpoint-level; no browser on DooPlex) + +Both boxes ran 0.245.0. Requests ran **inside** the guests (the controller answers on the container +address with the mandatory `Host` header; neither guest is reachable from DooPlex). + +- **Escrowed (9201):** bar absent on /dashboard, /launcher, /backups/apps; the row reads „védi" (×2). +- **Paused (9202):** off-site configured through `POST /backup/offbox/config` → `escrow_state=pending` + with both secret files on disk. Bar present on /dashboard, /launcher, /backups/apps and /settings. + `POST /backup/offbox/run` → „A távoli mentés a kulcs letétbe helyezésére vár.", `last_run=None`, + `snapshot_count=None` — refused by the fork-4 gate, **not** by an unreachable target. A throwaway + class-A app's row rendered „…védené … szünetel" with „védi"=0. Dismissal sets a cookie with no + Max-Age and no Expires; same visit 0 hits, next visit 1 hit. +- **Teardown, three layers:** app removed with data and backups (0 containers, 0 drive folders); the + off-site target cleared and its three secrets `shred -u`'d (verified by re-reading); temp files + gone on guest and host; the hub provisioned nothing (9202 does not report). ## Gates -`go build ./... && go vet ./... && go test ./...` — green. `controller_gates.py --fast` — all 15 OK. +`go build ./... && go vet ./... && go test ./...` — green. `controller_gates.py --fast` — 15/15 OK. +CI run 671 for the previous push (`ad398b60`) — **success**, matched by head_sha. ## Requires -Hub **v0.116.0**, which registers `app_deploy_started` / `app_deploy_failed` in `allowedEventTypes` -and `customerMessages`. Against an older hub those two POSTs 400 and the events are simply absent; -`app_deployed` keeps working. MinAgent unchanged at **0.131.0**. +Hub **v0.116.0** (unchanged from v0.244.0). MinAgent unchanged at **0.131.0**. No agent change, no +hub change, no installer change, no golden.