# REPORT — self-serve decommission + intent-aware re-assert → agent v0.32.0 (2026-06-14) Agent half of TASK B2 (the controller half ships as felhom-controller v0.65.0). Implemented trunk-based on `main` against the locked spike design (`felhom.eu/documentation/audits/SPIKE-decommission-migration-2026-06-14.md`). Built on 192.168.0.180, deployed + restarted on demo host `felhom-pve`. > Validate against pushed source, not this report. ## Baseline → shipped - `main` was `8e6d00a` = **v0.31.0** → shipped **v0.32.0** (commit `f43697c`). ## What shipped - **`POST /disks/decommission`** (`internal/localapi/disks.go` `handleDiskDecommission`, route in `server.go`) — self-serve, NO operator signature, NON-destructive. Mirrors `handleDiskEject` exactly: `withGuest` self-scoping, `scopedFromBody`, and the same **user-data role gate** (`roleForMountPath` must be `RoleUserData`, else 403; fail-safe-to-protected). It records a PERMANENT `IntentDecommissioned`, prunes the `GuestBindStore` entry, and unmounts. It **NEVER** calls any format/mkfs path — the data stays on the drive. A compromised controller gains nothing beyond eject/customer-confirmed-wipe; the hub can't reach the local API at all. - **`ReassertGuestBinds` is now intent-aware** (the load-bearing F9-reconnect correctness fix): the startup re-assert skips any durable-id whose intent ≠ `enrolled`, so a decommissioned- (or ejected-) but-still-present drive is never auto-rebound into the guest on agent restart. Nil intent store → legacy bind-all (matches the watchdog's nil-intent rule). Covers both the self-serve and the operator-signed decommission paths (both land on `IntentDecommissioned`). - **`GuestBindStore.Remove(vmid, durableID)`** — idempotent (absent = no-op), atomic tmp+rename; drops the vmid key when its set empties. Re-enroll re-`Record`s via the existing `recordGuestBind`, so Remove doesn't break re-commission. - `IntentRecorder` extended with `SetDecommissioned` + `Get`. The operator-signed `DecommissionExecutor` + `reconcile.Classify` classification are **untouched** (the absent-drive/DR route). ## Tests (`go build && go vet && go test ./...` green) `internal/localapi/decommission_test.go`: role-gate refuses system/backup (403, no unmount); decommission sets intent + removes the bind + unmounts + never formats (all three asserted); **intent-aware re-assert does NOT rebind a decommissioned-but-present drive** (companion: enrolled DOES rebind — and the guard was mutation-proven: removing it makes the test FAIL with the drive re-bound); re-commission re-records; `GuestBindStore.Remove` idempotency + persistence. ## Deploy / verify - Built v0.32.0 on 192.168.0.180 (`go build -ldflags "-X main.version=0.32.0"`); installed on felhom-pve at `/usr/local/bin/felhom-agent` (prior backed up to `.bak-0.31.0`); `systemctl restart felhom-agent` → **active**, `--version` 0.32.0. - **Live-proven on restart:** the new intent-aware re-assert logged `F9 re-assert: skipping non-enrolled drive (intent-gated) vmid=9201 durable_id=uuid:ce9d1d81-… intent=ejected` — the exact drive that lingered in `guest-binds.json` (B0 spike finding) is now correctly skipped (it would have been re-bound on the pre-fix code). Clean startup, no errors. ## NOT yet live-validated — awaiting supervised B3 The endpoint's full effect via the real UI flow (customer decommission → drive un-enrolled → does not auto-mount/re-bind on physical reconnect, with real data on the drive) is deferred to the operator-present B3 session. The intent-gate itself is already proven live by the restart log above.