Files
felhom-agent/REPORT.md
T
admin 8abc1b8852 agent v0.38.0: DR recipe — emit secret-free storage/guest/PBS half in host-report
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>
2026-06-16 18:32:15 +02:00

57 lines
3.2 KiB
Markdown

# 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.