R-331 (controller half): forward stats_known so the hub can tell empty from unmeasured (v0.225.0)
gates / gates (push) Successful in 12s

The hub's operator Backup card read `Snapshots 0 / Repo Size 0 MB / Integrity
Unknown` for EVERY customer, because it rendered the report's `backup` object --
whose snapshot/size/integrity fields have had NO producer since disk-tier restic
moved to the host agent (slice 8C). buildBackupReport leaves them zero
deliberately and says so. Measured on demo-hp 2026-08-30 while that night's log
said `[offbox] backup OK: 8 app(s) backed up, 67 snapshot(s), 2m14s`.

The live numbers were always in the report's `offsite` object, which the hub
already reads for its Offsite page and its fill/staleness alarms. The hub fix is
to render that -- and that made exactly ONE field mandatory that was not being
forwarded.

snapshot_count:0 means two opposite things: "holds nothing" and "never
measured". R-225 measured that confusion inside this repo (a rebuilt box
rendered 0 pillanatkep over a store really holding snapshot f3d9cd67), and
settings.OffboxTarget.StatsKnown fixed it for the controller's own UI. It was
never put on the wire, so the hub was free to make the identical mistake one
layer up -- and did. OffboxReportStatus.StatsKnown now carries it, omitempty, so
an older controller sends no key and a reader degrades to UNKNOWN, never to
EMPTY. Absence is ignorance, not emptiness.

The four dead BackupReport fields stay on the wire (historical reports in the
hub store must keep parsing) but now carry a warning naming R-331 and pointing
at Offsite. TestBackupReport_DeadFieldsStayZero fails the moment a producer
appears for one -- the prompt to update the hub card in the SAME change rather
than ship a field nothing renders.

RED-PROOF: drop `StatsKnown: t.StatsKnown` -> "a MEASURED empty repository
reported stats_known=<nil>". Tests assert the JSON the hub sees, not the Go
struct: measured-empty and never-measured must differ ON THE WIRE, which is the
entire point of the field.

Green gate clean: 28 packages, rc 0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LB8FmJaGd2cyjvy6dbEjpM
This commit is contained in:
2026-08-30 18:38:10 +02:00
parent 45b52b6ed5
commit e5eee501b5
7 changed files with 324 additions and 180 deletions
+44
View File
@@ -1,3 +1,47 @@
## v0.225.0 — the hub could not tell an empty off-site store from an unmeasured one (2026-08-30, R-331)
**MinAgent: 0.129.0** (unchanged — no new agent coupling)
### R-331 (controller half) — forward `stats_known`
One field, and the reason it is a release. The hub's operator Backup card read
`Snapshots 0 · Repo Size 0 MB · Integrity Unknown` for **every customer** because it rendered the
report's `backup` object, whose snapshot/size/integrity fields have had **no producer** since
disk-tier restic moved to the host agent (slice 8C) — `buildBackupReport` says so in a comment and
leaves them zero. Measured on `demo-hp` 2026-08-30, while that night's log said
`[offbox] backup OK: 8 app(s) backed up, 67 snapshot(s), 2m14s`.
The live numbers were always in the report's `offsite` object (`OffboxReportStatus`), which the hub
already reads for its Offsite page and its fill/staleness alarms. **The hub fix is to render that
object — and that made exactly one field mandatory that was not being forwarded.**
`snapshot_count: 0` means two opposite things: *this repository holds nothing* and *nobody has ever
measured this repository*. **R-225 measured that confusion inside this repo** — a rebuilt box rendered
„Tarolo meret · 0 pillanatkep" over a store that really held snapshot `f3d9cd67` — and
`settings.OffboxTarget.StatsKnown` is what fixed it for the controller's own UI. It was never put on
the wire, so the hub was free to make the identical mistake one layer up, and did.
`OffboxReportStatus.StatsKnown` now carries it. It is `omitempty`, so a controller below this version
sends no key at all and a hub parsing it sees `false` — **which must mean "cannot answer", never "the
answer is zero"**. Absence is ignorance, not emptiness; that direction is pinned by
`TestOffboxReportStatus_AbsentStatsKnownParsesAsUnknown`.
### The four dead `BackupReport` fields are now labelled and pinned
`SnapshotCount`, `RepoSizeMB`, `LastIntegrityCheck` and `IntegrityOK` stay on the wire so historical
reports in the hub's store keep parsing, but they now carry a warning naming R-331 and pointing at
`Offsite` instead. `TestBackupReport_DeadFieldsStayZero` fails the moment a producer appears for any
of them — the prompt to update the hub card in the **same** change rather than shipping a field
nothing renders. That is the "seam built but never wired" class, hit five times in this project.
`IntegrityOK` has no source at all and cannot get one by accident: the controller runs no integrity
check, and `NotifyIntegrityOK` / `NotifyIntegrityFailed` exist and are called from nowhere. The hub
card drops the row rather than re-sourcing it.
**Tests:** `offbox_statsknown_report_test.go` asserts the JSON the hub actually sees, not the Go
struct — the measured-empty and never-measured cases must differ on the wire, which is the entire
point of the field. **RED-PROOF:** dropping `StatsKnown: t.StatsKnown` from `OffboxReportStatus()`
fails with `a MEASURED empty repository reported stats_known=<nil>`.
## v0.224.0 — the backup alarmed about the apps it was holding down (2026-08-30, R-330)
**MinAgent: 0.129.0** (unchanged — no new agent coupling)