DR recipe slice (agent half), grounded in SPIKE-dr-recipe-2026-06-16. Additive `dr_recipe` host-report section = the non-secret reconstruction scaffolding the operator must rebuild before PBS bytes can land. Built by pure BuildDRRecipeHostHalf from facts the report already collects (no new reads): guests[] sizing, drives[] (user-data by durable_id/role/mount/intent), pve_storage[] (storage.cfg), pbs coordinates. BOUNDARY (Phase-1 lesson): every field is an identifier/intent/size/coordinate — never a key/password/token/hash/ENC:. PBS key stays in escrow; restic password stays in escrow; the recipe names only the coordinates the restore targets. Tests: BuildDRRecipeHostHalf selection, NoPBS, NoSecrets (boundary mirror), dr_recipe key-set in the cross-repo golden contract test. recipe_version=1, ignore-unknown on read. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
3.2 KiB
REPORT — felhom-agent v0.38.0 (DR recipe: agent storage/guest/PBS half)
TASK — DR recipe slice (agent half). Emit the secret-free reconstruction-recipe scaffolding the
agent already owns, as an additive host-report section. Grounded in SPIKE-dr-recipe-2026-06-16.md.
What
The recipe is the non-secret re-provision plan that complements escrow (keys) and PBS/restic
(bytes): guest sizing, drive inventory (durable-id → role → mount → intent), PVE storage defs, and PBS
coordinates — the host/guest/storage scaffolding the operator must rebuild on new hardware before the
PBS bytes can land. The agent owns every fact (StorageTarget, GuestSpec, PBSSnapshot), so it emits
its half additively in the existing host-report; the hub assembles it with the controller's app half.
Implementation
internal/hub/dr_recipe.go— wire types + the pureBuildDRRecipeHostHalf(guests, targets, pbs):guests[]← eachGuest.Spec(cores/memory/disk), skipping status-unknown guests.drives[]← user-data external drives only (isUserDataDrive: usb/local-dir with a non-empty durable-id + mount path) →{durable_id, role, mount_path, intent=enrolled, total_bytes}.pve_storage[]← every storage target{name, type, content}(rebuildstorage.cfg).pbs← the latest snapshot's{repo_id (the pbs storage id), namespace, latest_snapshot_id}.
HostReport.DRRecipe *DRRecipeHostHalf json:"dr_recipe"(always set inCollect(), never null).- No new reads — derived from the just-collected report facts.
Boundary (non-negotiable)
Every field is an identifier / intent / size / coordinate. The PBS key stays in escrow, the access
token in identity-escrow, the restic password in escrow — the recipe names only the coordinates
the restore targets. This is the exact axis the retired infra-backup violated (it shipped
encryption_key_b64/restic_password/cf_api_token).
Tests (non-hollow)
TestBuildDRRecipeHostHalf— drives = only user-data; pve_storage = all targets; pbs = latest snapshot; guests skip nil-spec.TestBuildDRRecipeHostHalf_NoPBS— no snapshots →pbsomitted, slices non-nil.TestDRRecipeHostHalf_NoSecrets— the boundary mirror: serialized half has NO key matching(?i)(password|secret|token|hash|passphrase|api[_-]?key|\bkey\b|enc:). (The load-bearing boundary test, with a synthetic-secret app + allowlist red-proof, lives on the controller emitter.)dr_recipekey-set + sub-array element key-sets added toTestHostReport_ContractMatchesGolden(the cross-repo golden, byte-pinned with the hub's copy).
Versioning
recipe_version=1, carried in the section. Read is ignore-unknown (encoding/json default) for
forward-compat, mirroring storage_manifest. Golden discipline: host-report.golden.json here must stay
byte-identical to the hub's copy — manual checksum-diff on any wire change (the golden spans three repos).
Gate
go build, go vet, go test ./... all green (local + build server). Deployed to felhom-pve.
Deferred (NOT in this slice)
The agent-side restore_directive consumption / recovery-mode execution (syncer.go:92) — slice-10D.
This slice only EMITS the recipe.