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:
2026-07-07 18:42:19 +02:00
parent e04b75e1f8
commit ca0b169a4e
8 changed files with 278 additions and 0 deletions
+3
View File
@@ -172,6 +172,9 @@ type GuestAPI interface {
ResizeLXC(ctx context.Context, vmid int, disk, size string) (string, error)
// RestoreLXC restores an archive into a (fresh) vmid — the create path (slice 6). Async → UPID.
RestoreLXC(ctx context.Context, opts proxmox.RestoreLXCOptions) (string, error)
// PoolAddVMID re-asserts pool membership after a restore-over-existing (campaign-2 R2). Sync (no
// UPID); idempotent. Membership is what lets the pool-scoped token reach the guest next time.
PoolAddVMID(ctx context.Context, pool string, vmid int) error
// DestroyLXC destroys a guest — the scratch-teardown primitive (slice 6). Async → UPID.
// Destructive-class; the engine only ever issues it for an agent-tagged scratch guest
// (benign by provenance) via the gate.