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
+30
View File
@@ -851,6 +851,36 @@ type oomRun struct {
// OOMStormDetails is the app_oom_storm payload. stack_name lets the hub's per-app cooldown tell two
// storming apps apart.
// AppRepairDetails is app_repaired_after_crash's payload (R-1007, decision 236): COUNTS ONLY — never a file name,
// never photo data. The household's list of lost files stays on its box (app.yaml), shown on the app page.
type AppRepairDetails struct {
App string `json:"app"`
StackName string `json:"stack_name"`
BootAt string `json:"boot_at,omitempty"`
Found int `json:"found"`
Repaired int `json:"repaired"`
StillBroken int `json:"still_broken"`
Lost int `json:"lost"`
LostChecked bool `json:"lost_checked"`
DurationS int64 `json:"duration_s"`
Skipped string `json:"skipped,omitempty"`
Problem string `json:"problem,omitempty"`
}
// NotifyAppRepairedAfterCrash tells the OPERATOR what an app's after_unclean_boot run did (operator-only at the hub).
// warning: a file is lost or still broken, a step failed, or the run was skipped; info: everything found was
// repaired (recorded at the hub, not mailed). Nothing found and nothing lost: no event.
func (n *Notifier) NotifyAppRepairedAfterCrash(d AppRepairDetails) {
switch {
case d.Skipped != "":
n.pushEventMsg("app_repaired_after_crash", "warning", "event.app_repair_after_crash_skipped", d, d.App, d.Skipped)
case d.Lost > 0 || d.StillBroken > 0 || d.Problem != "":
n.pushEventMsg("app_repaired_after_crash", "warning", "event.app_repaired_after_crash", d, d.App, d.Found, d.Repaired, d.StillBroken, d.Lost)
case d.Found > 0:
n.pushEventMsg("app_repaired_after_crash", "info", "event.app_repaired_after_crash", d, d.App, d.Found, d.Repaired, d.StillBroken, d.Lost)
}
}
type OOMStormDetails struct {
App string `json:"app"`
StackName string `json:"stack_name"`