docs(plans): resolve the S30 prober's terminal-versus-positional exposure

Summary

Clears the S30 plan's one open item — the virtual prober's terminal-versus-positional question (Steps 6, 15, and 17) — so the implement-step workflow can dispatch against the plan.

Decision: the failure is positional, and it carries the data error. A request-build failure fails the position that read it rather than the resolution: the failed read surfaces as a probe error carrying a positional marker, which the resolver's positional-credential arm admits — the position is recorded failed, marked a data error, and resolution carries on. The mark keeps a sibling's 404 from becoming the client's answer: the read never consulted the upstream, so the list establishes no absence. Step 6 states the mechanism — the builder's read returns the marker, and the read-side causes need a new positional marker in internal/remote admitted by probeErrorIsPositional beside the two markers main already routes, with no slice-side re-map that would swallow them. The decision is recorded once at the resolver seam — a new ruling in S13's Probe contract — so every virtual slice carries it: npm's half-terminal state is named (refused and unopenable causes already positional, row-read causes terminal) with the conforming change booked on the S31 workstream before its handler steps expose a production route (#1334), and S32's Step 9 precedent is corrected here so its token-acquisition failure books the data error too. The decision and its reason are recorded in a new ## Resolutions section; ## Open Questions is now None., and Step 6's entry cites the resolution instead of the open question.

The reason that operates: one position's failed credential read is evidence about that position's credential state, not about what the remaining positions hold, so a terminal mapping answers 500 for every request over the composition for a fault one remote position owns. The read's window carries its condition: the read shares resolution.probe_timeout with the HEAD while the foundation applies that timeout around the whole Probe, and S32's Step 9 split moves it under the resolution budget; Step 6 states the condition and its event and names the merge order (it lands before the split). The exposure is bounded as the pair: one credential read per probed position, at most resolution.probe_concurrency reads per wave against the application pool's connections — pgxpool's max(4, NumCPU) default, which the service never sets itself, so the deployed DSN decides — and Step 17's run-recipe entry books the production-route sizing check the pair requires. The fixture pair includes the separating case: one credential-failed position alongside a ProbeMiss answers unavailable rather than not-found, since a healthy companion wins before serveNoWinner runs.

The riding corrections keep spec and plans in agreement: S30's spec line records the positional ruling, the data error, and the window condition; S13's Probe contract carries the ruling every slice inherits, and scopes its acquisition-budget rule to acquisitions over a wire leg of their own, so a local credential row read charges to the surrounding budgets — the probe window until S32's Step 9 split, the resolution budget after — with the narrowing recorded in S13's Dependencies; S32's plan no longer claims S30 and S31 "inherit it at no cost" — both slices acquire credentials inside a Probe, so its Step 9 split moves both reads under the resolution budget — and its Step 9 and credential-shape notes are corrected as above.

Why this amendment

The implement-step skill hard-stops on any plan whose ## Open Questions section is not clear (precondition 4). The question did not affect Step 1 (schema, no predecessors), but the gate is unscoped, so the section must be clear before any dispatch. This MR records the operator's decision — positional for all slices, recorded in S13 — taken on the reviewer's escalation that Maven and the container slice were about to ship two different answers for the same failure class.

Size

247 changed lines across four files:

  • docs/plans/2026-09-10-s30-maven-virtual.md — 131 additions, 31 deletions (the ## Resolutions entry; Step 6's prober mechanism, mapping, window condition, acceptance, tests, and sidecar entries; the merge order on its Depends on line; and Step 17's sizing booking).
  • docs/specs/S13-virtual-remote-foundation.md — 34 additions, 4 deletions (the Probe-contract ruling every slice inherits, and the acquisition-budget rule scoped to wire-leg acquisitions).
  • docs/plans/2026-08-27-s32-container-virtual.md — 29 additions, 14 deletions (the corrected inherit claim, the Step 9 data-error booking and separating fixture, the credential-shape split, and the scoped quote).
  • docs/specs/S30-maven-virtual.md — 3 additions, 1 deletion (the ruling and the window condition).

A plan-plus-riding-spec-change is one approval unit: the plan is the gate that records the decision, and the spec lines are the same decision stated in each spec's own copy.

Testing

Docs-only: no code changes. Markdownlint, Vale, lychee, and gitlint passed through the pre-commit chain on every commit.

Edited by Moaz Khalifa

Merge request reports

Loading
Loading