Files
felhom-agent/REPORT.md
T
Claude Code e4f22f4c4f REPORT: R-82 agent arc v0.97.0 -> v0.102.0 (overwrite)
Four defects found by running it rather than reviewing it, the frozen untargeted
contract verified live, the fail-safe directions stated once, and what is NOT
done — including that the scheduled restore-test never selects the offsite tier
and that R-84 is now closer to a prerequisite than a tidy-up.
2026-07-26 17:56:42 +02:00

5.2 KiB

REPORT — R-82: per-target backup tiers, agent v0.97.0 → v0.102.0 (2026-07-26)

Overwritten per the standing rule. Live on demo-felhom and demo-hp. Full cross-repo arc + the Phase-0 gates: felhom.eu/REPORT.md.

Versions in this arc

v0.97.0 Slice A — per-target cadence/retention, /backup/tiers, per-tier due + runner + job keys
v0.98.0 the 30-minute false failure — per-tier vzdump wait bound
v0.99.0 operator rulings — 2-week offsite retention (scoped PBS prune opt-in) + one backup at a time per guest
v0.100.0 the restore tier comes from the ARCHIVE, not the configured target
v0.101.0 a leaked restore-test scratch can no longer auto-start (onboot=0 at restore time)
v0.102.0 an unprovisioned tier DEFERS instead of firing at a missing storage

Four defects found by RUNNING it, not reviewing it

  1. v0.98.0 — a 41-minute PBS backup was recorded success:false at 30 minutes while it was still running, and it later completed TASK OK. Worse than "the backup didn't happen": the tier stays permanently due and the retry collides with the guest lock.
  2. v0.100.0 — a felhom-pbs: archive was classified local and got the 10-minute bound against a 14.46 GB WAN restore, failing at 600 s. A silent regression of the S4.1 fix — the mechanism was never removed; its input changed when local_backup_target was retargeted to local. A fix keyed on "the configured target" stops holding the moment more than one target exists.
  3. v0.101.0 — a leaked scratch guest kept onboot: 1, so a host reboot would have started a clone of the live guest. onboot=0 is now set at restore time, because "after" is the path that leaks.
  4. v0.102.0 — a tier fires at a storage that does not exist yet (a fresh box before DR provisioning). Now deferred, going live with no restart once the storage appears.

The frozen contract

Untargeted /backup/due, POST /backup and /backup/status keep the primary tier and the pre-R-82 response bytes (Target is omitempty and stays empty); the primary's job-id format is unchanged. Verified live on demo-felhom:

/backup/due (untargeted)      {"vmid":9201,"due":true,"reason":"no successful backup recorded yet"}
/backup/due?target=felhom-pbs {...,"target":"felhom-pbs"}
/backup/due?target=bogus      400 "unknown backup target: bogus"

An unknown ?target= is a 400, never a silent fallback — a caller asking about a tier this agent does not serve must find out, not act on another tier's freshness.

Fail-safe directions, stated once

  • Unparseable timestamp → due (a spurious backup is cheap; a skipped one is not).
  • Storage-view error → present ("I could not check" is not "not there" — reading it that way would silently suppress backups, the rule this project has relearned three times).
  • Missing tier cadence → rejected, logged at ERROR (never defaulted: a weekly DR tier silently running daily would fill the datastore).
  • Unset keep_lastnever prune (a DR tier must not start pruning itself because someone forgot a field). The primary tier keeps the absolute PBS-prune refusal, because its target and retention both default and could prune the DR by accident.

Live validation (demo-felhom)

Both tiers armed: local 24h keep_last=3 wait 30m + felhom-pbs 168h keep_last=2 wait 12h prune_pbs_allowed=true. First real PBS-targeted backup: TASK OK, 41 minutes, 14.46 GB snapshot, and it restored cleanly (vzrestore: stopped OK). Measured incremental cost: +2.7 GB on disk for a 14.46 GB logical snapshot (~81 % dedup).

demo-hp: v0.102.0 deployed via the documented break-glass path (binary + config backed up first), tier armed, first-ever PBS backup triggered against a verifiably empty namespace.

Tests

go build ./... && go vet ./... && go test ./...rc=0, 29 packages, vet run unpiped.

Red-proofs observed and restored: old-controller compat ("target":"local" leaking into the untargeted body), the per-tier wait bound, and the onboot override. A per-tier single-flight defect was caught by its own test before it ever shipped — job ids were unique only by clock luck, so the PBS request was handed the local job's id.

Process failure recorded: I ran the suite and committed in the same command, read packages ok: 28 and pushed without checking rc=1; five of my own tests were failing (a harness artifact, fixed in the follow-up). That is the exact exit-code trap recorded twice earlier in this arc.

NOT done

  1. The scheduled restore-test still only ever selects the primary tier, so the offsite tier is never automatically restore-tested — arguably the more important half of "is the DR tier real?".
  2. The agent does not report per-tier cadences, so the hub must infer "PBS ⇒ weekly" from storage type.
  3. The backup Store is still in-memory (R-84): every agent restart re-triggers a backup on every tier. On the local tier that is wasted minutes; on the offsite tier it is a wasted multi-hour upload after every deploy — which makes R-84 closer to a prerequisite than a tidy-up.
  4. The boot+verify half of the restore round-trip has not run under the fixed code.