Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
3.6 KiB
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
mainwas8e6d00a= v0.31.0 → shipped v0.32.0 (commitf43697c).
What shipped
POST /disks/decommission(internal/localapi/disks.gohandleDiskDecommission, route inserver.go) — self-serve, NO operator signature, NON-destructive. MirrorshandleDiskEjectexactly:withGuestself-scoping,scopedFromBody, and the same user-data role gate (roleForMountPathmust beRoleUserData, else 403; fail-safe-to-protected). It records a PERMANENTIntentDecommissioned, prunes theGuestBindStoreentry, 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.ReassertGuestBindsis 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 onIntentDecommissioned).GuestBindStore.Remove(vmid, durableID)— idempotent (absent = no-op), atomic tmp+rename; drops the vmid key when its set empties. Re-enroll re-Records via the existingrecordGuestBind, so Remove doesn't break re-commission.IntentRecorderextended withSetDecommissioned+Get. The operator-signedDecommissionExecutor+reconcile.Classifyclassification 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,--version0.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 inguest-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.