GL-5: DR restore passes the FULL archive layout (live finding #2)

The live scratch DR exposed the second half of PVE's all-or-nothing
explicit-params restore: mountpoints NOT named in the params are silently
DROPPED - the DR guest came up without its mp0/mp1 data volumes (boot
passed; the customer's world did not ride along). drRestoreOverrides now
derives the COMPLETE param set from the archive's extracted config:
explicit rootfs, every storage-backed mpN passed through (size + in-guest
path + backup flag preserved so vzrestore extracts its content), the two
structural binds replaced by 4d-swapped throwaways; unknown bind mpN or
unparseable size refuses loudly. Snapshot sections never shadow the
current config.

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-08 09:10:54 +02:00
parent 3bf0110697
commit b3446213df
3 changed files with 154 additions and 33 deletions
+40
View File
@@ -1,3 +1,43 @@
## v0.75.0 — DR bring-up structural bind overrides + real-bind swap (GL-5 / go-live G8) (2026-07-08)
Implements the verdict of `felhom.eu/documentation/audits/SPIKE-dr-bindmount-source-2026-07-07.md`:
`bring-up -mode dr` of a customer archive FAILED outright because the archive carries the two
structural host-bind mountpoints (mp8 parent bind, mp9 bootstrap bind) that a `pct restore` under
the privsep token cannot recreate ("restoring 'mp8' to bind mount is only possible for root") —
bring-up passed no `MountOverrides`. Provision was never affected (the golden has no mp8/mp9; the
back-half adds them) — that asymmetry was the bug.
- **DR restore overrides** (`internal/reconcile/bringup.go`): ModeDRGuestLoss synthesizes throwaway
1G-volume overrides for the two PLATFORM-CONSTANT mpN (mirroring backhalf.go's values; the spike's
whole point — no archive parse for the LAYOUT) via the shared `throwawayVolumeOverride` format
helper (extracted from `bindMountOverrides`; the restore-test's is-a-bind FILTER reads live
configs, which DR by definition has none of). ModeProvision passes nil — regression-contract test.
- **LIVE-DISCOVERED PVE constraint (not in the spike — it never ran an override restore):** mpN
restore params without an explicit `rootfs` → HTTP 500 "mount points configured, but 'rootfs' not
set" (same rule restoretest.go:211 documents for the live-config path). Fixed with NEW
`Client.ExtractArchiveConfig` (GET `/nodes/{node}/vzdump/extractconfig` — **answers 200 under the
scoped agent token**, verified live; PBS keys stay server-side, the spike's candidate-1 rejection
holds): the archive's OWN embedded config supplies the rootfs SIZE only; unreadable/unparseable →
clean refusal before any restore.
- **Step 4d — real-bind swap** (DR only, pre-start): mp9 bootstrap host dir created (idempotent —
a same-host guest-loss still has bootstrap.json there, untouched), then mp8/mp9 set to the REAL
binds via the host runner (`pct set` — bind mounts are root@pam-only, hence NOT the API; sudoers
already allowlists both shapes), one slot per call so a failure names the exact mpN and rolls
back per the committed/launched envelope (C2 — never a silent half-wired success). The displaced
throwaway volumes (PVE parks them as `unusedN`) are deleted via one config PUT (`delete=`);
a scoped-token refusal logs the residue LOUDLY + warns in the result instead of widening
privileges. NEW `proxmox.GuestConfig.Unused()`.
- **Engine seam:** `EngineOptions.HostRunner` + `StateDir` (DR refuses up front on an API-only
engine); the bring-up selftest wires the same ExecRunner shape as the back-half and removes the
scratch vmid's mp9 host dir at teardown (never a real drive's bind source).
- Tests (non-hollow): Scenario A exact-override + exact-swap-command + unusedN-delete asserts;
provision-nil regression; C2 mid-swap rollback; C3 older-archive-without-mp9; DR-without-runner
refusal; extract-failure refusal; 403-residue warn; archive rootfs parse (snapshot sections never
shadow). Red-proofs: override synthesis reverted → A FAILS; unconditional overrides → B FAILS
(both run→fail→revert).
- Live validation (campaign-2-precedent scratch DR, §REPORT): see REPORT.md — the exact op that
failed on v0.74.0 now round-trips with correctly-wired binds and no residue.
## 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