# 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 pure `BuildDRRecipeHostHalf(guests, targets, pbs)`: - `guests[]` ← each `Guest.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}` (rebuild `storage.cfg`). - `pbs` ← the latest snapshot's `{repo_id (the pbs storage id), namespace, latest_snapshot_id}`. - `HostReport.DRRecipe *DRRecipeHostHalf json:"dr_recipe"` (always set in `Collect()`, 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 → `pbs` omitted, 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_recipe` key-set + sub-array element key-sets added to `TestHostReport_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.