REPORT: hub v0.13.1 DR-recipe v1 drive-shape sync — live-validated assembly, zero secrets
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -4,61 +4,44 @@
|
||||
|
||||
---
|
||||
|
||||
## REPORT — hub v0.13.0: DR recipe (assemble + store + view)
|
||||
## REPORT — hub v0.13.1: DR recipe v1 drive-shape sync (test-data + regression guard only)
|
||||
|
||||
**TASK — DR recipe slice (hub half).** Receive the two secret-free recipe halves, assemble them into one
|
||||
customer recipe, store PLAINTEXT in a dedicated table, and expose an operator view/download. Grounded in
|
||||
`documentation/audits/SPIKE-dr-recipe-2026-06-16.md`. Pairs with felhom-agent v0.38.0 (storage/guest/PBS
|
||||
half) + felhom-controller v0.73.0 (customer/apps half, the boundary-enforcing emitter).
|
||||
**TASK — DR-recipe completion (hub side of a cross-repo change).** The agent's **v0.39.0** dropped two
|
||||
fields from the host-half `drives[]` (`role`, `restic_repo_coord`) and now resolves the `pbs` coord live.
|
||||
The hub change is **test-data + a regression guard only — NO behavior change; redeploy optional**, because
|
||||
the hub already reads drive sections as `json.RawMessage` (verbatim passthrough): dropping fields needs no
|
||||
store/handler/struct change.
|
||||
|
||||
### Store (`hub/internal/store/dr_recipe.go`)
|
||||
**Baseline:** v0.13.0 (`5f5e3c5`) → **v0.13.1** (`149a3b0`, pushed to `main`).
|
||||
|
||||
- New `dr_recipe` table (migrate()) — DEDICATED, separate from the opaque `host_escrow` and from the
|
||||
dropped `infra_backup*` tables. Keyed by `customer_id`; columns `recipe_version, host_id,
|
||||
host_half_json, app_half_json, updated_at`.
|
||||
- `SaveDRRecipeHostHalf` / `SaveDRRecipeAppHalf` — each upserts its half and PRESERVES the other
|
||||
(last-write-wins per half; a re-report of one half never clobbers the other).
|
||||
- `AssembleDRRecipe(rec)` — stitches the two halves into `AssembledRecipe{recipe_version, customer,
|
||||
guests, pbs, drives, pve_storage, apps}`. Sub-sections pass through as `json.RawMessage` (verbatim);
|
||||
**ignore-unknown** at the top level + version-skew tolerant (`recipe_version` = max) for forward-compat
|
||||
across the three repos. Pure → unit-tested.
|
||||
### Changes
|
||||
- `internal/store/testdata/dr-recipe.golden.json` + the `drHostHalf` test fixture — dropped the `role`
|
||||
key from `drives[0]` to match the v1 shape the agent emits.
|
||||
- `internal/api/testdata/host-report.golden.json` — re-synced **byte-identical** with the agent's
|
||||
`internal/hub/testdata/host-report.golden.json` (sha256 `57f2a5e7…18b2f2b5`). The hub copy previously
|
||||
lacked the `dr_recipe` section entirely; it is now a verbatim copy, so the cross-repo golden truly
|
||||
matches and POSTing it through `/host-report` exercises the `SaveDRRecipeHostHalf` ingest path.
|
||||
- New `internal/store/dr_recipe_test.go::TestAssembleDRRecipe_V1DriveShape` — the regression guard: a
|
||||
stored host half whose `drives[]` carry NEITHER dropped field but WHICH HAS a `pbs` block assembles
|
||||
cleanly (pbs carried through, drives passed through verbatim, neither `role` nor `restic_repo_coord`
|
||||
present). Demonstrated to FAIL when the fixture re-adds `role`, then reverted.
|
||||
|
||||
### Ingest (`hub/internal/api/handler.go`)
|
||||
### Verification
|
||||
- `go build ./... && go vet ./... && go test ./...` — **green**.
|
||||
- **Live (the real acceptance):** after deploying the daemon (agent v0.39.0) and its startup host-report
|
||||
landing in the hub DB, the **real `AssembleDRRecipe`** over a copy of the live `/data/hub.db` produced
|
||||
the `demo-felhom` recipe — `recipe_version 1`, 3 guests w/ sizing, **`pbs` present** (`felhom-pbs/root/9201`),
|
||||
**2 drives clean** (no `role`/`restic_repo_coord`), 5 pve_storage, **14 apps**. **Secret scan over the
|
||||
full assembled recipe AND both raw stored halves = 0 hits** (nothing to redact — the point of the arc).
|
||||
(Operator HTML panel not browser-rendered: no dashboard password in this env; the server pipeline +
|
||||
exact stored/assembled bytes were verified.)
|
||||
|
||||
- `handleHostReport` persists the `dr_recipe` host-half (keyed by the host's customer).
|
||||
- `handleReport` persists the `dr_recipe` app-half (keyed by `customer_id`), mirroring the
|
||||
`app_telemetry` pattern. Both backward-compatible (old agents/controllers omit the field) and never
|
||||
fatal to the heartbeat.
|
||||
### Cross-repo golden checksum
|
||||
`hub/internal/api/testdata/host-report.golden.json` ≡ `felhom-agent/internal/hub/testdata/host-report.golden.json`:
|
||||
`57f2a5e7154ece1be94a79fd327f2924762e70c3ee388d6e375df7c918b2f2b5` (both).
|
||||
|
||||
### View (`hub/internal/web/dr_recipe.go` + customer page)
|
||||
|
||||
- A DR-recipe panel on the customer detail page (which half landed + last-updated) with a
|
||||
**Download recipe (JSON)** link.
|
||||
- `GET /customers/{id}/dr-recipe.json` serves the assembled recipe (operator dashboard-auth, pretty
|
||||
JSON, `Content-Disposition` attachment). No decrypt, nothing to redact.
|
||||
|
||||
### Boundary
|
||||
|
||||
The recipe is PLAINTEXT-at-rest because it carries NO secrets — only identifiers/intents/sizes/
|
||||
coordinates. The PBS key stays in escrow, the access token in identity-escrow, the restic password in
|
||||
escrow. This is the clean inverse of the retired infra-backup (which shipped `restic_password`/
|
||||
`cf_api_token` and was a zero-knowledge violation). The leak-preventing allowlist is enforced at the
|
||||
controller emitter; the hub adds a defense-in-depth no-secrets sweep.
|
||||
|
||||
### Tests
|
||||
|
||||
`TestDRRecipe_StoreRoundTrip`, `TestAssembleDRRecipe_MatchesGolden` (assembled shape pinned in
|
||||
`testdata/dr-recipe.golden.json`), `TestAssembleDRRecipe_IgnoreUnknownAndVersionSkew`,
|
||||
`TestAssembleDRRecipe_PartialHalves`, `TestAssembleDRRecipe_NoSecrets`. `go build`/`go vet`/`go test ./...`
|
||||
green.
|
||||
|
||||
### Cross-repo golden discipline
|
||||
|
||||
The recipe wire spans three repos. The agent's `host-report.golden.json` `dr_recipe` section and the
|
||||
hub's host-half test literal must stay key-consistent; the controller's emitter and the hub's app-half
|
||||
test literal likewise. On any wire change, manually checksum-diff the golden across the three repos
|
||||
(there is no shared types module yet).
|
||||
|
||||
### Deploy
|
||||
|
||||
Built + pushed `felhom-hub:0.13.0`; manifest tag bumped + ArgoCD `felhom` app synced.
|
||||
### BACKLOG flagged (not acted on)
|
||||
External-drive bulk data has **no offsite / second-failure-domain backup** — cross-drive (Tier-2) rsync
|
||||
lands on the same internal SSD in the same guest. This is why `restic_repo_coord` named nothing real and
|
||||
was dropped from v1. Design a genuine off-host bulk-data tier before re-introducing a drive backup coord.
|
||||
Full detail in `felhom-agent/REPORT.md` §8.
|
||||
|
||||
Reference in New Issue
Block a user