Files
felhom-agent/REPORT.md
T
admin 9842c52853 docs: REPORT for agent v0.105.0 (R-88 Part 2)
Records the wire encoding (string enum, empty = legacy not unknown) and the two
judgement calls: the fail-safe direction is unchanged (unknown is still due), and
a service with no lister stays ABSENT because calling it unknown would starve a
genuinely new box on a pre-R-84 build.
2026-07-27 18:12:04 +02:00

71 lines
3.8 KiB
Markdown

# REPORT — R-88 Part 2: the agent can say "unknown" (v0.105.0) (2026-07-27)
**Overwritten** per the standing rule. Agent **v0.104.0 → v0.105.0**. Producer half of a wire
contract; the controller consumer is v0.178.0.
## What was broken
`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` emitted a POSITIVE
`"no successful backup recorded yet"` with a nil age. The controller read that as *never backed up*,
fired its window-gate safety valve, and quiesced customer app stacks **outside** the backup window —
what happened during the 2026-07-27 PBS outage. The comment described an intent the type forbade.
## The wire encoding, and how the zero value is handled
**A string enum**, matching the repo's existing convention (`phase` on `/backup/status`):
```
age_state: "known" | "absent" | "unknown" (omitted entirely by a pre-v0.105.0 agent)
```
The zero value is `""`, and it means **"legacy agent, no information"** — never "unknown". A `bool`
would have made the legacy case indistinguishable from a real `false`, which is the trap the task
named. The controller maps `""` (and any unrecognised future value) to `AgeStateLegacy` explicitly.
Internally `archiveLookup` (`found`/`absent`/`unknown`) replaces the old bool, so the doc comment's
promise is now something the type can actually express.
## Two decisions worth stating
**The fail-safe direction is unchanged: unknown is still DUE.** An agent that cannot read the storage
must never suppress a backup. Only the *window-gate bypass* narrows.
**A service with NO lister deliberately stays `absent`, not `unknown`.** This broke a pre-existing
test (`TestBackupDue_ServiceWithoutLister_UnchangedBehaviour`) and the test was right: calling it
"unknown" would stop the controller firing its first-backup valve on a pre-R-84 build, so a genuinely
new box 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.
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.
## Tests — 29 packages ok, 0 failed; `build`/`vet`/`test` each rc=0, run separately, vet unpiped
+5. Red-proofs observed and restored, in both directions:
| Mutation | Observed |
|---|---|
| error path → `archiveAbsent` (the pre-fix collapse) | `an unreadable storage must report age_state="unknown", got "absent" — that is a POSITIVE claim of 'never backed up' built out of two absences` |
| `!found``archiveUnknown` (the over-correction) | `a genuine never-backed-up tier must report age_state="absent", got "unknown" — the controller only licenses a first backup outside the window on ABSENT` |
## Deployed — producer BEFORE consumer
demo-hp then demo-felhom, both `felhom-agent 0.105.0`, service active, previous binary kept as
`.bak-0.104.0`. **Verified on the live wire** from inside guest 9201, over the exact route the
controller calls:
```
tier=local due=True age_state=known age_seconds=91097 reason=older than cadence
tier=felhom-pbs due=False age_state=known age_seconds=100172 reason=within cadence window
```
## NOT yet live-validated
- **No `unknown` state has ever been observed on real hardware.** Both boxes report `known`; the
unknown path is unit-proven only, and reproducing it live means making PBS unreadable, which must
not be done to ep0.
- The `absent` path is likewise unobserved live — it needs a genuinely fresh tier.