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## Resolutionsentry; 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.