Files
felhom-controller/REPORT.md
T

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.

  • offboxConfigHandler rebuilds 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.
  • ApplyOffsiteTarget does 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_stale firing from a stale anchor.
  • R-101 (filed today) — Tier-2 LastRun is 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.