feat(reconcile): re-assert pool membership after restore-over-existing (campaign-2 R2, v0.74.0)
Pool membership is what lets the pool-scoped token reach a guest; pct restore --pool sets it only at CREATE, so a restore over an existing VMID drops the guest from the felhom pool and 403s the next restore-test/DR on VM.Audit. This empty-pool state is the true root cause of the campaign's "R1" (bind-mount restore failing was a symptom — restore-test's existing bind neutralization never ran without config-read). Add Client.PoolAddVMID (PUT /pools, additive+idempotent, Pool.Allocate) and call it in bring-up after liveness when spec.Pool!="" — warn-not-fail on a hiccup (liveness wins). B3 scratch-teardown 403 diagnosed as a cascade (restoretest already passes Pool). Role/ACL untouched. Tests + red-proof. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
This commit is contained in:
@@ -1,3 +1,29 @@
|
||||
## v0.74.0 — pool membership re-asserted after restore-over-existing (campaign-2 R2) (2026-07-07)
|
||||
|
||||
Closes campaign-2 finding **R2** (`felhom.eu/documentation/tests/CAMPAIGN-2-2026-07-07.md`). Pool
|
||||
membership is what lets the pool-scoped `FelhomAgentGuest` token reach a guest (the grant applies only
|
||||
to pool MEMBERS). `pct restore --pool` sets membership at CREATE, but a restore **over an existing
|
||||
VMID** (the host-loss/finale path) never re-applies it — and no code re-added a guest to the pool — so
|
||||
every destroy-restore silently dropped membership, and the NEXT `--selftest=restore-test`/DR 403'd on
|
||||
`VM.Audit`/`VM.Allocate`. That empty-pool state is the true root cause of the campaign's "R1" (the
|
||||
bind-mount restore failure was a symptom: with no config-read, restore-test's *existing, correct*
|
||||
bind-mount neutralization never ran).
|
||||
|
||||
- **`Client.PoolAddVMID(ctx, pool, vmid)`** (`internal/proxmox/mutate.go`): `PUT /pools/{pool}`
|
||||
`vms={vmid}` — PVE-additive (merge, not replace), idempotent (already-member swallowed), needs
|
||||
`Pool.Allocate` (the token has it). Sync — no UPID.
|
||||
- **bring-up re-asserts** (`internal/reconcile/bringup.go`): after liveness is proven, if `spec.Pool
|
||||
!= ""`, call `PoolAddVMID`. A pool-add hiccup is surfaced as a LOUD warning + `res.StartWarnings`
|
||||
but must NOT flip a healthy running guest's verdict (membership matters for the NEXT op).
|
||||
- **B3 (scratch teardown 403) — diagnosed, no code:** `restoretest.go` already passes
|
||||
`Pool: DefaultPool` for scratch restores, so the campaign's `VM.Allocate` teardown 403 was a
|
||||
CASCADE of the bind-mount restore failing (half-built guest outside any pool), not an independent
|
||||
gap. A re-run on the healed pool confirms.
|
||||
- Tests: `PoolAddVMID` PUT shape + idempotency + real-error-surfaces + validation (`pool_test.go`);
|
||||
bring-up re-asserts when `Pool!=""` (red-proof: pre-fix no-call → FAIL, demonstrated), no-pool→no-call,
|
||||
pool-add failure warns-but-passes (liveness wins, guest kept). Role/ACL untouched — the fault was
|
||||
membership, not privileges.
|
||||
|
||||
## v0.73.0 — F2 mount-role fallback: enrolled user-data drives are ejectable/decommissionable again (2026-07-06)
|
||||
|
||||
Closes campaign finding **F2** (`felhom.eu/documentation/audits/CAMPAIGN-nomercy-2026-07-06.md` +
|
||||
|
||||
Reference in New Issue
Block a user