Loading
feat(remote): Probe operation over the upstream doer seam (S13 step 9)
📦 What this MR does
S13 Step 9: Prober, the remote repository's Probe operation — the parallel existence check the virtual resolver fans out across remote upstreams to decide which of them holds a file without transferring bodies.
- A
HEADthrough the slice-providedRequestBuilderand the security-hardened client behind theUpstreamDoerseam (!1109 (merged)), under the fail-smallBodyKindMetadatacap, carryingIf-None-Matchexactly when the caller supplies a stored ETag. - Taxonomy mapping per the spec:
200→ProbeHit, conditional304→ProbeFresh,404→ProbeMiss, any other status →ProbeErrorcarrying that status. A transport failure — blocked URL, unsafe redirect, and probe deadline included — is positional data for the resolver (ProbeError, zero status, nil error); onlycontext.Canceledpropagates as a Go error, so the resolver can tell caller abandonment apart from upstream failure. - Unsolicited
304maps toProbeHit, notProbeFresh(ratified during implementation): a304answering a HEAD that sent noIf-None-Matchis a protocol violation but still an existence claim; mapping it toProbeFreshwould make the resolver serve a cached copy that a fresh-miss position lacks and a stale no-ETag row must not serve on age alone.FetchFreshFromCachegets the mirror note so the Fetch stack inherits the decision at the taxonomy layer; the spec's unqualified304 → ProbeFreshwording is queued for the spec-amendment work item (see follow-ups). - Seam-contract hardening from the review rounds:
RequestBuilder.BuildRequestmust bind the returned request toctx;UpstreamDoer.Domust return a non-nilBodyon a nil error and keep context errorserrors.Is-visible — each stated on the interface so the step 11 production adapter has a normative contract to conform to. remotetestupgrades the probe tests need and later steps reuse:FakeUpstreamDoernormalizes a scripted nilBodytohttp.NoBodyon a shallow copy (mirroring the established nil-Header normalization), plus a shared mutex-guardedCloseRecorder.- Step 9 plan edits under the documented drift carve-out:
Depends on:gains step 7 part 1 (theUpstreamDoerseam Probe consumes —internal/remotecannot importupstreamhttpback without a cycle), and theTests:line is corrected fromhttptestto the canonicalremotetestfakes (no concrete adapter exists until step 11).
🔗 References
- Plan:
docs/plans/2026-07-16-s13-virtual-remote-foundation.md— Step 9 - Spec:
docs/specs/S13-virtual-remote-foundation.md— Probe, virtual resolution Phase 2, security covers
🎯 Spec coverage
| Acceptance / contract point | Test |
|---|---|
200/304/404/other status taxonomy |
TestProber_Probe |
Conditional HEAD with matching ETag → ProbeFresh |
TestProber_Probe + TestProber_Probe_RequestDiscipline |
Unsolicited 304 → ProbeHit |
TestProber_Probe |
| Blocked URL / unsafe redirect / header injection / deadline → positional failure, nil error | TestProber_Probe_TransportFailureIsPositional |
HEAD-only, BodyKindMetadata, no cap override, conditional-header discipline |
TestProber_Probe_RequestDiscipline |
context.Canceled propagates as the only doer-side error |
TestProber_Probe_CanceledPropagates |
| Request-build failure propagates, no upstream call | TestProber_Probe_BuildErrorPropagates |
| Response body closed | TestProber_Probe_ClosesResponseBody |
Nil-seam guard → ErrNotImplemented |
TestProber_NilSeams |
| Fake nil-Body normalization on a shallow copy | TestFakeUpstreamDoer_NormalizesNilBody |
🔬 e2e scenarios
No scenario added or affected: Probe is format-agnostic service-layer plumbing with no reachable request path until step 11 wires the production adapter and step 12 composes it into virtual resolution.
🚧 Follow-ups
- Consolidate the two test-local close-recording bodies in
internal/remote/upstreamhttpontoremotetest.CloseRecorder(touching those files is outside this step's scope). - Spec amendment: qualify the
304 → ProbeFreshrows with the conditional-probe precondition and record the unsolicited-304 →ProbeHitrule — folds into #320 (closed).
Related to #330 (closed)