R-537 — the contents label is now PER TIER. One string computed from the app's shape was rendered on all three tier rows; a Tier-1 unit has no file-copy step, so for the four class-A apps it was claiming „Adatok" for files it does not hold. R-538 — a unit restore REFUSES before anything is touched when the unit cannot return the app's drive-side files, and names the route that can. It runs before the stack is stopped because the measured harm included the app's own wastebasket going unreachable, which still held every byte. R-536 — „Alkalmazás telepítve" moved from the deploy's acceptance to its completion, with app_deploy_started and app_deploy_failed as the honest pair. Each fix red-proofed: seen failing with its own sentence, passing when restored. Requires hub v0.116.0 for the two new event types. MinAgent unchanged (0.131.0). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
3.5 KiB
REPORT — controller v0.244.0: the backup page stops promising what it does not hold
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.
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;RecoveryManifesthas 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 carriesTier1Contents,Tier23ContentsandDriveFilesNote, and the template renders the right one per row.
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.
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).
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" |
Each test also carries a negative control: an app whose data really is in the captured volumes keeps its „Adatok" and is not refused.
Gates
go build ./... && go vet ./... && go test ./... — green. controller_gates.py --fast — all 15 OK.
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.