v0.39.0 — DR-recipe completion: live PBS coord + drop role/restic_repo_coord from v1 drive shape

Live PBS coord: new internal/pbs/live_reporter.go (LiveSnapshotReporter implements
hub.PBSReporter via the cheap Client.Snapshots() list with last-known-good fallback,
bounded by an 8s timeout, list-only — never triggers a verify). Closes the gap where
the recipe's pbs block was omitted whenever the verify-loop SnapshotStore was empty
(one-shot collect + the first ~6h after a daemon restart). SnapshotStore.Get added
(per-datastore LKG). Wired into the collector in both runDaemon and runSelftestHub;
the verify loop keeps Recording into the SAME shared store via one hoisted pbsTargets.

v1 host-half drive shape: dropped drives[].role (hub/operator-owned manifest concept,
not host-derivable) and drives[].restic_repo_coord (named a backup tier that doesn't
exist). Drive shape is now {durable_id, mount_path, intent, fs_type?, total_bytes}.
Hub reads drives as json.RawMessage → no hub struct change; goldens re-pinned
byte-identical (agent + hub copies).

Tests: live_reporter_test.go (T1 load-bearing coord-without-verify + T2..T6),
TestDRRecipeHostHalf_V1DriveShape; each companion demonstrated to fail pre-fix then
reverted. go build/vet/test green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-16 20:25:09 +02:00
parent 8abc1b8852
commit fbe113011d
8 changed files with 414 additions and 23 deletions
+38
View File
@@ -3,6 +3,44 @@
All notable changes to **felhom-agent** are recorded here. Update on every code
change that gets pushed.
## v0.39.0 — DR recipe completion: live PBS coord + drop the two unfillable drive fields (2026-06-16)
**DR-recipe agent-half completion.** A live eyeball of the demo recipe (v0.38.0) found three host-half
problems; all three are resolved here. No behavior change outside the recipe path.
- **PBS coord now resolved LIVE each collect.** New `internal/pbs/live_reporter.go`
`LiveSnapshotReporter` implements `hub.PBSReporter` by doing the cheap `Client.Snapshots()` list
itself, with **last-known-good fallback**, instead of reading only the verify-loop's `SnapshotStore`.
Previously the recipe's `pbs` block was omitted whenever the store was empty — which a one-shot
collect (`--selftest=hub`) and the first ~6 h window of every daemon after a restart always saw (the
verify loop populates the store on its own 6 h cadence). The restore SOURCE must not depend on a
maintenance cadence. Per-datastore: a live error/timeout → that datastore's last-known-good; a
successful (even empty) response is authoritative and updates the shared store. Targets-resolution
failure → the full LKG aggregate. Bounded by `DefaultLiveSnapshotTimeout` (8 s) so a hung PBS never
stalls the heartbeat. List only — it never triggers a `Verify`. The verify loop keeps Recording into
the SAME store (shared last-known-good); both use one hoisted `pbsTargets` closure.
- `SnapshotStore.Get(datastore)` added (per-datastore LKG copy) — the only `SnapshotStore` change.
- Wired into the collector in BOTH `runDaemon` and `runSelftestHub` (the selftest built its own
collector with a `nil` reporter — that is why the live `--selftest=hub` showed `pbs_snapshots:[]`).
- Intended side effect: `report.pbs_snapshots` is now live too (fresher hub PBS view).
- **`drives[].role` DROPPED from the v1 host-half shape.** A drive's purpose is a hub/operator-owned
manifest concept, not cleanly derivable host-side (both demo externals are `content=backup`, yet one
is the primary data drive and the other holds no apps). Deferred until the hub/operator stamps it.
- **`drives[].restic_repo_coord` DROPPED from the v1 host-half shape.** It named a backup tier that does
not exist — cross-drive backup is rsync to the SAME internal SSD; there is no offsite/second-failure-
domain bulk copy. RESERVED for a future tier (see the BACKLOG note in REPORT). v1 drive shape is now
`{durable_id, mount_path, intent, fs_type?, total_bytes}` — identifiers/intent/size only.
- The hub reads drives as `json.RawMessage`, so dropping fields needs NO hub struct change — only
golden + test sync. Cross-repo golden (`host-report.golden.json` here + the hub's copy) re-pinned and
verified **byte-identical** (sha256 `57f2a5e7…18b2f2b5` — manual checksum-diff discipline): the hub copy
previously lacked the `dr_recipe` section entirely; it is now a verbatim copy of the agent golden.
- Tests: new `internal/pbs/live_reporter_test.go` (T1 coord-present-without-prior-verify [load-bearing] +
inline bare-store companion, T2 error→LKG fallback, T3 success-warms-store, T4 targets-error→aggregate,
T5 bounded-by-timeout, T6 empty-success-authoritative); `TestDRRecipeHostHalf_V1DriveShape` (drive
object carries neither `role` nor `restic_repo_coord`); `TestBuildDRRecipeHostHalf` /
`TestHostReport_ContractMatchesGolden` updated to the v1 drive shape. Each companion was demonstrated
to FAIL on the pre-fix/mutated code, then reverted (see REPORT).
## v0.38.0 — DR recipe: emit the secret-free storage/guest/PBS half in the host-report (2026-06-16)
**DR recipe slice (agent half).** Additive `dr_recipe` section on the host-report — the agent half of the