R-237: /backups/restore listed apps that are CURRENTLY DEPLOYED and CURRENTLY TOGGLED ON for future off-site backups. A rebuilt box has neither, so a household that had just lost everything was shown nothing to restore while the repository held their snapshots — measured live on the R-201 re-walk. To restore an app you had to select it, to select it you had to have installed it, and to know what to install you had to see the backup you could not see. The store is now the source of the list (offsite_restore_list.go), built on the existing R-193 OffsiteInventoryList. Installed-ness became a property OF a row, never a filter on it. Every case is answered rather than hidden: a snapshot for an app that is not installed is offered and says it will reinstall first; an installed app with no snapshot is shown as having nothing; an unreadable store renders as UNKNOWN (R-225's rule, one screen over) AND keeps the action, because "we could not look" is not "there is nothing"; no-target is its own state. The felhom-offbox and _shares marker tags are excluded from the app list. R-238 classified as a HARNESS ARTIFACT: mode=full without confirm=1 is step 1 of a deliberate two-step — it starts no job by design and redirects carrying &full_prep=<app>, which deriveWizardStep requires to reveal the commit. A driver that did not carry it forward landed back on the intent step. The operator's browser run completed the same restore. The wizard's precedence rules were NOT re-keyed: a stale ?full_prep= must never resurrect a commit button mid-restore. The residue WAS real and is fixed: neither branch of that step wrote anything to the log, so a refusal — including by the headroom gate — left no trace on the box. Both branches now log, and so does the concurrent-op refusal. resolveWizardApp is removed: it was dead once the gate moved, and its test pinned the defect's behaviour (an untoggled app refused), which would have read as policy. 28 packages ok, 9/9 gates OK. Three red-proofs, each asserted to have applied.
5.6 KiB
REPORT — v0.204.0: the restore list is keyed on the store (R-237), and the size gate stops refusing in silence (R-238)
2026-08-06. Controller v0.203.0 → v0.204.0. MinAgent unchanged (0.127.0) — nothing here needs
a new agent capability. felhom-agent untouched.
R-238 — classified: a HARNESS ARTIFACT, with a real residue that is fixed
The two runs diverge at one parameter, and the state at that point is quoted rather than inferred:
POST /backup/offbox/restorewithmode=fulland noconfirm=1is step 1 of a deliberate two-step (offbox_handlers.go:314). It computes size + headroom viaOffboxRestorePrepareFull, starts no job, and redirects torestoreWizardPath(app) + "&full_prep=<app>&full_size=<size>".deriveWizardStep(restore_wizard.go) reveals the commit only whenin.FullPrepApp == in.App, sourced from?full_prep=.- The endpoint-level driver posted step 1 and then re-fetched the wizard without that parameter.
The pure function therefore returned the intent step — correctly.
restore-status.last == nullis likewise correct: step 1 starts no job by design. - The operator's browser run followed the redirect, saw the confirm, pressed „Igen" twice, and the restore completed.
So the button is not dead, and the wizard was not re-keyed. The precedence rules exist so a stale
?full_prep= can never resurrect a commit button mid-restore, and they were left alone.
The residue, which is real whoever triggers it: neither branch of step 1 wrote anything to the
log. offboxRedirectTo only flashes to the page. A customer refused a disaster restore — including
a refusal by the headroom gate — left no trace on the box at all. Fixed: the refusal logs
[WARN] … full-restore preparation REFUSED for <app> (no job started): <err>, the success logs
[INFO] … full-restore prepared for <app> (size N) — awaiting the customer's confirm; no restore has started, and the concurrent-op refusal logs too.
R-237 — the restore list, rebuilt on the store
buildOffsiteRestoreRows (new, pure) merges OffsiteInventoryList (the repository's own snapshot
tags — the existing R-193 reader) with the installed set. resolveOffsiteRestoreApp replaces
resolveWizardApp's toggle requirement.
Every §7 case, and the Hungarian as rendered:
| case | rendered |
|---|---|
| snapshot present, app not installed | restore offered + „Nincs telepítve — a visszaállítás előbb újratelepíti." |
| installed, no snapshot | „Nincs mentése a távoli tárolóban — nincs mit visszaállítani." |
| store unreadable | „Nem tudjuk elolvasni a távoli tárolót, ezért nem tudjuk, mi van benne. Ez nem azt jelenti, hogy üres — próbáld újra később, vagy jelezd az üzemeltetőnek." + „Nem tudjuk, van-e mentése — a tárolót nem sikerült elolvasni." and the action stays offered |
| no target yet | „A távoli tároló kapcsolódási adatai még nem érkeztek meg ehhez a géphez, ezért még nem tudjuk megmutatni, mi van benne. Ez magától rendeződik." |
| store empty | „A távoli tároló üres — nincs mit visszaállítani." |
| app under a different name | not guessed — it lists under the tag the store holds, and if nothing is installed under that name the row says so. No fuzzy matching. |
Wizard refusals also changed: „Ehhez az alkalmazáshoz nincs mentés a távoli tárolóban." and, when the store could not be read, „Nem tudjuk elolvasni a távoli tárolót, ezért nem tudjuk, van-e benne mentés ehhez az alkalmazáshoz." Both log an INFO naming the app and the store state.
felhom-offbox and _shares are excluded from the app list.
Tests
go build · go vet · go test ./... → 28 packages ok. controller_gates.py --fast → 9/9 OK.
New: offsite_restore_list_test.go (7 tests — the truth table, the rebuilt box, unreadable-is-unknown,
no-target, empty, and two rendered-page tests) and offbox_restore_silence_test.go (handler-level,
because the silence was in the handler).
Red-proofs — each mutation asserted to have applied before the result was trusted
| # | mutation | result |
|---|---|---|
| RP-1 | store rows dropped from the builder — the list keyed back on installed apps (the original defect) | TruthTable + RebuiltBox_SeesItsSnapshots FAIL |
| RP-2 | state forced to known and StoreUnknown forced false (both guards) |
UnreadableStoreIsUnknownNotEmpty + NoTargetIsItsOwnState FAIL |
| RP-3 | both size-gate log lines removed | FullPrepareRefusal_IsNotSilent FAIL — „wrote NOTHING to the log" |
All three restored; suite green again afterwards.
Files
- new
internal/web/offsite_restore_list.go,internal/web/offsite_restore_list_test.go,internal/web/offbox_restore_silence_test.go internal/web/restore_wizard.go(gate on the store, not the toggle)internal/web/offbox_handlers.go(three log lines)internal/web/handlers.go(wire the rows)internal/web/templates/backups_restore.html(the list + the state wording)internal/web/backups_split_test.go(fixture: the new data contract)internal/backup/offbox_inventory.go(ErrNoOffsiteTargetSentinel, so the no-target case is constructible from another package's table test)CHANGELOG.md,controller/README.md
Not done here, deliberately
- R-236 is diagnosed, not fixed — the fix's shape depends on the diagnosis, and this arc has twice shipped a fix aimed at the wrong half of a defect.
- The R-193 unlock listing still shows the
felhom-offboxmarker as if it were an app. Noticed while reusing its reader; out of scope and not touched.