docs(plans): S31 Step 12 declares no configuration surface
What
Edits docs/plans/2026-08-21-s31-npm-virtual.md so Step 12 declares no configuration surface, and so the rest of the plan agrees with that and with the S13 DRI's resolver-deadline ruling. Everything here is about resolve_timeout. This MR moves no code: it is the plan catching up with where the key lands and where the budget is applied, both of which are S13's.
The passages, in the order they were corrected:
| Passage | Change |
|---|---|
Step 12 Files |
The proto, loader, config.example.yaml and configuration-reference bullet is replaced by "no configuration surface", naming #1069 (closed) as the MR that carries them |
| Step 12, the deadline paragraph | The derivation moves to S13. Now states S13's value and scope: 90s, armed inside virtual.Resolver, released at winner selection |
## Dependencies |
Names !2249 (merged), corrects what the amendment actually does, and records the S13 code MR that sits between it and Step 12's |
| Step 12, the amendment-contingent paragraph | "the config default this step lands" becomes the S13 MR's; the re-derive list is recounted |
Step 12 virtual_operations.go bullet |
"S31 is the first virtual slice, so it lands the shared key" is replaced by a correction note naming #1069 (closed) |
## Research Findings |
Retitled to name the ruling the plan does override, with a correction note recording what it originally claimed |
## Naming Conventions |
Becomes "No new configuration of S31's own"; the reversal claim is dropped |
## Open Questions |
The key described as one "that reverses the spec's 'no configuration' ruling" becomes the key S13 declares |
| Step 12, the deadline mechanism | Marked unreconciled against the S13 DRI ruling, naming the S32 plan section that carries it. Retained as pre-ruling text, not rewritten |
| Step 12, the follower paragraph | Marked pre-ruling: S13 puts the follower wait outside the budget, bounded by single_flight_wait_timeout alone |
| Step 12, the truncation passages | The consequence is no longer attributed to S13, which rules against it. The cold-fill truncation assertion is called out as a test Step 12 must not write |
Step 12 Tests, the deadline case and fixture |
Marked pre-ruling; the fixture carries 90s |
Why
Two things belong to S13, and the plan claimed both.
The key. It sits on the virtual_repositories.resolution block S13 declares, the bound is a property of the resolution rather than of npm, and S30 and S32 need the same one. This plan's ## Dependencies already said so, so Step 12's Files list was the passage out of step with the plan's own decision.
The mechanism, and the value. The S13 DRI ruled that the budget is armed inside virtual.Resolver, scoped to Phases 1 and 2, and released at winner selection, so the single-flight follower wait and the transfer both sit outside it. The same ruling sets the default to 90s. Both are recorded on main in docs/plans/2026-08-27-s32-container-virtual.md, which says in terms that S31's Step 12 moves with it, and !2249 (merged) carries the ruling in spec form.
The plan had built the budget on Step 12's facade and kept a 40s default with a zero-slack consequence derived from it, attributing that consequence to S13. S13 rules against it: with the budget released at winner selection it never spans a fill, so it cannot truncate one. Left as it stood, an author implementing Step 12 would write a facade-side budget that cuts single-flight followers and truncates cold fills, and a test asserting the truncation, all of which S13's acceptance criteria say must fail.
A second review round then found seven more, all fallout from the alignment commit or passages it had missed:
| Passage | Change |
|---|---|
## Dependencies, the #1069 (closed) gate |
Gated Step 12 on "the facade's parameter". The gate moves to Step 14, which is the step that reads the key |
| Step 12, the pre-ruling fence | Started 61 lines after the claim the ruling reverses and ran 14 lines past it. The reversed claim gets its own in-place note; the fence names its own end |
Step 12 Tests |
A stranded "Plus ..." item claimed loader round-trip cases for a key #1069 (closed) owns. Dropped |
| Step 12, the deadline paragraph | Quoted npm's 10s term four lines above "does not restate the arithmetic". The value is sized against container's worst case, so the arithmetic is dropped and the claim reworded |
Step 14 Files |
Three sentences gave it the config read and it recorded nothing. Now names how the value reaches the resolver, and remote.ResolutionConfig.ResolveTimeout |
## Dependencies, the !2249 (merged) list |
Named three corrected surfaces; the amendment changes four. resolution_outcome's budget_expired added |
## Naming Conventions and ## Research Findings |
Both attributed the "no bound anywhere" ruling to the spec's "deliberately not added here" sentence. It comes from the client's-own-deadline sentence instead |
A third review round found eight more non-blocking findings, seven of them in this MR's scope:
| Passage | Change |
|---|---|
## Research Findings, the override citation |
Certified two spec sentences as reading on main. !2249 (merged) replaced the paragraph they lived in, 48 minutes before the previous commit was written. The provenance is historical now and the ruling verbs are past tense; the quotations stay, because the override argument needs them |
## Dependencies, the four-surface list |
Named the Error Cases abandonment row, which !2249 (merged) leaves byte-identical. The correction is an added Budget expiry row, now named, with the addition called out as such |
| Step 12, the pre-ruling fence | Enclosed a live prohibition. "The truncation half is not a test Step 12 writes" gets its own in-place carve-out, and no longer calls itself the pre-ruling text |
Step 12, the fence's ErrResolverCancelled carve-out |
Pointed at Step 12's Acceptance for a restatement that is not there. Now points at the virtual_read_errors.go Files entry, and says what Acceptance does cite |
Step 14 Files |
Claimed a per-field read of the key. remote.ResolutionConfig is populated field by field only in config.RemoteConfigFrom and threaded whole from there, so the bullet describes the threading and re-grounds the #1069 (closed) gate on Step 14 being where the resolver is built |
| Step 12, the amendment-contingent paragraph | Said virtual.NewResolver already carries the rejection a non-positive duration needs. It carries four arms, none of them that one. Reworded to the error return and the idiom a fifth arm follows, which keeps the count at four |
| Step 12, the in-place ownership note | "the rest of this paragraph" pointed at nothing after the previous commit split the paragraph. It now names the arithmetic below in the bullet |
| Three passages, on the surviving need | Called it an end-to-end bound. The ruling gives the key the resolution decision only |
Follow-ups
Two findings land outside this MR, and both are handled:
- #1069 (closed)'s
## Merge orderand## Notesstill gated Step 12 on the facade's constructor parameter, the premise this MR retires. Item 3 now names Step 14, the composition root that builds the resolver from the configuredremote.ResolutionConfig, and records what it originally claimed. The issue's two40sfigures went stale when !2249 (merged) sized the key at90sonmain; both are corrected. No change here was needed for any of it. - #1088 (closed) tracks stale cardinality figures the round found and deliberately left out of scope, because this delta touches neither passage. The plan's two sites and #899 all state an 18-value
outcomeset, a 13-valueresolution_outcomevocabulary and a 30-value union; measured ateae544e3cthose are 25, 15 and 39.
Scope, and what is deliberately not here
Reworking Step 12's facade-side deadline mechanism onto the resolver-side seam is a follow-up MR. Those paragraphs are fenced as pre-ruling text and left in place rather than rewritten, so this MR stays a documentation correction and the mechanism change gets its own review. The fence names its own start and end rather than a line span, because spans in a file this MR is editing go stale between its own commits.
Two consequences are named in the plan but deliberately not applied, because they land only when that seam moves: virtual.NewResolver's boot-time rejection count going from four to five, and the cold-fill truncation test being replaced rather than merely forbidden.
Two facts are hedged rather than asserted. transfer_idle_timeout is S13's key at a 60s default, but S16's spec records it as reaching no configuration message and no client yet (#269), so the plan says that rather than claiming the transfer is bounded in practice. And the re-derive list that said four surfaces move with the value is recounted to two against the text as it now stands.
The Dependencies correction
That bullet said the amendment "reverses the S31 introduces no configuration ruling". It does not have to, and !2249 (merged) does not. The sentence is true whenever the field belongs to S13, so it stays as written. What the amendment corrects in S31 is narrower, and it is four surfaces: the ## Resolutions entry's premise that every knob a virtual read needs already existed, the No aggregate deadline of its own passage, the Error Cases table, which gains a Budget expiry row recording that resolution.resolve_timeout running out must not be folded into the abandonment row above it, and the resolution_outcome value list, which gains budget_expired. The Error Cases correction arrives by addition: the abandonment row itself is left byte-identical.
The bullet is amended in place with a note saying what it originally claimed, rather than silently substituting the corrected fact. The ## Research Findings, virtual_operations.go and deadline passages are corrected the same way, for the same reason.
Relationship to !2249 (merged)
Independent, not stacked. This branch is cut from main and touches only the plan; !2249 (merged) touches only the two specs. Neither needs the other's content to apply. The logical order is !2249 (merged) first, since this plan text points at it, but a merge in either order is correct.
Merge order for the work itself, which this MR records in ## Dependencies:
- !2249 (merged), the spec amendment declaring the key on S13.
- #1069 (closed), the S13 code MR for the proto field, the loader default and validation,
config.example.yaml, the configuration reference and the regeneratedgen/**, with the five loader tests. That work is written and green on #893 (closed)'s branch and moves out of it wholesale. - #893 (closed)'s own MR, which then declares no configuration and keeps its own deadline text.
Notes
No step MR is touched and the ## Status table is not edited, per the single-writer guardrail. No e2e scenario is affected: the change is a plan document.
Related to #893 (closed)