R-331 (controller half): forward stats_known so the hub can tell empty from unmeasured (v0.225.0)
gates / gates (push) Successful in 12s
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:
@@ -1438,6 +1438,21 @@ type OffboxReportStatus struct {
|
||||
SnapshotCount int `json:"snapshot_count"`
|
||||
RepoSizeBytes int64 `json:"repo_size_bytes"`
|
||||
QuotaGB int `json:"quota_gb"`
|
||||
// StatsKnown (R-331) forwards settings.OffboxTarget.StatsKnown to the hub, and it is the ONLY
|
||||
// thing that lets a hub-side display tell "this repository holds nothing" from "nobody has ever
|
||||
// measured this repository". Both are `snapshot_count: 0` on the wire and they are opposite news.
|
||||
//
|
||||
// It is carried rather than inferred for the reason R-225 recorded on the settings field itself:
|
||||
// the counts are `omitempty` ints, so absent and zero are the same bytes and the distinction
|
||||
// cannot be recovered downstream. R-225 was measured live — a rebuilt box rendered
|
||||
// „Tároló méret · 0 pillanatkép" over a store that really held snapshot `f3d9cd67` — and this
|
||||
// field is what stopped the controller's own UI doing that. Not forwarding it left the hub free
|
||||
// to make the identical mistake, which it did: the operator Backup card read `Snapshots 0` for
|
||||
// every customer.
|
||||
//
|
||||
// Absent on a pre-v0.225.0 controller, and a hub MUST degrade to "unknown" there rather than to
|
||||
// "empty" — absence means the box cannot answer, never that the answer is no.
|
||||
StatsKnown bool `json:"stats_known,omitempty"`
|
||||
}
|
||||
|
||||
// OffsiteStateNeedsCredential is the ONE declared state (v0.199.0, R-204 item 4 / R-193): this box
|
||||
@@ -1628,6 +1643,7 @@ func (m *Manager) OffboxReportStatus() *OffboxReportStatus {
|
||||
Enabled: true, EscrowState: t.EscrowState, LastRun: t.LastRun, LastStatus: t.LastStatus,
|
||||
LastSuccess: t.LastSuccess,
|
||||
SnapshotCount: t.SnapshotCount, RepoSizeBytes: t.RepoSizeBytes, QuotaGB: t.QuotaGB,
|
||||
StatsKnown: t.StatsKnown, // R-331 — without it the hub cannot tell "empty" from "unmeasured"
|
||||
AbandonPurgeRequested: t.AbandonPurgeRequested, // R-241: declared until the hub drops the package
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user