# REPORT — TASK A storage-lifecycle cleanups → controller v0.64.0 (2026-06-14) Two settings-layer cleanups (A1, A2) + two doc-only changes (A3, A4) from the F9 storage-registration diagnosis. Trunk-based, all on `main`, no branches. Each code change has a table-driven test that fails on the pre-fix code. Built, pushed, and deployed to demo guest 9201; verified `:0.64.0 Up (healthy)` with a clean startup (`controller_started (0.64.0)`, no errors). > Verify against pushed source, not this report. ## Pre-flight `main` is the only branch in felhom-controller; HEAD was `688ba0d v0.63.0` (CHANGELOG top + live container both `:0.63.0`). Confirmed before starting. ## Per-item results ### A1 — `AutoDiscoverStoragePaths` made ADDITIVE - **File:** `controller/internal/settings/settings.go` (the function previously at L584). - **Change:** removed the `if len(s.StoragePaths) > 0 { return }` early-return. It now registers only discovered paths NOT already present. Invariants enforced: - Never removes/modifies a manually-added path. - SKIPS any path already in the registry IN ANY STATE — including a `Decommissioned` soft-marked entry — so it is never re-added or reactivated (`if existing[path] { continue }`, keyed on `filepath.Clean`). - Never flips `IsDefault`: a newly-discovered path becomes default ONLY if the registry currently has no default at all (and then only the first such new path) — `IsDefault: !hasDefault`, with `hasDefault` seeded from existing entries and set true after the first new default. - **Tests:** new `controller/internal/settings/storage_discovery_test.go` (`TestAutoDiscoverStoragePaths_Additive`, table-driven, + `..._DecommissionedNotReactivated`): - (a) non-empty registry + a deployed-app path missing from registry → exactly that path registered; pre-existing entry byte-identical via `reflect.DeepEqual` (IsDefault asserted unchanged); new path not promoted to default. - (b) Decommissioned path present + a deployed app still pointing at it → NOT reactivated; entry byte-identical; path count unchanged. - extra cases: empty registry (first becomes default), registry-with-no-default (first new becomes default), fallback path pickup. - **Non-hollow proof:** temporarily removing the `if existing[path] { continue }` guard makes `..._DecommissionedNotReactivated` FAIL (`path count changed: got 4 want 2`); restored → green. - **Commit:** `2d4d432` (shared with A2). **Test result:** `ok internal/settings`. ### A2 — internal-SSD label disambiguation - **File:** `controller/internal/settings/settings.go` (`InferStorageLabel`). - **Change:** when `filepath.Base(path) == appbackup.FelhomDataDir` (the `"felhom-data"` namespace dir), the label is now `Belső SSD (rendszer)` instead of the colliding `Tárhely (felhom-data)`. Imported `internal/appbackup` for the constant (no import cycle — appbackup only depends on `internal/util`). Still overridable via `SetStorageLabel`. - **User-drive safety confirmation:** Model-A user drives register their MOUNT ROOT (e.g. `/mnt/felhom-usb`) — confirmed live on 9201, where the user drive is registered as `/mnt/felhom-usb`, never `.../felhom-data` — so the `base == felhom-data` match cannot mislabel a user drive. - **Untouched:** the separate host-metrics label in `web/agent_host_metrics_handler.go:91` was left as-is (display-only host-metrics path; A2 is the storage-registry label only). - **Tests:** `TestInferStorageLabel` covers `/mnt/sys_drive/felhom-data` → `Belső SSD (rendszer)`, a second felhom-data path, plus the existing hdd/ssd/usb/`Tárhely` branches (note: `felhom-usb` does NOT start with `usb`, so it correctly labels `Tárhely (felhom-usb)` — pre-existing behavior). - **Demo seeded-label update (before/after):** the seeded `settings.json` on 9201 had `"/mnt/sys_drive/felhom-data" → "Tárhely (felhom-data)"`. Updated **through the real UI endpoint** (`POST /settings/storage/label` → `SetStorageLabel`, the same handler the dashboard form posts to) to `"Belső SSD (rendszer)"`; verified persisted in `settings.json`. (Note: the public URL `https://felhom.demo-felhom.eu` returned 200 without applying the change — the Cloudflare/Traefik path mangled the POST body; posting to the container's own `127.0.0.1:8080` returned 302 success and persisted. Same handler either way.) Before: `Tárhely (felhom-data)`. After: `Belső SSD (rendszer)`. - **Commit:** `2d4d432` (shared with A1). **Test result:** `ok internal/settings`. ### A3 — doc: reject auto-register-on-attach (no deploy) - **File:** `felhom.eu/documentation/backlog/DIAGNOSIS-f9-storage-registration-gap-2026-06-14.md` (the doc lives in `backlog/`, not `audits/` as the task said). - **Change:** recommendation #1 annotated **REJECTED (2026-06-14)** — contradicts the new→enrolled manual-enrollment model; manual enroll is by design. Noted that the "make discovery additive" sub-point WAS adopted (v0.64.0 A1) but only for paths deployed apps reference, not auto-register of agent-attached drives. - **Commit:** `b5a9614` (felhom.eu repo). ### A4 — doc: live-validation rule (no deploy) - **Files:** `felhom-controller/CLAUDE.md` and `felhom-agent/CLAUDE.md` — added the verbatim "Live validation" paragraph to both. - **Commits:** `e850402` (felhom-controller), `8e6d00a` (felhom-agent). ## Build / deploy / verify - `go build ./... && go vet ./internal/settings/ && go test ./...` → all green locally. - Built + pushed `gitea.dooplex.hu/admin/felhom-controller:0.64.0` on 192.168.0.180. - Deployed to guest 9201 via the golden/bootstrap mechanism (`docker pull` → write `/etc/felhom-controller-image` → restart `felhom-controller-bootstrap.service`). - **Verify:** `docker ps` → `gitea.dooplex.hu/admin/felhom-controller:0.64.0 Up (healthy)`; logs show `Event pushed: controller_started (info) — Controller elindult (0.64.0)` and no startup errors. No rollback needed. ## Commit hashes (per item) | Item | Repo | Commit | |---|---|---| | A1 + A2 (code + tests + CHANGELOG + README) | felhom-controller | `2d4d432` | | A4 (CLAUDE.md) | felhom-controller | `e850402` | | A4 (CLAUDE.md) | felhom-agent | `8e6d00a` | | A3 (diagnosis annotation) | felhom.eu | `b5a9614` |