## v0.351.0 (unreleased, 2026-11-09) — old test builds and old infrastructure images are deleted (decision 56 amended, decision 285) ### 2026-11-09 **MinAgent: 0.158.1** (unchanged). **Why (measured 2026-11-09, demo-hp guest 9201, `docker system df -v`):** the whole-guest backup copies `/var/lib/docker` (mp0, `backup=1`), and demo-hp's local tier was refused for space again (felhom.eu R-548). Of 27.5 GB of images, 13.2 GB ran in containers, 9.8 GB were the apps' undo images (decision 53, kept on purpose), 2.2 GB were 28 old controller images and 1.35 GB were old infrastructure and helper images. Nothing deleted the last two kinds. **Controller images: a test build is a version (decision 56 amended).** `RetainControllerImages` left every `-rc` tag alone, so 25 test builds stayed. Tags, the running version and the previous now parse with `util.ParseVersionPre` (an rc orders below its release): an old rc goes like an old release; an rc above the running one or between the previous and the running one stays; `latest` stays. A box that RUNS a test build is judged (it was skipped). The fallback previous prefers a release over a newer test build. Tests `TestControllerRetention_OldTestBuildsGo`, `_BoxOnATestBuildIsJudged`; the existing test's rc expectation changed with the rule. **Infrastructure images (decision 285, new).** `RetainInfraImages(infra.Images())`, 3 minutes after every start: per pinned repository the pin, newer versions, the previous pin and every in-use image stay; older and untagged ones go by exact name/ID; never forced, never pruned; not while an app pulls (R-863, retried every 2 minutes, 30 tries) or while the controller swaps itself. Tests `TestInfraRetention_*` (4). Customer boxes get a new traefik or cloudflared pin every few weeks (~65–195 MB each); this is the part a customer box needs. Red-proofs: six code mutants, each test red (the previous-pin keep, the in-use check, the R-863 lock, the tag parser, the running-version parser, the release preference). Green gate: `go build`, `go vet`, `go test ./...` rc 0; `controller_gates.py --fast` OK. ## v0.350.0 (2026-11-03) — Traefik v3.7.17 (GHSA-xv5x-242w-7hf7); a restore forgets the update-held flag (R-1347); a failed put-back at start-up mails (R-1348); three false start-up lines (R-1339, R-1351, R-1323) ### 2026-11-03 **MinAgent: 0.158.1** (unchanged). **Load test, scratch 9202 only, from the LAN, 2026-11-03** (felhom.eu `audits/release-2026-11-03/part1/`): the same many short TLS connections with an `Authorization: NTLM` header to the dashboard route. v3.7.16: Traefik's anonymous memory 18.7 → 79.0 MiB after 920 connections, not released (stopped at twice its start, as the test's bound says). v3.7.17: 16.2 → 25-26 MiB, flat for 25,116 connections and the whole 300 s. **Traefik v3.7.17 (GHSA-xv5x-242w-7hf7, high, published 2026-11-02).** v3.7.14–v3.7.16: an unauthenticated client that sends a `Negotiate` or `NTLM` `Authorization` header gets a dedicated backend transport per connection, and it is not released when the connection closes: many short connections exhaust Traefik's memory, whether or not an NTLM backend exists. Traefik fronts every box. `TraefikImage` v3.7.16 → v3.7.17; a running box takes it by the reconcile at start (`TestEnsureTraefik_ANewImageRecreatesTraefik`). The upgrade notes name Go 1.27 (GODEBUG TLS settings: we set none; 500 request-header values at most, else 431) and the ingress-nginx provider (not used). Found by reading the release itself: the watch had flagged it on a word-only line (felhom.eu R-1310). **R-1347 — a restore that lifts an update hold forgets the „update held" flag.** `clearUpdateHoldAfterRestore` cleared the hold in settings, but `stacks.Manager` kept `updateHeld`: the crash-loop watcher did not watch the app and the night leg skipped it until the controller restarted. New seam `backup.SetUpdateHeldForgetter` (`stackMgr.ForgetUpdateHeld`). Tests `TestR1347_*` (the seam, the wiring, and the consequence: the same restarts trip the watcher once the flag is gone). **R-1348 — a failed put-back at start-up mails the operator.** `holdPutBackFailed` writes the D20 hold at `Recover`, before any notifier exists, so only the hold reporter's info and the hub's later reminder said anything. `AppStopGuard.SetHeldNotify` hands over what `Recover` held, once, and mails a later failure (a drive that returned) at once: the R-1340 sentence for `MixedCauseInterruptedPutBack`, from the hold record. The mail's run name says „restore" for it, not „offsite-reconstitute" (`heldRunKind`, seen on the live proof). Tests `TestR1348_*`. **Live proof 2026-11-03, scratch 9202, test image of c4b6844** (felhom.eu `audits/security-2026-11-02/part3/`): a restore cut at „the restore copy is recorded", the volume held by a created-only container, the controller started: hold `restore_mixed` / `interrupted_put_back`, the event `backup_run_failures` with the sentence in the sink. **R-1347's live proof, same image:** an update with its undo copy cut off → hold `update_failed`; the household's restore → „the update hold is CLEARED", „its update-held flag is forgotten"; six `kill -9` → the crash-loop watcher stops the app (`unhealthy_stop`), so it watches again. **R-1339 — no false „drive … not mounted".** A per-app folder under a mounted drive is never a mountpoint itself; nine false lines at every start on 9202. The folder still gets no bind and no skeleton of its own (the row's „not skipped" would write a `userdata/` inside the app's folder and recreate the file manager, so the behaviour stays and only the line changes). A drive that is really absent keeps its line, and a folder of it names the drive. `TestR1339_*`. **R-1351 — FileBrowser's two start paths at boot take one lock.** `EnsureBaseStack`'s `compose up` and `web.syncFileBrowserMounts`' `compose up --force-recreate` in the same second gave „Conflict. The container name … is already in use", two ERROR lines and a first-boot WARN for an app that then ran healthy. `stacks.Manager.LockFileBrowserStack`. `TestR1351_*`. **R-1323 — „No such container" is not „every app can still reach it".** The checked container was replaced between the check and the leave; the line now reads the CURRENT container's networks and says the truth. A current container on the app network, an unreadable one, or any other failure keeps the alarm. `TestR1323_*`. ## v0.349.0 (2026-10-29) — the household line after an operator reboot and the busy file for it (D65 = A); a hold clear records the wanted stop (R-1350) ### 2026-10-29 (evening) **MinAgent: 0.158.1** (unchanged). The new `GET /guest/operator-reboot` is asked once at start; an older agent answers 404, and such an agent cannot reboot from the hub either — nothing to show, nothing broken. The busy file is read BY the agent (≥ the reboot_guest commit); an older agent never reads it. - **D65 = A — the busy file (`internal/busymark`).** `/ctl/busy`, rewritten every 15 s while a backup, a restore, an off-site run, the night catch-up, an app update or an app export/import runs; removed when idle and at start. The agent refuses the operator's „Reboot guest" while it is fresh. Tests `TestBusyFileFollowsTheWork`, `TestBusyMarkPathIsTheOneTheAgentReads` (the path is the agent's guest path), `TestBusyMarkIsWiredAtStartup`. - **D65 = A — „Felhom support restarted your box at