Files
felhom.eu/REPORT.md
T
admin 5f5e3c54a1 hub v0.13.0: DR recipe — assemble + store + view the secret-free reconstruction recipe
DR recipe slice (hub half), grounded in SPIKE-dr-recipe-2026-06-16. The hub
receives two additive dr_recipe halves on the existing report paths (agent
storage/guest/PBS on host-report; controller customer/apps on the controller
report), stores them PLAINTEXT in a DEDICATED dr_recipe table keyed by customer
(each half preserves the other), and AssembleDRRecipe stitches them into one
operator-readable recipe (ignore-unknown + version-skew tolerant).

View: a DR-recipe panel on the customer page + GET /customers/{id}/dr-recipe.json
download (operator-auth, no secrets to redact). Plaintext-at-rest is correct —
the recipe is the clean inverse of the retired infra-backup.

Tests: store round-trip (each half preserves the other), assemble-matches-golden,
ignore-unknown + version skew, partial halves, no-secrets sweep. Manifest tag
bumped to v0.13.0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 18:49:45 +02:00

3.4 KiB

felhom.eu — task reports

Overwrite this file with a summary of the most recent task only (uniform with the other repos; not cumulative). The cumulative hub history lives in hub/CHANGELOG.md.


REPORT — hub v0.13.0: DR recipe (assemble + store + view)

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

Store (hub/internal/store/dr_recipe.go)

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

Ingest (hub/internal/api/handler.go)

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

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.