76f9cb5977
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
52 lines
3.6 KiB
Markdown
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.
|