slice 7 Phase 1: unified bring-up reconcile job (provision + guest-loss DR) (v0.8.0)

The shared front half of provision and guest-loss DR as a journaled reconcile job
(internal/reconcile/bringup.go), mirroring the restore-test's crash-safety but keeping
the guest on success and applying a scenario-specific identity policy. Agent-only; no
hub/wire change. Grounded by the slice-7 bring-up spike (commit 3342993): F1/F3/F4.

- RunBringUp: restore -> reset identity -> size -> attach mounts -> start link-up;
  verdict is liveness (waitRunning), success KEEPS the guest.
- identity policy: provision = fresh MAC (net0 sans hwaddr -> PVE regen) + hostname,
  host-side; machine-id/host-keys regenerate guest-side (systemd + baked golden unit).
  dr_guest_loss = preserve continuity (keep hostname; keep MAC unless KeepMAC=false).
- compensating rollback: mid-flight failure destroys the just-created guest
  (SameTxnCreated provenance, gated); new Rollback journal flag + Recover.recoverBringUp
  reap a half-built guest from a crash.
- F4: coalesced config PUT + bounded retry on the transient PVE config-lock 500 only.
- --selftest=bring-up (mode/archive/vmid/hostname/keep).
- configs/build-golden.sh: validated golden recipe incl. the F3 first-boot host-key unit.
- doc-03 §9 + identity-reset settled/implemented.

Deferred (stated): provisioning back half -> slice 8; host-loss DR + escrow consumption
and the BringUpSpec source (hub desired-state) -> slice 10.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-09 21:27:49 +02:00
parent 9f6753de0f
commit 57405c1a99
10 changed files with 1184 additions and 73 deletions
+12
View File
@@ -31,6 +31,11 @@ type fakeAPI struct {
statusFunc func(upid string) (proxmox.TaskStatus, error)
// logTailFunc backs TaskLogTail (restore-test start-warning surfacing); default = empty.
logTailFunc func(upid string) ([]string, error)
// setFunc, when set, backs SetConfig (drives the F4 lock-500-then-200 test).
setFunc func(vmid int, params map[string]string) (string, error)
// restoreHook, when set, fires inside RestoreLXC (used to assert the owning journal entry
// is written BEFORE the restore — crash-safety ordering).
restoreHook func()
starts []int
stops []int
@@ -48,6 +53,9 @@ type resizeCall struct {
}
func (f *fakeAPI) RestoreLXC(_ context.Context, opts proxmox.RestoreLXCOptions) (string, error) {
if f.restoreHook != nil {
f.restoreHook()
}
f.mu.Lock()
f.restores = append(f.restores, opts)
f.mu.Unlock()
@@ -121,7 +129,11 @@ func (f *fakeAPI) Stop(_ context.Context, vmid int) (string, error) {
func (f *fakeAPI) SetConfig(_ context.Context, vmid int, params map[string]string) (string, error) {
f.mu.Lock()
f.sets = append(f.sets, setCall{vmid, params})
fn := f.setFunc
f.mu.Unlock()
if fn != nil {
return fn(vmid, params)
}
return f.setUPID, f.setErr
}