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

91 lines
9.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.