Files
felhom-agent/REPORT.md
T
2026-06-14 20:18:01 +02:00

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

  • 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-Records 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-agentactive, --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.