chore(remote): extract the record-free reachability seam (S17 Namespace Connection Test plan: 1/2)

Summary

The namespace connection test (S17 Phase 6, the pre-create probe behind the monolith create form's Test connection button) needs a record-free probe. HealthMonitor.Probe records by contract (failure streak, health columns), and npm's and container's probe request builders are unexported, so submitted values can't be probed. This step extracts the round trip and classification into remote.ReachabilityChecker (the monitor delegates, so the sub-500 rule keeps one copy) and exports the npm and oci NewHealthProbeRequestBuilder constructors. Step 2 (the endpoint) consumes these seams and follows stacked on this branch. Plan: the merged docs(plans): plan the namespace connection test (!1933 - merged) • Hayley Swimelar • 19.4.

872 reviewable LOC against the 500 guideline: 543 test (reachability_test.go, the npm and oci builder tests, and the read-order pins in health_test.go), 325 production (97 of them HealthMonitor.issueProbe moved onto the checker and counted twice as delete-plus-add), and 4 config-doc lines. Splitting would land the checker without its tests.

Ride-along from review: config.example.yaml and the configuration reference said an explicit 0 on max_idle_conns_per_host means unlimited. net/http reads a zero there as its default of 2, so both lines now say so.

Spec coverage

# Criterion Tests
A-1 Any answered sub-500 status classifies as passed, 405 and 429 included TestReachabilityChecker_Check_ClassifiesAnsweredStatuses (200/204/302/401/404/405/429/499 rows)
A-2 A 5xx classifies as failed, carrying the status TestReachabilityChecker_Check_ClassifiesAnsweredStatuses (500/503 rows)
A-3 A transport failure classifies as failed with no status, and no error TestReachabilityChecker_Check_TransportFailureIsFailedWithNoStatus (dial row)
A-4 A covers-denied URL surfaces as the same failed-with-no-status verdict TestReachabilityChecker_Check_TransportFailureIsFailedWithNoStatus (covers row)
A-5 A request-build failure surfaces as an error, never a verdict TestReachabilityChecker_Check_BuildFailureIsAnError
A-6 Caller abandonment surfaces as an error keeping ctx.Err() Is-visible TestReachabilityChecker_Check_CallerAbandonmentIsAnError
A-7 A non-relative HealthPath is refused with ErrHealthPathNotRelative TestReachabilityChecker_Check_RejectsNonRelativeHealthPath
A-8 Constructor rejects a nil doer and a non-positive timeout, and a valid pair builds TestNewReachabilityChecker
A-9 Every arm that received a response closes its body, the 5xx arm included TestReachabilityChecker_Check_ClosesResponseBodyOnEveryAnsweredArm
A-10 npm builder attaches the bearer token exactly when one is supplied TestNewHealthProbeRequestBuilder_HeaderMatrix (npm)
A-11 Container request carries no Authorization header at all TestNewHealthProbeRequestBuilder_BuildsUnauthenticatedBaseProbe (oci)
A-12 Existing monitor suites pass, and the refusal tests also pin that nothing is read first Pre-existing suites, plus ReadCalls pins in TestHealthMonitor_Probe_UnwiredDependenciesAreNotImplemented and TestHealthMonitor_Probe_RejectsNonRelativeHealthPath

Pass-2 review added test-parity rows (a 199 boundary, credential-precedence, deadline and inner-error assertions, npm and oci suite symmetry), so the test names in the diff are a superset of this table.

Governing ADRs

None. The extraction keeps the probe's behavior unchanged, and the plan records no ADR obligation for this step. The ADR-021 and ADR-009 amendment obligations it names attach to Step 2's route and mapping, and they land through the handbook.

Testing

Unit suites for the three packages: go test ./internal/remote/... ./internal/format/npm/... ./internal/format/oci/.... The monitor suites pass with no edits beyond the read-order pins and the exported no-host marker, which is the no-behavior-change proof, the pinned nil-doer subtest included. Full CI pipeline on the MR.

Related to #812 (closed)

Context for LLM agents

Rationale

  • A no-record mode on HealthMonitor. Rejected because a monitor requires reader, writer, counter, and audit seams the connection-test endpoint must not hold.
  • A second HEAD-and-classify copy for the endpoint. Rejected because it duplicates the sub-500 classification rule.
  • Exporting the builder types. Rejected as a wider surface than the one call site needs, so only the constructors are exported.

Non-goals

  • The endpoint, handler, wiring, OpenAPI and Bruno entries, and audit event. Step 2, stacked next on this branch.
  • Close-marking of probe requests. The endpoint (Step 2) marks its own requests.
  • Exporting maven's builder. It is already exported.
  • Matching the plan's url parameter name. It shipped as baseURL to stop shadowing net/url.
Edited by Hayley Swimelar

Merge request reports

Loading
Loading