v0.311.0: after a power cut, an app repairs the files the cut left empty; the household sees lost photos (R-1007)
gates / gates (push) Successful in 52s

New template block after_unclean_boot (decision 236): once per controller start that followed an
UNCLEAN host boot (crashboot's own verdict, after the 15-minute crash grace; a normal or unknown boot
never runs it), each app with the block rebuilds ONLY its zero-byte files (scan: regular files, no
symlinks, its own filesystem; ids appended to the app's own command in batches; one retry, then
reported) and its own lost-file check is read. The household sees lost files (name, date taken) on
the app page until dismissed; the operator gets app_repaired_after_crash with counts only. Skips,
with the reason, an unproven image, an unknown key, an app not running. MinAgent 0.155.0 unchanged.
Tests red-proofed (14 guards). One new parity fixture (the new card only).

Claude-Session: https://claude.ai/code/session_012qRErfCoiTkvDK9N5XHbzb
This commit is contained in:
2026-10-14 16:47:13 +02:00
parent acbbf4b305
commit 8d862e4e69
24 changed files with 1865 additions and 3 deletions
+29
View File
@@ -1,3 +1,32 @@
## v0.311.0 — after a power cut, an app repairs the files the cut left empty; the household sees lost photos (R-1007) (2026-10-14)
**MinAgent: 0.155.0** (unchanged — the crash fact is the existing `GET /host/crash-guard`). **Hub:** v0.150.0 registers
`app_repaired_after_crash` (allow-listed + operator-only); an older hub answers 400 and the operator gets no counts —
the repair and the household's note do not depend on it. **Catalog:** Immich's `after_unclean_boot` block. Design: `09`
§3 decision 236; evidence felhom.eu `documentation/audits/immich-repair-2026-10-14/`.
- **New template block `after_unclean_boot`** (`internal/stacks/after_unclean_boot.go`): where the zero-byte files are
(`under`, only `${HDD_PATH}` filled in), how a name maps to the app's id (one capture group), the repair command (ids
appended, plain id text only, in batches) and the app's own lost-file check (`LOST {json}` lines). The box finds
zero-byte REGULAR files only (no symlinks, its own filesystem), runs the repair, re-scans, retries a still-empty file
ONCE, then reports it. Skips, with the reason, an image the template has not proven, a key it does not know, an app
that is not running within 10 min. It never deletes or rewrites a file, never stops the app.
- **When** (`internal/aftercrash`): once per controller start that followed an UNCLEAN host boot — `crashboot`'s own
verdict (new `Grace.Decided`), after the same 15-minute crash grace; a normal or unknown boot never runs it. Runs off
the scheduler (a goroutine; never twice).
- **The household:** the Immich app page shows „Elveszett fotók egy váratlan leállás miatt" / "Photos lost in an
unexpected stop" — each lost file's name and the date it was taken, the boot time, „upload them again from the phone",
and a dismiss button (`POST /apps/<slug>/lost-files/dismiss`). Nothing when nothing is lost. The list lives only in
`app.yaml` on the box.
- **The operator:** `app_repaired_after_crash` — counts only (found / repaired / still broken / lost, the boot, the
duration, a skip reason). warning when anything is lost, still broken, failed or skipped; info when all repaired;
nothing when nothing was found. File names never reach a log line or an event (tests assert both).
- Tests: `internal/stacks/after_unclean_boot_test.go`, `internal/aftercrash/aftercrash_test.go`,
`internal/crashboot` (`TestDecided_*`), `internal/notify/app_repaired_after_crash_test.go`,
`internal/web/lost_files_note_test.go`, one new parity fixture (`app_info_lost_files`, the new card only).
Red-proofed: 14 guards, each removed in turn convicted its test (the symlink rule only with the regular-file check
removed too — that check is what holds; both stay).
## v0.310.0 — „A gép kikapcsolása": the household can turn the whole machine off (R-998); honest restart text (R-999) (2026-10-13)
**MinAgent: 0.155.0** — the new route calls the agent's `POST /host/poweroff`. An older agent: the button is hidden and