feat(relay): JSON /healthz with version and connection counts (#10)#18
Conversation
Replace the plain-text "ok" handler with a JSON response carrying status, version, current binary/phone counts, and uptime in seconds. Endpoint remains unauthenticated by design; aggregate counts are an explicit operational tradeoff (no per-server-id breakdown). The new handler lives in internal/relay/healthz.go as a factory returning http.Handler so future per-handler state (logger, clock) can be added without changing the call site. main now constructs startedAt post flag-parsing and a Registry handle (idle until WS upgrade tickets land) and wires both into the handler. Sets Cache-Control: no-store to keep intermediaries from serving stale counts. Tests cover response shape (status, headers, all five JSON keys, body under 200 bytes) and registry-state tracking (claimed binaries and registered phones reflected in the counts).
Code Review: #10Decision: PASS Findings
SummaryThe diff matches the architect's spec exactly: new Doc-comment on Tests cover all 9 AC bullets across two cases. Security-sensitive label honoured: the architect's spec contains a
|
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
What
Replace the plain-text
"ok\n"/healthzhandler with a JSON response containing five fields:status,version,connected_binaries,connected_phones,uptime_seconds. Endpoint remains unauthenticated and serves identically to every HTTP method (no behavioural change there). AddsCache-Control: no-storeso intermediaries can't serve stale counts.The handler is a factory (
relay.NewHealthzHandler) returninghttp.Handler— keeps the door open for adding per-handler state later (logger, clock injection) without rewriting the call site.mainnow also capturesstartedAtafter flag parsing and constructs a*relay.Registry; the registry is idle in v1 (no WS upgrade handlers yet) but holding it now means #4/#5/#16 don't have to refactormain.Issue
Closes #10.
Testing
internal/relay/healthz_test.goadds two tests:TestHealthz_ResponseShape— covers status code, both response headers, all five JSON keys present and well-typed, body under 200 bytes, and a uptime ≥ 30s when constructed with a 30s-oldstartedAt. Uses a genericmap[string]json.RawMessagedecode in addition to the typed decode so a Go field rename that broke the wire contract would still fail.TestHealthz_TracksRegistryState— populates a registry with 2 binaries and 5 phones across 2 server-ids, asserts the handler reports those counts.go test -race ./...clean.go vet ./...clean.go build ./cmd/pyrycode-relayclean.Architecture compliance
internal/relay/healthz.goandinternal/relay/healthz_test.go, single edit tocmd/pyrycode-relay/main.go.healthzResponseis unexported; field order matches the AC's wire-key order.Counts()call per request and releases the registry RLock before any response I/O (matches the established "copy under lock, do slow work outside" pattern).time.Sincefloored at zero — defence-in-depth per the spec, not observable today but cheap to keep.json.Marshalof a fixed primitive struct cannot fail; discarded error documented inline. Marshal-then-write keeps the response atomic.encoding/json,net/http,time).Cache-Control: no-storeset, header values are constant strings.🤖 Generated with Claude Code