v0.6.0-rc1: slice 6 Phase A — backup + the self-restore-test (local target)

The guest-level backup layer + the journaled self-restore-test (restore→boot→verify→
teardown) that closes "a backup you haven't restored isn't a backup". All benign
(reuses the slice-4 classifier/gate/journal; no new destructive class/crypto). Local
target only; PBS = Phase B. Restore to a NEW guest only. Backups crash-consistent.

- proxmox: DestroyLXC, VzdumpOptions.Notes (notes-template), LatestBackupVolID.
- reconcile: Engine.RunRestoreTest (journal Scratch entry BEFORE mutation; net link-down
  pre-boot; defer teardown always; benign gated destroy) + Recover extended to reap a
  leaked scratch guest (Scratch flag, special-cased before the UPID path; idempotent).
- internal/backup: runner (vzdump + archive resolve + bulk-gap = backup!=1) + cadence
  scheduler (4th daemon goroutine, default 24h) + in-memory report store.
- hub: Backup/RestoreTest filled; collector seams; cross-repo golden byte-identical +
  bidirectional key-set tests; hub handler logs a FAILED restore-test prominently.
- config BackupConfig (band 990000-990009 default); --selftest=backup / restore-test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-09 13:49:39 +02:00
parent e548ab57fe
commit b527430ec7
23 changed files with 1727 additions and 46 deletions
+51
View File
@@ -3,6 +3,57 @@
All notable changes to **felhom-agent** are recorded here. Update on every code
change that gets pushed.
## v0.6.0-rc1 — slice 6 Phase A: backup + the self-restore-test (local target) (2026-06-09)
Phase A of the backup/restore slice (doc 03 §8) — the agent's guest-level backup layer and
the **self-restore-test**, which closes "a backup you haven't restored isn't a backup".
Everything here is BENIGN (backup, restore-to-NEW, scratch teardown): reuses the slice-4
classifier/gate/journal — no new destructive class, no new crypto. Local target only; PBS is
Phase B. Restore is to a NEW guest only (no overwrite). Backups are crash-consistent only
(app-consistency needs the controller quiesce, slice 8) — marked so in the report.
### Added
- **proxmox** (`mutate.go`/`query.go`): `DestroyLXC` (DELETE …/lxc/{vmid}?purge=1&destroy-
unreferenced-disks=1 → UPID; the scratch-teardown primitive); `VzdumpOptions.Notes`
`notes-template` (verified on PVE 9.2.2); `LatestBackupVolID` (resolve a produced archive
from the backup-storage listing — the task status carries no result volid).
- **reconcile self-restore-test** (`restoretest.go`) — `Engine.RunRestoreTest`: pick a free
scratch VMID (configured band, excludes 9999; full band → skip, never out-of-band) →
**journal a Scratch-owned entry BEFORE any mutation** → restore-to-new → benign net
**link-down** SetConfig (so the clone can't conflict with a running source's MAC/IP; this
is test-safety, NOT slice-7 identity reset) → boot → verify **reaches `running`** → ALWAYS
teardown (defer; benign `ClassGuestDestroy` + agent-tagged-scratch provenance, gated). Runs
on the scratch VMID's queue lane. Reuses the journal/gate; result feeds the report.
- **Crash-safe recovery** (`recover.go`): a Scratch journal entry is resolved by TEARDOWN,
not by re-checking the restore sub-task's UPID — special-cased BEFORE the generic path
(else the restore task's OK would mark it succeeded while the guest leaks). `Recover` now
destroys a leaked scratch guest (idempotent: already-gone → clean; list-unreadable → left
in-flight for a later pass). `JournalEntry.Scratch` flag; `RecoverResult.ScratchClean/
ScratchDestroyed`. GuestAPI gains `RestoreLXC`/`DestroyLXC`/`GuestStatus`.
- **`internal/backup` package**: `BackupRunner.Backup` (vzdump + archive/size resolve +
bulk-volume gap — a mountpoint is UNCOVERED unless it carries an explicit `backup=1`, so
an unset `backup=` is reported uncovered too, the safe DR direction); `PickRestoreCandidate`
(newest backup); an in-memory `Store` (latest-backup-per-target + latest-restore-test)
implementing the hub `BackupReporter`/`RestoreTestReporter` seams; a cadence `Scheduler`
(default 24h; the fourth daemon goroutine; disabled cleanly when off/misconfigured).
- **hub report** (`report.go`): filled the `Backup` + `RestoreTest` stubs (`PBSSnapshot`
stays a Phase-B stub); collector `BackupReporter`/`RestoreTestReporter` seams. Cross-repo
golden updated in BOTH repos (byte-identical) + bidirectional key-set tests for
`backups[0]`/`restore_tests[0]`. Hub `handler.go` parses + persists them (report_json; no
new columns) and logs a **FAILED restore-test prominently** (the loudest DR signal).
- **config** `BackupConfig` (local target, restore storage, restore-test cadence, scratch
VMID band 990000990009 default) + accessors + env overlay + cadence-gated validation.
- **`--selftest=backup -vmid N`** (one-shot backup → print the Backup record) and
**`--selftest=restore-test [-archive volid]`** (Recover-then restore→boot→verify→teardown,
print the RestoreTest record). Standalone on the Proxmox host.
### Notes
- The daemon runs cleanly with the cadence off or misconfigured (logs + disables, never
crashes); a leaked scratch guest from a mid-test crash is reaped by `engine.Recover` on
restart. `go test -race` covers the new scheduler goroutine.
- Slice-3/4/5 exported surfaces, goldens, and adversarial tests intact. Version bumps to
**v0.6.0** when Phase B (PBS) lands.
## v0.5.1 — slice 5 live-validation prep: durable_id mis-id fix + re-mount UUID memory (2026-06-09)
Two correctness fixes surfaced while preparing the live USB validation on `demo-felhom`