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

52 lines
3.6 KiB
Markdown

# 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.