Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pg8ANF97SEeKYSN5Jxw3qJ
5.4 KiB
REPORT — Controller→agent channel health-check, v0.90.0
Task: the next self-health slice — a periodic check that the controller↔agent local-API channel is
up, classifying failures and alerting the operator + dashboard on a state change. Closes the gap the R1
pin-mismatch incident exposed (the channel was only probed once at startup and only logged).
Spike-proven: felhom.eu/documentation/audits/SPIKE-controller-agent-channel-health-2026-06-29.md.
Baseline: controller main @ 77bccf1 (live 0.89.0) → v0.90.0 @ a277b18 (image
…/felhom-controller:0.90.0). Companion hub change: felhom-hub v0.20.0 (allowlist; see below).
Implementation
-
internal/channelhealth(NEW): aCheckerover two seams —Probe(the channel call) andSink(dashboard + notify) — so it is decoupled + table-tested. Each run classifies intoup | down:<reason>. -
Classifier (spike Q1 map, substring match):
reason match first-obs alert? severity event type pin_mismatch TLS pin mismatchyes error agent_channel_pin_mismatchunauthorized HTTP 401yes error agent_channel_unauthorizedmisconfigured no such host/lookupyes error agent_channel_misconfiguredconstruction_error agentClient()build error (LATCHES)yes error agent_channel_construction_errorunreachable connection refusedno (N≥2) warning agent_channel_unreachabletimeout context deadline exceeded/i/o timeoutno (N≥2) warning agent_channel_timeoutunknown (fallthrough) no (N≥2) warning agent_channel_unknownrecovered down→up transition — info agent_channel_recovered -
Probe = the PRODUCTION memoized client (
Server.ProbeAgentChannel→s.agentClient()+Storage()), not a freshagentapi.Newper probe (the spike's decisive call: self-heals, reflects the disk UI exactly, no transport leak). A construction error (latchingsync.Oncebuild failure) is surfaced distinctly via theconstructionErrflag. -
Debounce + state machine: transient reasons need N≥2 consecutive down-probes before a transition (the ~1s agent-restart gap is suppressed); pin/401/DNS/construction transition on the first down. First scheduler observation seeds state, no alert. A transition (up→down, down→up, reason-change) fires once; steady-down does not re-fire.
-
Operator alert + dashboard:
Notifier.NotifyAgentChannelDown/Recoveredpush an English, operator-only event (customer can't act on it; the type is not a customer toggle); the hub applies its 1 h cooldown.AlertManager.SetAgentChannelAlertshows a short Hungarian banner whenever the channel is down (state-based, idempotent — prepended inGetAlerts). No customer email. -
Wiring:
cmd/controller/main.goregisters a ~60 ssched.Every("agent-channel-health", …)job (only whenlocal_api.endpointis set), after the webServer is built (it owns the memoized client).probeLocalAPIstays at startup. AchannelSinkadapter bridges the package to notify+alerts.
Hub change (was "verify-only" — live test showed it WAS needed)
The controller pushes agent_channel_* to /api/v1/event, but the hub's allowedEventTypes
allowlist rejected them (live: Event push failed … HTTP 400). host_capability_* didn't hit this
because those are hub-generated; agent_channel_* are controller-pushed. felhom-hub v0.20.0
adds the 8 types to the allowlist (operator-only; the dispatcher relays generically — no template
change). No agent change.
Tests (all green: go build/vet/test ./...)
internal/channelhealth: classifier per-reason (pin/401/dns/construction), transitions (up→down once,
steady-down no duplicate, down→up recovered, reason-change re-alerts), first-obs seed (born-down shows
on dashboard, no notify), probe-seam-only. Debounce red-proof (TestDebounce_TransientBlipSuppressed):
one refused → no alert; two consecutive → exactly one — a no-debounce impl fails this.
Live validation (felhom-pve guest 9201) — deployed + verified
- Startup:
local-api: channel up (agent 192.168.0.162:8443) — guest 9201, 4 mount(s)+[scheduler] Registered periodic job: agent-channel-health (every 1m0s). - Transient (clean
systemctl restart felhom-agent): no channel-down alert, dashboard stayed clean (the ~2s gap + 60s cadence + debounce). - Sustained (
systemctl stop felhom-agent): the live logs show the debounce + transition exactly:[channel] transient down (unreachable, 1/2) — suppressedthen 60s later[channel] agent channel DOWN (up→down:unreachable); the dashboard banner "A tárolókezelő ügynök nem elérhető." appeared (verified in the served HTML). - Recovery (
systemctl start):[channel] agent channel recovered (was down:unreachable). - Operator relay: initially
HTTP 400(hub allowlist) → fixed in hub v0.20.0 → re-validated: theagent_channel_unreachable+agent_channel_recoveredevents push to the hub HTTP 200 (Event pushed: agent_channel_…).
NOT changed
The agent; the agentapi pin/transport; agentClient() memoization; the disk gate. Detection/surfacing
only.
Forward note
Remaining self-health leg (backlog): the hub-side leaf-fp comparison (proactively catching an agent re-key fleet-wide before any user hits it).