agent v0.105.0 — R-88 Part 2: /backup/due gains age_state
newestArchiveOn's (time.Time, bool) signature could not express the 'unknown' its own doc comment promised: a read error and a genuine not-found both returned (zero,false), so /backup/due emitted a POSITIVE 'no successful backup recorded yet' with a nil age, and the controller fired its window-gate valve on an unreadable storage. Three states now: known / absent / unknown, carried as a STRING enum so the zero value unambiguously means 'legacy agent' rather than masquerading as an answer. Fail-safe direction unchanged — unknown is still DUE; only the window-gate bypass narrows to ABSENT. A service with NO lister deliberately stays ABSENT: calling it unknown would stop a genuinely new box on a pre-R-84 build from ever backing up outside its window. An unparseable timestamp becomes unknown — a backup happened, we cannot date it.
This commit is contained in:
@@ -1,3 +1,44 @@
|
||||
# felhom-agent — Changelog
|
||||
|
||||
## v0.105.0 — R-88 Part 2: the agent can finally say "unknown" (2026-07-27)
|
||||
|
||||
`newestArchiveOn` promised, in its own doc comment, that *"errors and unsupported services degrade to
|
||||
unknown, never to 'no backup'"*. Its `(time.Time, bool)` signature made that **impossible**: an error
|
||||
and a genuine not-found both returned `(zero, false)`, so `/backup/due` answered a POSITIVE
|
||||
`"no successful backup recorded yet"` with a nil age. The controller read that as *never backed up*
|
||||
and fired its window-gate safety valve — quiescing customer app stacks **outside** the backup window.
|
||||
That is what happened on 2026-07-27 during the PBS outage. The comment described an intent the type
|
||||
forbade.
|
||||
|
||||
**The fix: three states, on the wire.** `archiveLookup` (`found` / `absent` / `unknown`) internally,
|
||||
and `age_state` on `/backup/due`:
|
||||
|
||||
- `known` — `age_seconds` is set and meaningful;
|
||||
- `absent` — a POSITIVE determination that no backup has ever landed. **The only state that licenses
|
||||
the controller to bypass its backup window;**
|
||||
- `unknown` — could not determine (storage unreadable, or an unparseable timestamp).
|
||||
|
||||
**A string enum, not a bool.** The zero value must mean *"legacy agent, no information"*, and `""`
|
||||
says that unambiguously where `false` would masquerade as a real answer. It also matches the repo's
|
||||
existing wire convention (`phase` on `/backup/status`).
|
||||
|
||||
**The fail-safe direction is unchanged: unknown is still DUE.** An agent that cannot read the storage
|
||||
must never suppress a backup. What changes is only whether the *window gate* may be bypassed.
|
||||
|
||||
**An unparseable in-memory timestamp is now `unknown` too** — a backup DID happen, we simply cannot
|
||||
date it. It previously fell through to the same positive "never" claim.
|
||||
|
||||
**A service with NO lister stays `absent`, deliberately.** "Unknown" is the tempting answer there and
|
||||
it would regress the first-backup safety valve: a genuinely new box on a pre-R-84 build would never
|
||||
back up outside its window and nobody would notice for weeks. On that path the in-memory record is
|
||||
the only registry that exists, so its absence means "no backup recorded" in the only terms available.
|
||||
`unknown` is reserved for a lister that was asked and could not answer.
|
||||
|
||||
**Compatibility:** the field is additive. An OLD controller decodes into a struct without it and
|
||||
behaves exactly as today — every pre-existing field is unchanged (pinned by
|
||||
`TestAgeState_IsAdditive_PreExistingFieldsUnchanged`).
|
||||
|
||||
Tests +5; 29 packages ok. Red-proofs observed for the unknown/absent collapse in both directions.
|
||||
## v0.104.0 — R-85: unattended per-tier restore-test (Phase 2): tier rotation, persisted state, one heavy op at a time (2026-07-26)
|
||||
|
||||
The scheduler could only ever see `cfg.Backup.BackupTarget()`, so the offsite tier's archives were
|
||||
|
||||
Reference in New Issue
Block a user