Surface the real Halibut connection log per machine
Summary
- An agent's operator-facing connection status was only
MachineHealthStatus, derived from whether the last Capabilities probe succeeded — there was no way to see the actual transport-level story (connection opened, TLS/security negotiation, message exchange, listener accept, errors). A disconnected/flaky agent showedUnhealthywith no why. - The server's Halibut runtime was built with no log factory, so Halibut's per-endpoint connection events were unreachable. This wires Halibut's own bounded in-memory
LogFactoryviaWithLogFactory, holds the singleton reference, and exposes the events per machine viaGET /api/machine/{id}/connection-log(mirrors theupgrade-logendpoint). - The reader resolves the machine's Halibut endpoint URI the same way the health-check + dispatch paths do (
poll://{subscriptionId}/orhttps://host:port/, viaEndpointJsonHelper) so it reads the right log, and projects Halibut'sLogEventinto a transport-agnostic DTO (no Halibut type leaks past the service). Works for both polling and listening tentacles.
Design notes
-
Additive / non-breaking: a new read-only endpoint, a new reader service (
IMachineConnectionLogReader,IScopedDependency), a thin handler (Rule 16), and a one-line runtime wiring that passes Halibut the sameLogFactorytype it already uses internally. No schema change (the log is an in-memory recent-events buffer, not persisted), no changed response shapes, no frontend impact. - The handler clamps the
maxEntriesquery param to[1, 1000](default 200).MachineViewpermission, same as the upgrade endpoints.
Test plan
-
Unit — EndpointJsonHelperConnectionUriTests: URI resolution for polling / listening / unknown-style / malformed / null -
Unit — MachineConnectionLogReaderTests: projects type/message/error/time, returns most-recent-N in chronological order, resolves listening vs polling, empty for no-events / unknown machine / unresolvable endpoint -
Unit — GetMachineConnectionLogRequestHandlerTests: entry-cap clamping (null→default, ≤0→1, >max→cap, in-range passthrough) + delegation + response wrapping -
E2E — Agent_ConnectionLog_RecordsRealHalibutEvents_ForPollingEndpoint: a real Halibut RPC to a polling agent, then reads back non-empty, well-formed connection events — proves theWithLogFactorywiring AND that the polling endpoint is populated (ran green locally, 1/1) -
Full unit suite: 5833/5833 green -
Full solution build: 0 errors