Files
felhom.eu/documentation/audits/restore-truth-2026-10-25
..

A backup is never changed by a new install, a restore downloads first, and a restore tells the truth (2026-10-25)

Brief: Sunday 2026-10-25, up to 12 h, commit only. Operator rulings D47 and D48 (2026-10-25 08:38, 09 §3 decisions 256, 257). Box: scratch guest 9202 on demo-hp (ssh hp), controller 0.342.0 for the BEFORE proofs (its restore, unit and kept-data code is byte-identical to e1613ae: no commit between 42cda07 and e1613ae touched recovery_unit.go, restore_unit.go, restore_dbcreds.go), then 0.343.0-rc.1 built from bd9f4f7 (an -rc tag is never offered to a household) for the AFTER proofs; 0.342.0 put back at the end.

Safest readings taken (the brief allowed no question)

  1. Install identity: a new field install_id (16 random hex), not the data stamps — the stamps say which VERSION wrote the data, not which install. A unit from before D47 has none: then a carried secret that differs on both sides means another install (a reinstall mints new ones); with no differing secret it is this install's (the old behaviour).
  2. Where the set-aside unit lives and how it is removed: <namespace>/backups/set-aside/<app>/<UTC stamp>/ on its own drive (a rename), recorded as a D25 kept copy — listed on the restore page with „Visszaállítás" and „Eltávolítás". The app's remove with „delete its backups too" deletes it too: the D25 / R-1252 H9 rule for kept copies.
  3. A „partial write" = one half (definition or data) written into a unit whose other half belongs to another install. The writer refuses it when the set-aside cannot be done. A definition-only unit of a fresh install (no data yet) is not partial — it is that install's whole unit.
  4. R-1270 (the brief's own safest reading): a health timeout does not pour the old data back; the data stays, the undo copy is kept (the „kept for support" state a failed put-back leaves — no new retention rule), a late start within 30 minutes turns the record into a success and releases the copy.
  5. D48 sentences: Docker's „manifest unknown / not found / pull access denied / repository does not exist" reads as „the maker no longer offers it"; everything else (busy, rate limit, timeout, unreachable) as „try again later".
  6. R-1271: the clock-skew allowance is 10 minutes. A future-dated copy that is the only copy is still offered (it may be the household's own), marked and not recommended. The „another drive" check stays a path check (the brief: an old box's data on a new box is what off-site is for).
  7. The .fab import is not a „unit" path and is not changed here; its gap is a row (R-1279).

Before (9202, controller 0.342.0 = e1613ae for these files) — before/

catalog-sweep-2026-10-24/tools/unitprobe.py tandoor on 9202 (before/_unitprobe/tandoor/): install, one account, „Mentés most" (unit 07:53:46), remove keeping data and backups, install again, wait for the controller's next capture, „Visszaállítás".

  • R-1269: 08:00:36 „Recovery unit captured for tandoor" — the kept unit's app.yaml checksum cbfcb538… → e19d2610…, created_at 07:53:46 → 08:00:36, the data files unchanged. Cause at file:line: felhom-controller/controller/internal/backup/recovery_unit.go:194 (e1613ae) — the refresh's only „same definition" test is the pins (frozen), and a reinstall of the same version has the same pins; the writes at :254-258 then put the new install's app.yaml (its new database password) beside the old data. The restore takes the unit's password (restore_dbcreds.go R-1106 rule) — the new one — over the old data directory.
  • R-1270: the restore record ok: true, „A(z) tandoor: 3 adatkötet és az adatbázis visszaállítva — az alkalmazás újraindult." while the app stayed starting; password authentication failed ×44 (app) / ×23 (postgres); the account not there (restore_unit.go:567-569 at e1613ae: the health failure is a WARN).
  • Why a volume app's reinstall asks no kept-data question: the question is about the app's drive FOLDER (${HDD_PATH} binds under appdata/, stacks/kept.go OldAppDataPaths; 07 §6.5, decision 36). Tandoor keeps nothing there; „remove, keep my data" removed its three named volumes (the record's volumes_removed) and kept only its backup. A gap for the household → R-1278.

After (9202, controller 0.343.0-rc.2 = 1f9c863) — after/

proof before (e1613ae) after
R-1269 / D47, Tandoor (tools/unitprobe2.py, after/_unitprobe2/tandoor/) the refresh rewrote the kept unit's app.yaml; the restore paired the new password with the old data, starting, account gone 09:13:03 „the recovery unit of an earlier install (77ea…; the install ids differ) is SET ASIDE whole → backups/set-aside/tandoor/20261025T091303Z"; the set-aside unit is install A's unit byte for byte (9 files, sha256 each); „Megőrzött másolatok" offers „Visszaállítás"; restored in 42 s, running, the account reads back, 0 password errors (app and postgres); remove with data leaves nothing, the set-aside folder included
R-1269 / D47, AdventureLog (after/_unitprobe2/adventurelog/) (sweep: the same fault on its re-run) set aside 09:23:03, byte for byte (8 files), restored, running, the item back, 0 password errors, nothing left
D48 (tools/pullprobe.py vikunja, after/_pullprobe/vikunja/) the first pull came after the stop (code order, restore_unit.go at e1613ae) the unit's pin edited to vikunja:0.0.0-felhom-d48-missing → „REFUSED … before anything was touched: … could not be downloaded (the version does not exist any more)"; the household read „…verzióját (vikunja:0.0.0-felhom-d48-missing) nem lehet letölteni: a készítője már nem kínálja. Az alkalmazáshoz nem nyúltunk…"; the container's StartedAt the same before and after (09:29:54.57); the item still there; the original unit back byte for byte → a normal restore worked, the item back
R-1270 (same run) ok: true „…az alkalmazás újraindult." over starting the unit's pin edited to busybox:1.36.1 (pulls, exits at once) → after the 90 s wait the record ok: false, not_started: true, „A(z) vikunja: az adatokat visszatöltöttük, de az alkalmazás még nem indult el. Várj pár percet…"; the undo copy kept (restore-undo/vikunja/20261025T093149Z); the original back → a normal restore worked, the item back
R-1271 (tools/keptprobe.py romm, after/_keptprobe/romm/) the future-dated off-site copy chosen over the family's own (sweep) a forged second-drive copy dated 2026-10-28 06:39 beside the app's own unit: the question named „saját mentés, 2026-10-25 10:38", no future flag; the own unit moved away for a moment: the forged copy alone was offered with use_future: true and „Figyelem: ennek a mentésnek a dátuma (2026-10-28 07:39) a jövőben van…"; with the own unit back, „use my kept data" loaded tier 1 (backups/primary/romm, 09:38:55Z), the item back

Two findings of the live runs, fixed without a row (each red-proved): (1) run 1 of the Tandoor proof (after/run1-list-hidden/) found the restore page listing NO kept copy on 9202: the „Megőrzött másolatok" section sat inside the off-site card, and 9202 has no off-site — so a box without off-site never listed a D25 copy either. Fixed in 1f9c863 (one named template, two call sites, parity fixtures unchanged); rc.2 then listed it. (2) The R-1270 run's kept undo copy outlived the app's remove with its backups; fixed in 1f82959 (the remove deletes backups/restore-undo/<app>), proven by its test only — the copy on 9202 was removed by hand (2.6 MB, mine). after/run1-probe-bug/ is a probe of mine that crashed on its own bug before touching anything.

The file:line causes (at e1613ae): R-1269 backup/recovery_unit.go:194 (the frozen pins test) and :254-258 (the writes); R-1270 backup/restore_unit.go:567-569, the same WARN at offbox_reconstitute.go (the off-site path), tier2_restore.go (the file restore) and restore.go (the fallback); D48 restore_unit.go:497 (the stop) before :552 (the first compose up, the DB-only start); R-1271 backup/kept_load.go:179-184 (KeptNewer: the newer date wins, future or not) and :97 (the „another drive" check compares HDD_PATH, a mount path).

Not covered and filed: R-1278 (a volume app's reinstall does not offer its kept backup), R-1279 (the .fab import removes the app's volumes before its pull).

Observation, NOT-A-FINDING: 9202 logs „immich: definition REFUSED by the allow-list" at every start — it was there before this run's image (an earlier session's Immich on /mnt/sizing-bench); not this run's.

Teardown

  • Machine: 9202 only — my throwaway installs (Tandoor ×3, AdventureLog, Vikunja, RomM) each removed through the product with their data; the two empty folders my runs left (RomM's appdata/romm, the empty backups/set-aside/) and the one kept undo copy (above) removed by hand after checking they held nothing else; the 0.343.0-rc.1/.2 images removed from its Docker; the controller back on 0.342.0 (/etc/felhom-controller-image); the apps found running (Immich, Paperless-ngx, the file browser, traefik) running. No VM, no LXC created; Tester 1 and the thin pool untouched.
  • Host: DooPlex — the session's 9202 password file in the session scratchpad shredded at the end. The rc images stay in the registry as test tags (never offered to a household).
  • Hub: provisioned nothing (9202 has no hub). ep0: nothing.