v0.60.0: proof-of-launch destroy gating (F1a/b/c) + restore-test band-advance (F2)

Campaign pool-effects F1 (HIGH): the bring-up compensating rollback and the
restore-test teardown destroyed the target vmid even when RestoreLXC failed
synchronously without creating anything — destroying a guest the transaction
never made (only the pool ACL 403 contained it). A RestoreLXC UPID is now the
sole destroy authorization in all three destroy paths (in-process bring-up
defer, in-process restore-test teardown, Recover). F2: the restore-test
advances past an 'already exists' band vmid (invisible squatter) instead of
failing + false-alerting; a fully-occupied band Skips.

Red-proof verified: with the gates reverted, the four new tests fail with the
innocent-guest destroy. go build/vet/test clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-02 10:18:51 +02:00
parent 55ade9e254
commit b9356d60ab
8 changed files with 400 additions and 67 deletions
+36
View File
@@ -1,3 +1,39 @@
## v0.60.0 — proof-of-launch destroy gating + restore-test band-advance (campaign F1/F2) (2026-07-02)
Fixes the pool-effects campaign's HIGH finding (F1, `CAMPAIGN-pool-effects-2026-07-01.md`): the bring-up
compensating rollback and the restore-test teardown fired `DestroyLXC` on the target vmid even when
`RestoreLXC` failed synchronously WITHOUT creating anything (PVE refusing a pre-existing vmid the
pool-blind duplicate guard / band scan couldn't see) — destroying a guest the transaction never made.
Only the pool ACL's 403 saved the non-pool subset; an in-pool pre-existing guest would have been
destroyed, and any broad-token deployment re-arms the bug. Root cause: SameTxnCreated/scratch provenance
was ASSUMED, never verified against proof-of-launch. The fix makes a `RestoreLXC` UPID the sole destroy
authorization, in ALL THREE destroy paths — the pool ACL is defense-in-depth again, not the guard.
- **F1a `internal/reconcile/bringup.go` `runBringUp`:** the compensating-rollback defer is gated on
`launched` (set only after the restore POST is accepted). A synchronous restore failure (no UPID)
closes the owning entry terminal-failed WITHOUT any destroy. The pre-restore `OpStarted` journal
append is kept (crash-safety); `rollbackBringUp` is now only ever called launch-proven.
- **F1b `internal/reconcile/restoretest.go` `runScratchTest`:** same `launched` gate on
`teardownScratch` — a synchronous restore refusal never destroys the picked band vmid.
- **F1c `internal/reconcile/recover.go` `Recover`:** the no-UPID "POST never confirmed → abandon
fail-safe" check now runs BEFORE the Scratch/Rollback dispatch — a no-UPID Scratch/Rollback entry is
abandoned (marked failed, NO destroy) instead of destroy-by-vmid-existence. Recover is now safe by
DESIGN, not by the pool-blind "already gone" accident the campaign observed.
- **F2 `restoretest.go` `RunRestoreTest` band-advance:** a band vmid PVE refuses with "already exists"
(an invisible squatter — new `pveAlreadyExists`, mirrors `pveConfigLock`, never misclassifies a real
restore failure) is skipped and the next free band vmid tried (bounded by the band width;
`pickScratchVMID` gained an exclude set). A fully-occupied band → `Skipped` (scheduler raises no
"backup unrestorable" alert), never FAIL — one squatter no longer permanently breaks the restore-test.
- **Accepted residual (by design):** a crash in the one-statement window between obtaining the UPID and
journaling it leaks a half-built guest Recover won't destroy — cleanable, and vastly preferable to
destroying an innocent guest.
- Tests: red-proof companions verified (gates reverted → `TestRunBringUp_NoLaunchNoDestroy`,
`TestRunRestoreTest_RestoreNoLaunchNoTeardown`, `TestRecover_{BringUp,Scratch}NoUPIDAbandoned` all
fail with the innocent-guest destroy); no-regression `…LaunchedTaskFailureStillTearsDown` + rollback
table now includes an explicit restore-task-failure case; F2 advance + squatter-full-band-skips tests.
Live-validated on felhom-pve (provision onto existing 9001 → no destroy armed; restore-test advances
past a 990000 decoy). `go build/vet/test ./...` clean.
## v0.59.0 — report backing device + capacity for a registry-sourced drive in /disks (2026-07-01)
Completes the `/disks` representation for a registry-sourced (raw, no-PVE-storage) drive: the agent-view