Files
felhom-controller/REPORT.md
T

72 lines
5.2 KiB
Markdown

# REPORT — most recent implementation
## v0.141.0 — N100 polish: initialize-to-usable (F6) + Vissza back-routes (F7) — 2026-07-17
**Baseline:** v0.140.0 (`5be2449`) → **v0.141.0** (deployed live on demo 9201, healthy). Green:
`go build ./... && go vet ./... && go test ./...` all pass; template/emoji/native-confirm gates pass.
Closes `felhom.eu/documentation/tests/VALIDATION-n100-baremetal-2026-07-16.md` F6 + F7. (F5 lives in
felhom.eu `scripts/`; see that repo's REPORT.)
### Fork verdict (Part 1 Phase 1, source-grounded)
**F6 is controller-side — NO agent change.** The detached mkfs lives agent-side
(`felhom-agent/internal/localapi/formatjob.go:99` + `disks.go:635`, crash-safe, mkfs-only), but the
format→mount→register ORCHESTRATION is `internal/web/storage_handlers.go runStorageInit` and ran on
the REQUEST context; a disconnect cancelled it after `FormatDisk` (agent mkfs continues detached,
returns `errFormatClientGone`) → the mount+register leg never ran → device formatted but unusable.
The chain must reach `SyncFileBrowserMounts` (`handlers.go:2011`, controller-only), so a single
detached job can only live controller-side. The agent already exposes crash-safe format + assign →
agent v0.90.0 was NOT triggered.
### F6 — initialize now ends in a mounted+registered drive, disconnect-safe
- `POST /api/storage/init` starts a DETACHED single-flight job (`internal/web/storage_init_job.go`,
the `netAddState` shape) on `context.Background()`; `runStorageInit` gained a nil-safe phase callback
(formatting → mounting → registering). The wizard polls `GET /api/storage/init/status` and renders
the 3-step progress (`storage_init.html`); confirm/refuse verdicts surface through the same poll.
Register is the LAST step (marker-last, Scenario B) + idempotent prior steps (`AddStoragePath`
dedups) → a crash leaves at most an unregistered orphan, never a broken/duplicate registration.
- **Deeper half (found on the live leg):** a slow mkfs (a 64 GB USB) outruns the agentapi client's
15 s `Timeout`; the agent runs it DETACHED and records the job, so `runStorageInit` now POLLS the
agent's `GET /disks/format/status` (new `agentapi.Client.FormatStatus``awaitAgentFormat`) to the
terminal outcome on a client timeout, then continues to mount+register (the F6 root-cause's "mkfs
continues detached; poll the status").
- **Red-proofs (run-fail-revert):** `TestStorageInit_DetachedSurvivesClientDisconnect` (pre-fix
cancelled-ctx chain fails at mount, NOT registered → FAIL; fixed: detached job registers exactly
once) + `TestStorageInit_PollsAgentFormatStatusOnTimeout` (timeout→done registers; timeout→failed
surfaces the error).
### F7 — Vissza back-routes
The "Vissza" (Back) anchor on `/storage/init` and `/storage/attach` now routes to `/storage` (was
`/settings`); the init success link also points to `/storage`. Test
`TestStorageWizardBackAnchors_PointToStorage`.
### Live validation (F6 Scenario A, on the demo scratch USB)
Method: the REAL controller endpoints (curl against the in-guest controller with the dashboard Host
header + a login session + the CSRF token — the exact calls the UI makes; browser tool not used).
Scratch device `/dev/sdd` (serial `20120926571200000`, 59.5 GB) serial-checked against the enrolled
list FIRST (≠ the enrolled `/dev/sdb` hdd_1; ≠ OS `/dev/sda`) and confirmed present in the
agent-proven `initialize` candidates. Flow: init (no confirm) → `needs_confirmation` (ext4) via poll →
init (confirmed) → `{started}` (returns immediately; the client can leave) → detached job
`formatting` (~27 s mkfs, past the 15 s client timeout → the status-poll fix engaged) → `mounting`
**`done`** at `/mnt/felhom-drives/scratch1`. Verified: `/dev/sdd` mounted at the enrolled stable bind
`/mnt/felhom-drives/scratch1` (`/dev/sdd[/felhom-data]`); the enrolled hdd_1 untouched.
### Security-review acknowledgment (background review of the commit)
Flagged a TOCTOU in `runStorageInit` (format → `resolveEnrollUUID(path)``AssignDisk(uuid)`).
**Assessment:** pre-existing (unchanged sequence — F6 only moved it off the request context and added
the timeout status-poll, which widened the window by the poll interval). The DESTRUCTIVE step (format)
is the authorization boundary and is agent-enforced with anti-retarget **durable-id** binding — the
controller cannot make the agent format the wrong device; the subsequent mount is benign and keyed by
**fs-UUID** (a moved /dev node resolves to a non-existent UUID → a mount failure, not data loss or an
authz bypass). No new authorization bypass. Left as an Observation (not expanded this batch per the
minimal-changes mandate); a future hardening could bind resolve+assign to the format's durable-id.
### Operator sidebar (Viktor — CC does NOT do these; recorded verbatim from the spec)
1. **BIOS "State After G3" → "Power On"/"Last State"** before final sign-off — also added to the
hardware-prep notes in `scripts/iso/README.md`.
2. **campaign6 autofs orphan** — clears at the next host reboot.
3. **tied-CreatedAt flash duplicates** (audiobookshelf/komga/romm) — standing action item.
### NOT live this task
F5 (guest auto-sizing) is validated by harness only; live at the next from-scratch rehearsal
(Viktor's planned delete/re-create run) — see felhom.eu REPORT.