hub v0.13.1 — DR recipe v1 drive-shape sync (test-data + regression guard only)
Tracks felhom-agent v0.39.0, which dropped role + restic_repo_coord from the host-half drives[]. Hub reads drives as json.RawMessage (verbatim passthrough) → no store/handler change needed. Dropped role from the store golden + drHostHalf fixture; re-synced the api host-report golden byte-identical with the agent copy (it previously lacked dr_recipe entirely). Added TestAssembleDRRecipe_V1DriveShape regression guard (demonstrated to fail when the fixture re-adds role, then reverted). No behavior change; redeploy optional. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,22 @@
|
||||
# Felhom Hub — Changelog
|
||||
|
||||
## v0.13.1 — DR recipe v1 drive-shape sync: test-data + regression guard only (2026-06-16)
|
||||
|
||||
**No behavior change — redeploy optional.** Tracks the agent's v0.39.0 v1 host-half drive shape (which
|
||||
dropped `role` + `restic_repo_coord` from `drives[]`). Because the hub reads drives as `json.RawMessage`
|
||||
(verbatim passthrough), no store/handler/struct change was needed — only test-data + a regression guard.
|
||||
|
||||
- `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 now emits.
|
||||
- `internal/api/testdata/host-report.golden.json`: re-synced to be **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 `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.
|
||||
|
||||
## v0.13.0 — DR recipe: assemble + store + view the secret-free reconstruction recipe (2026-06-16)
|
||||
|
||||
**DR recipe slice (hub half)** — the assemble-store-view side of the secret-free reconstruction recipe
|
||||
|
||||
+24
-1
@@ -131,5 +131,28 @@
|
||||
}
|
||||
],
|
||||
"cloudflared": { "status": "active" },
|
||||
"audit_tail": []
|
||||
"audit_tail": [],
|
||||
"dr_recipe": {
|
||||
"recipe_version": 1,
|
||||
"guests": [
|
||||
{ "vmid": 100, "cores": 2, "memory_bytes": 2147483648, "disk_bytes": 21474836480 }
|
||||
],
|
||||
"pbs": {
|
||||
"repo_id": "felhom-pbs",
|
||||
"namespace": "root",
|
||||
"latest_snapshot_id": "9001"
|
||||
},
|
||||
"drives": [
|
||||
{
|
||||
"durable_id": "uuid:0fc63daf-8483-4772-8e79-3d69d8477de4",
|
||||
"mount_path": "/mnt/usb-backup",
|
||||
"intent": "enrolled",
|
||||
"total_bytes": 2000000000000
|
||||
}
|
||||
],
|
||||
"pve_storage": [
|
||||
{ "name": "local-lvm", "type": "lvmthin", "content": "rootdir,images" },
|
||||
{ "name": "usb-backup", "type": "usb", "content": "backup" }
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ const drHostHalf = `{
|
||||
"recipe_version": 1,
|
||||
"guests": [ { "vmid": 9201, "cores": 4, "memory_bytes": 12884901888, "disk_bytes": 34359738368 } ],
|
||||
"pbs": { "repo_id": "felhom-pbs", "namespace": "root", "latest_snapshot_id": "9201" },
|
||||
"drives": [ { "durable_id": "uuid:da9e7089-cf8e-4617-adcb-a377743fae00", "role": "bulk-data", "mount_path": "/mnt/felhom-usb", "intent": "enrolled", "total_bytes": 1000000000000 } ],
|
||||
"drives": [ { "durable_id": "uuid:da9e7089-cf8e-4617-adcb-a377743fae00", "mount_path": "/mnt/felhom-usb", "intent": "enrolled", "total_bytes": 1000000000000 } ],
|
||||
"pve_storage": [ { "name": "local-lvm", "type": "lvmthin", "content": "rootdir,images" }, { "name": "felhom-usb", "type": "usb", "content": "backup" } ]
|
||||
}`
|
||||
|
||||
@@ -104,6 +104,35 @@ func TestAssembleDRRecipe_MatchesGolden(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestAssembleDRRecipe_V1DriveShape is the regression guard for the v1 host-half drive shape
|
||||
// (agent v0.39.0 dropped role + restic_repo_coord): a stored host half whose drives carry NEITHER
|
||||
// field, but WHICH HAS a pbs block, must assemble cleanly — pbs present, drives passed through
|
||||
// verbatim (RawMessage passthrough means the hub needs no struct change for the dropped fields).
|
||||
func TestAssembleDRRecipe_V1DriveShape(t *testing.T) {
|
||||
const v1Host = `{
|
||||
"recipe_version": 1,
|
||||
"guests": [ { "vmid": 9201, "cores": 4, "memory_bytes": 12884901888, "disk_bytes": 34359738368 } ],
|
||||
"pbs": { "repo_id": "felhom-pbs", "namespace": "root", "latest_snapshot_id": "9201" },
|
||||
"drives": [ { "durable_id": "uuid:da9e7089", "mount_path": "/mnt/felhom-usb", "intent": "enrolled", "total_bytes": 1000000000000 } ],
|
||||
"pve_storage": [ { "name": "felhom-usb", "type": "usb", "content": "backup" } ]
|
||||
}`
|
||||
asm, err := AssembleDRRecipe(&DRRecipe{CustomerID: "c", RecipeVersion: 1, HostHalfJSON: v1Host})
|
||||
if err != nil {
|
||||
t.Fatalf("v1 host half failed to assemble: %v", err)
|
||||
}
|
||||
// pbs must survive the stitch.
|
||||
if !jsonContains(t, asm.PBS, "felhom-pbs") || !jsonContains(t, asm.PBS, "9201") {
|
||||
t.Errorf("pbs coord not carried through assembly: %s", asm.PBS)
|
||||
}
|
||||
// drives passed through verbatim, and carry NEITHER dropped field.
|
||||
if !jsonContains(t, asm.Drives, "uuid:da9e7089") {
|
||||
t.Errorf("drives not passed through: %s", asm.Drives)
|
||||
}
|
||||
if strings.Contains(string(asm.Drives), "role") || strings.Contains(string(asm.Drives), "restic_repo_coord") {
|
||||
t.Errorf("v1 drives must not carry role/restic_repo_coord: %s", asm.Drives)
|
||||
}
|
||||
}
|
||||
|
||||
// TestAssembleDRRecipe_IgnoreUnknownAndVersionSkew: a half carrying an UNKNOWN top-level field and a
|
||||
// HIGHER recipe_version still assembles (forward-compat), and recipe_version reflects the max.
|
||||
func TestAssembleDRRecipe_IgnoreUnknownAndVersionSkew(t *testing.T) {
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
"drives": [
|
||||
{
|
||||
"durable_id": "uuid:da9e7089-cf8e-4617-adcb-a377743fae00",
|
||||
"role": "bulk-data",
|
||||
"mount_path": "/mnt/felhom-usb",
|
||||
"intent": "enrolled",
|
||||
"total_bytes": 1000000000000
|
||||
|
||||
Reference in New Issue
Block a user