4.7 KiB
REPORT — R-100: record the last SUCCESS, not just the last attempt (v0.181.0) (2026-07-28)
Overwritten per the standing rule. Controller v0.180.0 → v0.181.0 — the producer half of
R-100, shipped and deployed BEFORE the hub so the consumer never reads a field nothing sends.
Consumer: hub v0.80.0. Full arc, Phase 0 answers and the live proof: felhom.eu/REPORT-r100.md.
Baselines (reconfirmed, not copied)
felhom-controller 4056fec = origin/main, clean; controller 0.180.0 live on both boxes; hub 0.79.0
(manifest and running pod); agent 0.110.0.
What changed here
OffboxTarget gains LastSuccess (RFC3339), carried to the hub on the offsite report as
last_success. LastRun is written unconditionally at the end of every run including failures — it
records an attempt, so "how long since LastRun" answers "how long since we last TRIED", which is
not the question the hub's freshness verdict was asking of it.
The rule is a pure function, called unconditionally beside the LastRun write:
func offboxAnchorAfterRun(prev, at string, runErr error) string {
if runErr != nil { return prev } // failures neither advance nor clear the anchor
return at
}
Both directions are distinct bugs and both are pinned: a failure must not advance it (the original defect) and must not clear it (one bad night making an established tier read as never-succeeded — the mirror-image over-correction, and on the hub side the newborn-box path).
Why not simply LastStatus == "error" ⇒ stale: that is the tempting one-line fix and it pages on
every transient blip — the F-A1 noise mode that trains an operator to ignore the alarm. Anchoring on the
last success tolerates one bad night and catches a persistent one, using the threshold that already
exists. No new knob.
Two silent-wipe sites, found and closed
Both are the "seam built but never wired" shape — the field exists, the writer sets it, and an unrelated routine path zeroes it. Neither would have surfaced until the hub's verdict changed, days later.
offboxConfigHandlerrebuilds the target from the form and copies runtime status across field by field, so an ordinary settings save (edit the host, edit the path) would have erased the anchor.ApplyOffsiteTargetdoes the same on a hub re-apply — an established tier reset to "never succeeded" every time the hub re-pushed its descriptor.
A hollow test of my own, caught by red-proofing it
The first version of TestOffboxLastSuccess_OnlyAdvancesOnSuccess re-implemented the rule in a local
closure. Mutating the production code left it green — it asserted my model, not the system. That is
what the extraction to offboxAnchorAfterRun is for: the test now calls the real rule and the red-proof
bites.
Red-proofs — all observed failing
| red-proof | observed failure |
|---|---|
drop the runErr guard |
a FAILED run advanced LastSuccess to "2026-07-21T02:15:00Z" — that is the R-100 defect in mirror image |
always return prev |
a successful run did not advance the anchor |
drop LastSuccess from the wire |
OffboxReportStatus dropped LastSuccess — the hub would degrade forever on a controller that has it |
| drop the handler preservation | a settings save erased LastSuccess (got "") — the tier would read as never-succeeded |
go build, go vet ./..., go test ./... — 27 packages, rc=0, run separately from the commit.
Live, on demo-hp and demo-felhom
demo-hp success run → ok last_run=11:24:20Z last_success=11:24:20Z
demo-hp settings save (port 23→2) → last_success PRESERVED 11:24:20Z ← the wipe-site fix, live
demo-hp failing run → error last_run=11:25:48Z last_success=11:24:20Z ← ANCHOR HELD
demo-felhom healthy run→ ok last_run=11:29:22Z last_success=11:29:22Z ← advanced
Both shapes reached the hub. The failure was induced with a closed port (creates nothing, touches no
data) and the config was restored field by field. peti-felhom was never touched.
Not proven live: the hub's 48h staleness threshold itself — that is unit-tested with an injected clock and would take days to observe live.
Deployed
felhom-controller 0.181.0 on demo-hp and demo-felhom, both Up (healthy).
NOT yet live-validated (carried forward)
- The 48h threshold, and with it an actually-observed
offsite_stalefiring from a stale anchor. - R-101 (filed today) — Tier-2
LastRunis also written on failure and three customer surfaces render it without a status, incl. the restore-confirm dialog. - Fault 4 — restic transport interruption; the closed-port injection sidesteps it, not solves it.
- R-99 — prune never removes phantom snapshots.