v0.204.0 — the restore list is keyed on the store (R-237); the size gate stops refusing in silence (R-238)
gates / gates (push) Successful in 26s

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.
This commit is contained in:
2026-08-06 16:44:05 +02:00
parent 4d349d1106
commit 53e9bf0224
13 changed files with 648 additions and 123 deletions
@@ -66,22 +66,40 @@
<div class="backup-section-card">
<h3>Visszaállítás a távoli tárolóból</h3>
<p class="form-hint" style="margin:-0.25rem 0 1rem">Válaszd ki az alkalmazást, és a következő oldalon döntsd el, mit szeretnél: ellenőrzést külön mappába, csak a hiányzó fájlok visszahozását, vagy teljes visszaállítást. Egyik sem indul el kérdés nélkül.</p>
{{if .OffboxToggledCount}}
<!-- R-237: driven by what is IN THE STORE, not by what is deployed and toggled. A rebuilt box has
neither and used to be told there was nothing to restore, while its snapshots sat in the
repository the whole time. Installed-ness is a property OF a row, never a filter on it. -->
{{if eq .OffsiteStoreState "unreadable"}}
<p class="form-hint">Nem tudjuk elolvasni a távoli tárolót, ezért <strong>nem tudjuk, mi van benne</strong>. Ez nem azt jelenti, hogy üres — próbáld újra később, vagy jelezd az üzemeltetőnek.</p>
{{else if eq .OffsiteStoreState "no-target"}}
<p class="form-hint">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.</p>
{{end}}
{{if .OffsiteRestoreRows}}
<div class="app-row-list">
{{range .OffboxApps}}
{{if .Enabled}}
{{range .OffsiteRestoreRows}}
{{template "app_list_row" dict "Slug" .Slug "Name" .DisplayName}}
<!-- R-48: ONE entry per app. The five inline forms that used to live here — verify,
prepare, the revealed commit, the missing-only merge and the true reconstitution —
were separable only by layout, and two of them differed by whether the customer's
data comes back at all. They are now described intents inside the wizard. -->
<a href="/backups/restore/app?name={{.Name}}" class="btn btn-xs btn-outline">Visszaállítás…</a>
{{if .Restorable}}
{{if and .InStore (not .Installed)}}
<span class="form-hint" style="margin-right:.5rem">Nincs telepítve — a visszaállítás előbb újratelepíti.</span>
{{end}}
{{if .StoreUnknown}}
<span class="form-hint" style="margin-right:.5rem">Nem tudjuk, van-e mentése — a tárolót nem sikerült elolvasni.</span>
{{end}}
<!-- R-48: ONE entry per app. The five inline forms that used to live here — verify,
prepare, the revealed commit, the missing-only merge and the true reconstitution —
were separable only by layout, and two of them differed by whether the customer's
data comes back at all. They are now described intents inside the wizard. -->
<a href="/backups/restore/app?name={{.App}}" class="btn btn-xs btn-outline">Visszaállítás…</a>
{{else}}
<!-- Shown, not hidden: "installed but nothing in the store" is an answer. Silently
dropping the row is what made R-220's empty list unreadable, one screen over. -->
<span class="form-hint">Nincs mentése a távoli tárolóban — nincs mit visszaállítani.</span>
{{end}}
{{template "app_list_row_end"}}
{{end}}
{{end}}
</div>
{{else}}
<p class="form-hint">Nincs távoli mentésre jelölt alkalmazás — a kijelölés a <a href="/backups/remote">Távoli mentés</a> oldalon történik.</p>
{{else if eq .OffsiteStoreState "known"}}
<p class="form-hint">A távoli tároló üres — nincs mit visszaállítani.</p>
{{end}}
<!-- R-7b: the shares source. Not an app row — it has no per-app toggle and no recovery unit —