Restore-truth run (2026-10-25): D47 + D48 recorded (09 §3 decisions 256, 257; CONTEXT); 07 §6.5 (R-1271 cause + the future-date rule), §6.6 (D48 order, the R-698 sentence), §8.2d (D47), §8.2e (R-1270); register 217 -> 219 (R-1278, R-1279 opened; R-1269/1270/1271 VERIFY; notes R-1206, R-1243 dated check done read-only, R-232 moved one day); STATUS D49 + Monday list; evidence (9202)
gates / gates (push) Successful in 13m44s

This commit is contained in:
admin committed 2026-10-25 11:04:46 +01:00
1 parent 67053335d1
commit 9647f1d158
58 files changed
+1834 -14

No files matched your search

@@ -937,6 +937,26 @@ holds no database, or **does not record its data version** (§6.6 — a unit wri
a mixed or mismatched unit is refused by §6.6's own rule. Then a dated folder's files move back and the one unit restore
runs. The downloaded copy is removed on every path. A failed download or a refusal leaves the kept files as they were.
**[FACT] 2026-10-25 — why the „another drive" check let another box's copy through (R-1271; catalog sweep 2026-10-24,
Tester4: Paperless-ngx, Nextcloud, Immich).** The check (`KeptCopyAt`, `internal/backup/kept_load.go`) compares the
unit's `app.yaml` `HDD_PATH` with the drive the kept files sit on. That is a mount PATH, and every box mounts its first
data drive under the same name (`/mnt/felhom-drives/hdd_1`) — so a copy taken of another box's disk matches. It is a
path check, not a physical identity, and it stays one: loading an old box's data on a new box is what off-site is for.
What chose the wrong copy was the DATE: the rehearsal box's clock had been set to 2026-10-28, its off-site snapshot
carried that time, this box had no data time of its own to cap it (`offsiteDataTime`), and „newer than every local
copy" took it over the family's own copy of the same evening. Immich's 20 minutes of 404 were the ROUTING, not a half
load: the unit brought the old box's `SUBDOMAIN` (`photos`), so the app answered at `photos.<domain>` and the probe
asked `immich.<domain>`; its containers were healthy.
**[DESIGN] 2026-10-25 — a copy dated in the future is never „the newest" (R-1271; brief 2026-10-25 Part 4, the safest
reading; controller `bd9f4f7`, unreleased, rides v0.343.0).** A copy whose date lies more than **10 minutes** past this box's clock (`KeptClockSkew`: NTP keeps a box
within a second; ten minutes covers a box that synced late after a power cut) is future-dated. It never counts as the
newest: the newest copy with a true date wins (a tie goes local); the household's own local copy wins over any
off-site copy that is not newer by a true date. A future-dated copy is offered only when no other copy exists — then
the question says its date is in the future and does not recommend it (the „use" button is not the primary one). The
question always names the copy it will use. An off-site copy is never refused only because another box of the
household made it.
**It is NOT backed up.** No tier captures `<drive>/kept` or a leftover `appdata/<x>` of a removed app; the page says
so („Erről nem készül mentés." / "This is not backed up."). What brings it back into an app is the unit it carries
(or the app's own unit on the drive), listed per row as „Visszatölthető innen" / "Can be loaded from".
@@ -1027,13 +1047,28 @@ requires a database dump written after the conversion whose recorded engine is t
image is re-pulled on restore. A version its maker has deleted cannot start. Measured 2026-09-26: the 42 ladder
digests all resolve today (`audits/version-travel-2026-09-26/A7/`). **Accepted as a known limit — operator ruling
2026-10-12 (S8, `09` §3 decision 203; R-698 closed).** What a household meets: a restore whose version the maker has
deleted stops at the image pull, the app stays as it was, and the household uses another copy or a newer version.
deleted stops at the image pull, the app stays as it was (since D48 by ORDER — the pull comes before the stop; before it,
by the D22 put-back), and the household uses another copy or a newer version.
The operator's follow-up — *„will need to mirror the apps we support later, just to be sure"* — is **R-933**: mirror
the images of every catalog app we support into our own registry, before scale. Until it is built, this limit holds.
**Since decision 53 (`09` §3, 2026-09-30 evening):** a box keeps only the image each app service runs and the one before
it; older images of the app are deleted. A restore that needs an older version re-pulls it — as before; the limit
above is unchanged, and kept data (decision 40) is not touched by the image clean-up.
**[DESIGN] 2026-10-25 — a restore downloads first (operator ruling D48, `09` §3 decision 257; controller `2d71270`,
unreleased — rides v0.343.0).** „The app stays as it was" in the paragraph above is now true BY ORDER, not by the D22
put-back. After the existing refusals and before the first stop or change, every restore path resolves every image the
definition that will RUN names — present on the box, or pulled now — under the image-work lock held from the first
check until the restore's last start (R-863: the image clean-up cannot delete a pulled image before the start). A pull
that fails is a REFUSAL: the app is not stopped, its data is not moved, and the household reads which version, and
why: „Ennek a mentésnek a verzióját (%s) nem lehet letölteni: a készítője már nem kínálja…" when Docker says the version
does not exist (`manifest unknown`, `not found`, `pull access denied`), „…most nem lehet letölteni… Próbáld újra
később…" for everything else (a busy or unreachable registry). The operator gets one `backup_run_failures` line (leg
`restore-image-unavailable`). The paths: the local unit restore — and with it the second drive's unit restore, the
second drive's whole restore (its `CheckOnly` pass carries the pull, so its file half never runs first), a kept-data
load from a unit and from off-site, and a set-aside unit (D47) — the off-site restore, the second drive's file restore
and the no-unit fallback. Not covered: the `.fab` import — it removes the running app's volumes before its definition is pulled (R-1279).
### 6.7 Why an off-site run failed — the failure classifier (R-571)
**[FACT, read from source 2026-10-05, felhom-controller `114ff27`]** When an off-site (restic) run fails, the
@@ -1651,6 +1686,60 @@ removed the old container at start; a restore probe during the copy was refused;
15-second test limit → the container removed, the partial copy stopped growing. Controller:
`internal/backup/tier2_copy_lifecycle.go`.
### 8.2d An earlier install's unit is set aside, never rewritten [DESIGN, operator ruling D47, 2026-10-25 08:38; `09` §3 decision 256; R-1269 — controller `02daebe`, unreleased, rides v0.343.0]
**[FACT] Measured before (controller 0.342.0, which is `e1613ae` for these files):** the catalog sweep 2026-10-24
(three times) and 9202 2026-10-25 (`audits/restore-truth-2026-10-25/before/`): Tandoor, one account, „Mentés most",
remove keeping data and backups, install again. Within five minutes the periodic refresh rewrote ONLY the unit's
`compose/app.yaml` (checksum `cbfcb538…` → `e19d2610…`) — the new install's database password — beside the OLD install's
data. Its one „same definition" test was the image pins (`recovery_unit.go:194`, the R-696 `frozen` rule), and a
reinstall of the same version has the same pins. A restore then paired the new password with the old data directory:
`password authentication failed` ×44 (app) / ×23 (postgres), the app stayed `starting`, the account was gone from
view. The next night's dump legs would have overwritten the old data as well, and the stale-drive prune deleted a unit
on another drive whatever install it belonged to.
**[DESIGN] The rule.** A recovery unit's definition and its data are written only together, by a backup of the same
install. Every install has an identity (`install_id` in its `app.yaml`): minted at a fresh install and at a `.fab`
import, carried by every load-then-save, taken FROM the unit by a restore (the app then holds that unit's data), and
minted once for an app installed before D47. The unit records it in its own `app.yaml`. Every writer of a unit asks
first — the capture and its refresh, the night database dump, the pre-backup and the update's pre-backup dumps, the
volume dump, the second drive's unit copy, the stale-drive prune. A unit of ANOTHER install (different identities; for
a unit written before D47, a carried secret that differs — a reinstall mints new ones) is **moved whole** (a rename on
its own drive; never a copy, never an edit) to `<namespace>/backups/set-aside/<app>/<UTC stamp>/` and recorded as a
kept copy (§8.2a's record: kind `unit`, reason `earlier_install`); the new install then gets its own unit from its
first backup. A move that cannot be done **refuses the write** and leaves the old unit byte for byte (an ERROR line
names the app and both identities). Nothing moves a unit while that app is being restored.
**What the household sees.** The restore page lists the set-aside unit under „Megőrzött másolatok" („…· egy korábbi
telepítés mentése") with **„Visszaállítás"** — the one unit-restore body, with the unit's own database password — and
„Eltávolítás". Only those two, and the app's remove with „delete its backups too", ever remove it (the §8.2a / R-1252 H9
rule). After a restore from it, the app is that unit's install; the app's own unit of the install it replaced is then
set aside at the next backup — never lost.
**Known limit, stated.** A box that reinstalled and refreshed BEFORE this release already holds a rewritten unit; its
old password is gone and nothing can bring it back. A unit from before D47 of an app with no carried secret has no
signal: it is treated as this install's (the old behaviour) and gets the identity at its next write.
**Why a volume app's reinstall asks no kept-data question:** the question (§6.5, decision 36) is about the app's drive
FOLDER (`${HDD_PATH}` binds under `appdata/`); a volume app keeps nothing there — „remove, keep my data" removes its named
volumes and keeps only its backups — so the install starts empty, and the kept backup is now the set-aside unit on the
restore page. (The missing offer at install is its own row, R-1278.)
### 8.2e A restore whose app does not start is never „success" [DESIGN, R-1270, 2026-10-25 — controller `b680db8`, unreleased, rides v0.343.0]
**[FACT] Measured before:** the health check after a restore was a WARN on every path, so a Tandoor restore whose app
stayed `starting` was recorded `ok: true`, „…visszaállítva — az alkalmazás újraindult." (catalog sweep 2026-10-24; 9202
2026-10-25, `audits/restore-truth-2026-10-25/before/`). **[DESIGN]** Every restore path — the unit restore (so the
second drive's unit and whole restore, a kept load, a set-aside unit), the off-site restore, the second drive's file
restore and the no-unit fallback — returns „not started" when the app does not reach running within the health wait
(90 s). The household reads „A(z) %s: az adatokat visszatöltöttük, de az alkalmazás még nem indult el. Várj pár percet,
majd nyisd meg újra. Ha így marad, szólj nekünk." (the record `ok: false`, `not_started: true`); the operator gets one
`backup_run_failures` line (leg `restore-not-started`). **The safest reading, the operator may change it:** a timeout
does NOT pour the old data back — a slow first start (a database migration) is common, and a put-back would throw away a
correct restore. The data stays restored and the restore's undo copy is KEPT — the same „kept for support" state a failed
put-back leaves (§8 D20/D22), so no new retention rule. A watcher looks 30 more minutes: when the app runs, the record
turns to success („…elindult: a visszaállítás kész.") and the copy is removed, as after any successful restore; if it
never runs, the copy stays for support.
### 8.2c Decisions in code that no document recorded [DESIGN — undocumented, found 2026-10-24, review round 2 — the operator confirms them through one register row]
Read at `file:line` on `main` (controller `321df1f`, hub `44d19042`) by the main session:
@@ -1089,6 +1089,24 @@ its length, and both fixes cost something the household would notice — operato
`plex: security fix CVE-2026-96652,CVE-2026-96654,CVE-2026-96655,CVE-2026-96656 taken` and ended `done` in 30.1 s
after the update's second-drive copy and undo copy. Released as controller 0.341.0, global floor 11:24 UTC.
### 2026-10-25 (08:38) — D47: nothing changes an existing backup; D48: a restore downloads first (operator rulings by starting the run; R-1269)
256. **An existing recovery unit is never changed in part (D47).** A unit's definition (compose, pins, `app.yaml`) and
its data are written only together, by a backup of the same install. A new install never adopts, rewrites or
deletes a unit that an earlier install left; that unit is set aside whole and stays restorable. *Operator ruling
2026-10-25 08:38, taken by starting the R-1269 brief.* Why: on 2026-10-24 the catalog sweep measured three times
that a reinstall's own five-minute refresh wrote the new install's database password into the unit „remove — keep
backups" had kept; a restore then could not open the old data (R-1269). Built: every install has an identity
(`install_id`), the unit records it, and every writer asks first — `07` §8.2a „An earlier install's unit is set
aside" (controller, unreleased; rides v0.343.0).
257. **Every restore path resolves and pulls every image its definition names before the first change to the app; a
failed pull is a refusal with the app untouched (D48).** *Operator ruling 2026-10-25 08:38, taken by starting the
brief.* Why: the restore stopped the app and moved its data first and pulled only inside the database-only start;
a version that could not be downloaded then ended in the D22 put-back, a full stop-and-pour-back for nothing.
Built: `07` §6.6 „The limit — the image is not in the backup" (controller, unreleased; rides v0.343.0).
**Confirmed, no change:** `07` §6.6 — a restore brings the app back at its data's version, then the guarded update
climbs the ladder; R-698 (a deleted version cannot come back) and R-933 (our own image mirror) are unchanged.
### 2026-10-15 (07:48–08:20) — bake first; Plex through its own key; HSTS one day (recorded before the media build)
247. ~~**Bake first.** A controller version is RELEASED only when its golden is baked.~~ **REVERSED by decision 250,