Files
felhom-controller/REPORT.md
T

3.9 KiB

REPORT — most recent implementation

v0.140.0 — Direction-2 immediate-sync: hub→box wait channel client (2026-07-16)

The reverse half of the immediacy arc (Direction 1 = v0.139.0 box→hub trigger). An operator action on the hub now reaches the box in seconds instead of on the next ~15-min cycle. Pairs with hub v0.58.0 (GET /api/v1/wait + the in-memory operator-intent notifier). Grounding: felhom.eu/documentation/audits/SPIKE-immediate-sync-transport-2026-07-16.md (option b).

What shipped

  • internal/report/waiter.go (new) report.Waiter. Holds a hanging authenticated GET {hub}/api/v1/wait?gen=N (same hub URL + key as the pusher — no new config keys). Its own http.Client has no overall Timeout (a held GET must stay open for the hub's ~240 s hold), with connect/TLS/ResponseHeaderTimeout deadlines; a per-request context bounds a black-holed connection. On a completion whose generation differs from the last seen, it fires the v0.139.0 report.Trigger — and nothing else; the report ACK delivers config/escrow/claim/floor through the UNCHANGED machinery (zero new delivery logic; the box pulls even the wake-up).
    • First observation records, never fires (no restart-echo report).
    • Same-generation timeout fires nothing (not interval-shortening).
    • Heartbeat newlines tolerated; only the {"gen":N} line is read (contentless wake).
    • Any error — transport, 404 from a pre-v0.58.0 hub, malformed body — backs off 5 s→5 min (reset on success), ONE WARN per state change, and the 15-min cycle keeps reconciling. Exits promptly on ctx cancel (even mid-hold).
  • cmd/controller/main.go. Waiter constructed + started beside the Direction-1 trigger, gated on the SAME hubPusher != nil && cfg.Hub.Enabled (strict no-op when hub reporting is off).
  • Copy soften. backups_remote.html + backups_escrow.html: "ez általában néhány másodperc, legfeljebb 15 perc" (the 15-min bound stays as the honest worst case; escrow grace window unchanged).

Tests + red-proof

Non-hollow waiter_test.go (httptest fake hub): fires-once-on-change, same-gen-timeout-no-fire, first-observation-records-no-fire (RED-PROOF: disable the baseline branch → first poll fires; run-fail-reverted), heartbeats-tolerated, malformed-completion-no-fire-no-panic, 404-as-error, Bearer+gen wire check, ctx-cancel-mid-hold-returns-promptly. Full controller suite green (go build/vet/test ./...); design gates pass (template_id / emoji / native_confirm / offbox_rename / mojibake).

Deploy + live validation (guest 9201, customer demo-felhom)

Image felhom-controller:0.140.0 built + pushed on 180; deployed via the bootstrap flow; container Up (healthy); log [report] hub wait channel active (hold ≤240s); startup report sent.

  • Completion-on-difference path: box-side curl with a mismatched ?gen=999999{"gen":0} in 0.047 s through the real ingress.
  • No-annotation hold proof: box-side curl at ?gen=0 held cleanly for 240.04 s (HTTP 200 → {"gen":0}, 10 heartbeat newlines); the controller's own Waiter logged no wait-channel errors over the window (holds + reconnects cleanly past nginx's 60 s default). No ingress annotation applied.
  • Scenario C (hub rollout restart mid-hold): the Waiter logged exactly one WARN (unexpected EOF — backing off), reconnected cleanly, and fired 0 out-of-cycle reports (gen 0→0, no storm). The "recovered" INFO is deferred to the next hold completion (pollOnce blocks) — cosmetic.
  • Save→applied via the operator UI needs an operator login (CC lacks the password by standing constraint); the mechanism is unit-proven and the delivery chain is the already-live v0.139.0 / config-refresh path.

Coupling

Immediacy needs hub ≥ v0.58.0; against an older hub the wait 404s and the box degrades to the 15-min cycle. No agent coupling, no MinAgent. The agent-plane ep0-poke stays parked in the OOB arc.