feat(remote): health monitor probe and status transitions (S13 Step 14, part 3/3)
📦 What this MR does
Part 3 of 3 of S13 Step 14 (health monitoring): the HealthMonitor itself — the probe operation, the status transitions, and the audit trail, over part 1's held client config and part 2's counter seam and fakes.
Probeissues aHEADthrough the step 2 seams (RequestBuilder+UpstreamDoer, under the fail-smallBodyKindMetadatacap) with theprobe_timeoutdeadline on a derived context, reads the prior status, advances it, writes the durablelast_health_status/last_health_checked_atas a compare-and-set on the probe timestamp that returns the status it replaced — the transition is judged on that, never on the pre-probe read, so interleaved probes cannot drop a recovery event, and an overtaken probe writes nothing and audits nothing. The recording phase runs on a context detached from the caller so an exhausted caller budget cannot swallow the streak update and the write, andRecordedtells a caller whether anything persisted, and audits every transition plus every manual probe (From == Towhen nothing changed — the operator-ratified no-new-field shape).- Reachability is open on the 4xx side: any answered status below 500 is reachable, and a probe failure is a transport error, a timeout, or a 5xx — the spec's unhealthy row. An answer proves the upstream is up, so a
405on a HEAD to the base URL (common on CDN-fronted mirrors) no longer takes an upstream with a working GET path out of resolution permanently. The taxonomy decision is being recorded on work item 320 alongside the 304 mapping. - Transitions follow the consecutive-failures contract:
unknownholds belowunhealthy_threshold, the threshold flipsunhealthy, one reachable probe restoreshealthyand resets the streak. Counter errors degrade instead of failing the probe: an uncountable streak never flips a remote unhealthy (WARN-logged, status held), and a reachable upstream sets healthy no matter what the counter says. - Counter errors never fail the probe and never flip a status: an uncountable streak holds the current one, and such a degraded probe writes nothing durable at all —
last_health_statusandlast_health_checked_atrecord conclusive probes only, so a held status cannot read as freshly confirmed. A counter call that fails with a context error logs at DEBUG, so shutdown raises no false cache alarm. One window is accepted and documented on the type: aResetthat fails during a cache outage leaves a stale streak (bounded by the counter TTL), so one failure after recovery can flip unhealthy early, worth at most one sweep interval and healed by the next passing probe. The complete fix is the cache-side atomic reset-and-count in #443; the process-local mark that tried to close it was removed after two demonstrated same-remote races. - Caller abandonment is cancellation only, the rule
Prober.Probefollows: a canceled caller gets the abandoned error — wrapping both the context error and the transport or build error, so a sweep's shutdown check can match either witherrors.Is— while any deadline expiring, the caller's own or the derived per-probe one, is an upstream that did not answer and counts as a failure. - Wiring and policy are guarded at the edges: a target with no
Builder, or a monitor with no doer, returnsErrNotImplementedbefore any read or write (one unresolvable remote must not panic the sweep goroutine), andNewHealthMonitorrejects a non-positiveProbeTimeoutorUnhealthyThresholdthe wayNewFailureCounterrejects the field it owns.
Plan drift, called out per the drift carve-out: the tests exercise the canonical remotetest fakes rather than the plan's httptest sketch — the step 9 precedent; no concrete adapter exists until step 11.
⚙️ Why stacked
Step 14's full diff is ~1.8k reviewable LoC, so it ships as 3 stacked MRs, reviewed and merged bottom-up. This part is the top: parts 1 (composition-root wiring) and 2 (failure counter + fakes) are below. Its ~1k reviewable LoC is a dependency-forced indivisible unit — nearly every subtest exercises the one Probe entry point, so the tests-with-code rule forbids cutting the suite from the type.
🔗 References
- Plan:
docs/plans/2026-07-16-s13-virtual-remote-foundation.md— Step 14 - Spec:
docs/specs/S13-virtual-remote-foundation.md— health monitoring: probe, status transitions, failure counter, audit events
🎯 Spec coverage
| Acceptance / contract point | Test |
|---|---|
| Reachable set (any answered status below 500; boundaries pinned at 200 and 499) → healthy | TestHealthMonitor_Probe_ReachableStatuses |
| 5xx fails (boundary pinned at 500) | TestHealthMonitor_Probe_UnreachableStatuses |
| Overtaken compare-and-set write audits nothing | TestHealthMonitor_Probe_StaleWriteSkipsAudit |
| Transition judged on the status the write replaced | TestHealthMonitor_Probe_AuditsAgainstTheReplacedStatus |
Recorded reports persistence for applied, overtaken, and degraded probes |
TestHealthMonitor_Probe_RecordedReportsPersistence |
unknown holds below threshold |
TestHealthMonitor_Probe_UnknownHoldsBelowThreshold |
| Transport failure is a failed probe (status 0) | TestHealthMonitor_Probe_TransportFailureIsFailedProbe |
Threshold flips unhealthy |
TestHealthMonitor_Probe_ThresholdFlipsUnhealthy |
Success resets streak, restores healthy |
TestHealthMonitor_Probe_SuccessResetsStreakAndRestoresHealthy |
Manual probe always audits, From == To on no change |
TestHealthMonitor_Probe_ManualTriggerAlwaysAudits |
| Counter outage degrades, never flips, never writes; accepted stale-streak window | TestHealthMonitor_Probe_CounterOutageDegrades |
| Cache outage warns, shutdown stays at debug | TestHealthMonitor_Probe_CounterErrorLogLevel |
| Repeat unhealthy scheduled probe stays audit-silent | TestHealthMonitor_Probe_RepeatUnhealthyStaysSilent |
| Build/store/audit seam errors propagate | TestHealthMonitor_Probe_SeamErrorsPropagate |
| Canceled caller → abandoned error; any expired deadline → counted failure | TestHealthMonitor_Probe_CallerAbandonmentPropagates |
Unwired builder or doer → ErrNotImplemented, nothing read or written |
TestHealthMonitor_Probe_UnwiredDependenciesAreNotImplemented |
Non-positive ProbeTimeout or UnhealthyThreshold rejected at construction |
TestNewHealthMonitor_RejectsNonPositiveConfig |
HEAD-only, BodyKindMetadata, derived probe_timeout deadline |
TestHealthMonitor_Probe_RequestDiscipline |
| Health path passes through to the builder | TestHealthMonitor_Probe_HealthPathPassesThrough |
| Trigger names (scheduled/manual) | TestHealthProbeTrigger_String |
🔬 e2e scenarios
No scenario added or affected: the monitor is format-agnostic service-layer plumbing with no reachable request path until steps 11/12 wire resolution and step 15 schedules the sweep and the on-demand entry.
Related to #335 (closed)