R-85 Phase 1: the restore-test spec is built PER RUN, not frozen at daemon start

SchedulerOptions.Spec was a VALUE produced by an immediately-invoked function
at daemon start, so storageTier() and restoreTaskTimeout() were evaluated once
and reused for every run for the process lifetime. Nothing tier-varying was
expressible (the offsite tier could never be scheduled), and it was a latent
staleness bug besides: a storage-type or config change did not take effect
until restart.

- backup.SpecBuilder: func(ctx, archive) RestoreTestSpec, called once per run.
  The archive is passed because the tier MUST come from it (v0.100.0 rule) —
  config-derived is what classified a PBS archive as 'local' and killed a
  14.46 GB WAN restore at the 10-minute local bound.
- A nil spec builder SKIPS loudly instead of panicking: a wiring bug must cost a
  restore-test, never the daemon goroutine.

Red-proof observed. Full suite green (29 packages, rc=0).
This commit is contained in:
Claude Code
2026-07-26 20:47:28 +02:00
parent edde8a01ca
commit 765d8b3168
5 changed files with 204 additions and 14 deletions
+28
View File
@@ -1,3 +1,31 @@
## v0.104.0-dev — R-85 Phase 1: the restore-test spec is built PER RUN (2026-07-26)
Prerequisite for scheduling the offsite tier at all. Shipped on its own because it is independently
correct and independently testable.
`SchedulerOptions.Spec` was a **value**, produced by an immediately-invoked function at daemon start
(`main.go`). So `storageTier()` and `restoreTaskTimeout()` were evaluated **once** and their result
reused for every run for the lifetime of the process. Two consequences:
- **Nothing tier-varying was expressible.** The offsite tier could never be scheduled, because the
spec's tier was fixed to whatever the configured target was at boot.
- **A latent staleness bug in its own right:** a storage-type or config change did not take effect
until the daemon restarted.
### Changed
- **`backup.SpecBuilder`** — `func(ctx, archive) reconcile.RestoreTestSpec`, called **once per run**.
The archive is passed in because the tier MUST come from it (`restoreTierForArchive`, the v0.100.0
rule). Deriving it from the configured target is what classified a PBS archive as `local` and
killed a 14.46 GB WAN restore at the 10-minute local bound.
- **A nil spec builder SKIPS loudly instead of panicking.** `Run` already refused to start without
one, but `tick` is reachable directly; a wiring bug must cost a restore-test, never the daemon
goroutine.
### Tests
+3. Red-proof observed: restoring the frozen value fails with
`the spec builder must run ONCE PER RUN, got 0 call(s) across 3 ticks`. Full suite green
(29 packages, `rc=0`, vet unpiped).
## v0.103.0 — R-84: an agent restart no longer triggers a redundant backup (2026-07-26)
**Observed live, not theorised.** Three redundant local backups ran on demo-felhom in a single