# 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.