## v0.173.0 (2026-11-10) — R-1368 part 2: the self-update folder keeps what it needs and no more — RELEASED 2026-11-10, NOT VOUCHED YET (release-agent.sh from a clean clone of `776025c`; agent sha256 752b2727fb6043417ae5f342acbda52330521b21bb799fda90760b960feef7ea, bundle sha256 51ebc1f4c66d87918c87c035b4297c0e40e10ed31e3250c3837dfeaa0002e1c1) - **The defect (measured 2026-11-10, space list place H12).** `Execute` downloads each new agent to `/selfupdate/felhom-agent-` and removed that file only when the download or its checksum failed. A good update left it for ever: 49 / 48 / 33 binaries = 740 / 724 / 509 MB on demo-hp, demo-felhom and Tester 1. - **What the folder is needed for (read first: `configs/felhom-selfupdate-guarded`).** The root wrapper reads the staged file once, copies it to its own root-owned folder and flips from that copy. The rollback needs `pending.json` (in the folder) and `/usr/local/bin/felhom-agent.prev` (outside it); the commit needs `pending.json`. No staged binary. - **The fix (`internal/selfupdate/prune.go`).** After the wrapper's `commit` returned success, `MaybeCommit` removes every staged binary but the running version's. Nothing is removed while a pending marker exists or cannot be read, at a plain start, after a rollback (the failed version's binary stays to be read), or when the running version is not a version this code can name a file for. Only a regular file with exactly the name `Execute` writes is removed, one `os.Remove` per name; a failed removal is logged and never fails the commit. A download and a prune take turns (`stagingMu`). - **Tests `TestPrune_*` (6).** After six updates the folder holds one binary and every bystander (unknown names, a folder and a symlink that carry a staged name); a pending or unreadable marker refuses; only a healthy commit prunes; the name rule follows `versionRe`; the wrapper's `commit` and `rollback` blocks name no staged binary; and the wrapper's REAL `commit` and `rollback` verbs, run from a sandboxed copy, still put the previous binary back after four pruned updates. **Red seen three times:** no prune call; no pending refusal; no regular-file check. - **Not reached.** `/usr/local/bin/felhom-agent.bak-*` (place H13): root-owned, in a folder the agent cannot write, made by a person. R-1368 part 4 stays open. - **Corrected 2026-11-10 (measured on three boxes).** This entry said: „A box gets the clean-up at its first good update AFTER the one that brings this code". That was wrong. The commit runs in the NEW binary, so the box cleans the folder at the commit of the update that brings the code. - **Delivered 2026-11-10 to the three test boxes by a signed `agent_update` each (no bundle job: `configs/` did not change since v0.172.1).** The folder before → after, read on each box: demo-hp 49 entries / 722,596 KB → 1 / 16,028 KB (commit 14:59:19 UTC, `removed=49`); demo-felhom 48 / 706,968 KB → 1 / 16,028 KB (commit 14:51:50 UTC, `removed=48`); Tester 1 33 / 497,244 KB → 1 / 16,028 KB (commit 15:00:19 UTC, `removed=33`). After each: `pending.json` absent, `/usr/local/bin/felhom-agent.prev` = 0.172.1, the unit active with no restart. - **A rollback still works after the clean-up (Tester 1, 2026-11-10 15:14 UTC).** A signed `agent_update` to 0.172.1 (the vouched version; it has no prune code) was installed with its pending marker. One `SEGV` in the 60-second dwell → the unit's `OnFailure` → the wrapper's `rollback` → 0.173.0 ran again 9 seconds after the install, `pending.json` gone, no failed unit. The failed version's staged file stays (by design, until the next good update): the folder holds 2 files, 32,040 KB. Evidence: felhom.eu `documentation/audits/release-space-fixes-2026-11-10/agent/`. - **Still to do:** the operator's vouch. Until then a new install gets the vouched version before this one. ## v0.172.1 (2026-11-07) — proxmox-grub joins the boot-chain lane: the nightly pve step stops being refused R6 — RELEASED and VOUCHED 2026-11-07 (release-agent.sh from `39db030`; agent sha256 505a0594a2fb117f359e329a0b7ae2c7a6902b74c51f52c1883145f3c2ac72f8, bundle 3e7acaee4500893bfaf567b1d427790ef89bf41f08cb6a1419ee528aeb40ddc3) - **The defect (measured on demo-felhom, 2026-11-05, 11-06, 11-07).** Proxmox published `proxmox-grub` 2.12-9+pmx4. It depends on `grub-efi-amd64` of the same version. `HOST_SLOW_RE` matches `grub` only at the START of a name, so `proxmox-grub` landed in the ring-0 pve plan; the simulation then pulled `grub-efi-amd64`, which is not in the plan, and `felhom-os-apply` refused the WHOLE pve step (R6 „the plan would touch grub-efi-amd64") every night — 34 Proxmox userspace upgrades held with it, and an `os_update_failed` error mail each morning. The same class as the 2026-10-09 `proxmox-secure-boot-support` fix. Read-only proof on the box: `apt-get -s install --only-upgrade proxmox-grub` → `Inst grub-efi-amd64 …pmx4`. - **The fix.** `proxmox-grub` joins `HOST_SLOW_RE`: GRUB's meta-package now waits with the rest of the boot chain (the slow lane, the operator's signed step), and the pve step runs without it. - **Test** `test_ring0_pending_pve_leaves_proxmox_grub_out`; red seen before the fix (proxmox-grub entered the plan), green after; the wrapper suite 149 of 149. - **Delivered 2026-11-07 (operator, signed jobs):** demo-felhom, demo-hp, Tester 1 — `agent_update` committed (23:05/23:13/23:13 CET), then the bundle: `BUNDLE DONE agent=0.172.1 written=1 same=30 self-check=ok`, capability probe 77/77 on each. `pinned.day0_agent` = 0.172.1 (D50). - **Delivery.** `configs/` changed, so a box needs BOTH signed jobs: `agent_update` 0.172.1, then `agent_config_update` 0.172.1 with the bundle sha. Not vouched by CC. Bundle vs 0.170.0 (what the boxes run): no path added or removed, only `felhom-os-apply` changed — so no step bundle is needed (R-880). Both downloads re-checked by sha. ## v0.172.0 (2026-11-03) — R-1302: the first grant of a tier is no loss — RELEASED and VOUCHED 10:18:10 CET (release-agent.sh from a clean clone of `ecbb0cc`; agent sha256 2c0222cd01713f65c627d6d17c18b4a8b2b1e0c39881db0df7ccb17de48e24c7, config bundle cc34b0bc3346ffd6a4ba80fe9269852dced8c56c9503c8b14a50d9ded2961cce; configs/ unchanged since 0.171.0, so the signed delivery was agent_update alone) - **R-1302 — the first grant of a tier is no loss.** A fresh install arms the whole-guest tier before its storage exists, so the first probe found no grant, repaired it and logged ERROR „GRANT WAS MISSING … investigate the loss (R-190)" plus a DEGRADED capability, on every new box (VM 9375, 2026-10-26). The agent now remembers which tiers have EVER held their grant (`/store-grant-seen.json`, written by a healthy probe). A first grant logs INFO „granted" and reports healthy; a grant that existed and vanished keeps R-190's ERROR and its one degraded cycle. A doubt keeps the ERROR: an unreadable or corrupt file, or no file path. A box updated to this agent records its tiers at the first healthy probe, so a loss in the one probe before that would read as a first grant. Tests `TestR1302_*`; red seen with the first-grant branch removed and on three more mutants. ## v0.171.0 (2026-10-30) — R-1344 decided from the night's measurement: a controller that runs but does not answer is RESTARTED — RELEASED (release-agent.sh from a clean clone of `bd18347`; agent sha256 c888ca7ba22a1f689059081575814a725e2edcecbb6bcc56829e9d9de88202a5, config bundle 95f977f6dc827185f2b5a2aa9e1c829db41a9f425d49eb40f317286d13c83d2b; binary-only signed agent_update on Tester 1, demo-hp, demo-felhom; vouched by the operator 2026-10-30 10:47:36 CET; `pinned.day0_agent` = 0.171.0). Live on Tester 1: the controller frozen (SIGSTOP) 09:24:50 UTC → Docker „unhealthy" 09:26:36 → the alarm 09:29:21 → the restart 09:31:22 → healthy 09:31:24; the hub mailed `controller_unresponsive` and `controller_unresponsive_restart` (hub 0.178.0), not `controller_restarted_by_agent` - **The measurement (operator brief 2026-10-29 Part 2, read 2026-10-30).** Docker's own health record of the controller on Tester 1 and demo-hp, every probe from 2026-10-29 11:12 to 2026-10-30 07:00 UTC: 4 733 probes through the night chain (demo-hp's PBS vzdump, Tester 1's local vzdump, both DB dumps, tier-2, the off-site push, a restore test, two controller updates). No failed probe, no 5-s timeout; slowest 303 ms (demo-hp, during the PBS vzdump). The only „unhealthy" was the controller stopped by hand for a proof. A busy night never came near ONE failed probe. - **So the alarm becomes a restart.** At `controllerUnresponsiveConfirm` = 6 unhealthy sweeps the alarm is raised, as before. At `controllerUnresponsiveRestart` = 10 (2 minutes later; ~6.5 minutes after the first failed probe) the agent restarts the controller's bootstrap unit — unless the controller's busy file is fresh (a backup, a restore, an update, an export) or cannot be read, or the brake refuses (3 restarts in 15 minutes → the crash-loop pause, which the hub mails). A swap, a parked guest, a locked guest and a vzdump keep resetting the count, as before. A refusal keeps the alarm and is logged once per reason. After a restart the count starts again from zero. - **The stanza** carries `unresponsive_restart_after_s` (120: the hub's alarm text says when the restart comes) and `unresponsive_restart_at` (equals `last_restart_at` for that act; the hub mails `controller_unresponsive_restart`). - **Refactor, no behaviour change:** the not-running restart's brake and its record are now `supervisorBrakeAllows` and `recordSupervisorRestart`, shared by both restarts. The busy-file stale log line no longer says „operator reboot". - Tests `TestSupervisor_UnresponsiveIsAlarmedThenRestarted`, `…Guards` (now also: never restarted), `…RestartRefusals` (busy fresh, busy unreadable, the brake, a crash-loop pause), `…StaleBusyFileDoesNotShield` — red first (build failure, then 3 behaviour failures); 7 mutants, each killed (one survivor — the count reset after a restart — found and pinned). ## v0.170.0 (2026-10-29) — „Reboot guest" from the hub (D65 = A): `reboot_guest` joins the agent's closed list — RELEASED (release-agent.sh from a clean clone of `ca15a31`; agent sha256 da8d12a05ef607c338b9ba307e7a81b18253a3188721bbd8421ba36658a22404, config bundle 03f2ff6442fc3ca600b1f25f26767ce4da466a770a258f3e362cf95f6e76b543; signed agent_config_update + agent_update on Tester 1, demo-hp, demo-felhom; vouched by the operator 2026-10-29 18:57:48 CET; `pinned.day0_agent` = 0.170.0) - **D65 = A (operator ruling 2026-10-29 15:50; felhom.eu `04` §6.1, `03`).** The agent's closed operator-action list is now `restart_controller` and `reboot_guest` (arg: the vmid). No signature: a reboot destroys nothing. The act is the existing guest-reboot path (`pct reboot ` of THAT guest over the existing exact grant) — never the host. - **Guards** (each refuses with the reason; nothing is called, no record is written): a guest this agent did not provision, not running, locked, or parked; the host-wide heavy-operation gate held (a backup of any tier the agent runs, a restore test, an OS step — the reboot HOLDS the gate until the guest is up again); `jobBlocker` (an agent backup job, a controller swap, a drive or NAS job, an escrow ceremony, a vzdump of the guest); the CONTROLLER busy — its `data/ctl/busy` file (a backup, a restore, an off-site run, an app update), fresh within 90 s; a stale file is a controller that stopped writing, not a busy one; any read failure but „no such file" refuses (fail closed); at most ONE reboot per hour per guest (`//operator-reboot.json`, on disk — it survives an agent restart); the operator restart's brake (a refusal in force refuses a reboot; a reboot counts as one of the 3 restarts in 15 minutes). - **During the reboot** the guest-power watchdog and the controller supervisor leave the guest alone (`rebootInFlight`): a racing start would fail the reboot's own start half. - **The result** says both times from the press: „rebooted; the guest was up after 41 s, the controller answered after 63 s" — or `failed`, said plainly. The record is written BEFORE the reboot; the new self-scoped `GET /guest/operator-reboot` gives it to the controller for one day, for the household's line „Felhom support restarted your box at