REPORT: v0.245.0 — the recovery-code ask, red-proofs and the two-box live validation
gates / gates (push) Successful in 15s
gates / gates (push) Successful in 15s
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
@@ -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=`<nil>`" |
|
||||
| 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.
|
||||
|
||||
Reference in New Issue
Block a user