docs(plans): batch S32 plan updates (2026-09-24)

What this changes

This wave of S32 plan updates books two gaps from the Step 12 review (!2900 (merged), note) on Step 17. It also corrects Step 15's budget rule, books a traceability correction on Step 16 from its validation run, and carries one S32 spec sentence. Each booking goes on the step that owns it.

  • Step 17: S16's fill instruments on a virtual fill. The standalone arm books bookRemoteUpstreamFetch and observeRemoteFetchFill. A virtual read books neither, although its fills tee into the same container-remote cache. Step 17 books both in the Fetcher recording wrapper it creates in virtual_wideevent.go, not in virtual_serve.go. fetchWinner settles every Fetch that does not stream inside Resolve (transport failure, 304, unusable status), so the serve only ever sees a winner that streams and cannot see when the Fetch started. A Fetch that does not stream books its response in the wrapper. A stream settles once, on its first Read that ends or on an earlier Close, and books its response. A complete body also books its fill, with the counted bytes and the time since the Fetch call. A flight follower (FetchResult.Coalesced) books no metric sample, because both helpers Step 14 landed skip it, but it still emits its fetch record. Acceptance and Tests cover the non-streaming answers.
  • Step 17: the winner fields on a failed drain's records. drainFill logs through logVirtualMappedFailure, which writes only error_message. writeVirtualReadError and writeVirtualAbandonedRead take attrs ...slog.Attr, and drainFill passes the failure subject's logAttrs(), so the record names the virtual repository, the winner, and the remote.
  • Step 17: ADR-012's virtual pull events. artifact_pulled carries repository_kind=virtual and cache_hit. virtual_cache_miss carries upstream_type. The pull event fires on a manifest GET whose payload the serve delivers in full, the hosted rule. A HEAD and a blob read emit none. The S32 spec never mentions ADR-012, so Step 17 follows S30's Step 17 for the cache_hit meaning and the miss trigger. S30's Step 17 books the same AnchoredEvent fields and event constant, and whichever step lands first adds them.
  • Step 15: the budget pick is by reference kind. The route arms before Resolve, so it cannot tell a tag miss from a tag revalidation. A by-digest read arms the miss-path sum, and a tag read arms the revalidation expression, miss or not. The rule now lives in ### Every virtual route arms its own write deadline, and the budget is stated against it. Step 15's Scope, Files, Acceptance, and Tests follow it, and its armed-instant table has a by-digest miss, a tag miss, and a stale tag. Step 15 also lists docs/dev/remote-read-budgets.md, which states only the standalone arms today.
  • Step 15: the manifest tail is floored at server.timeouts.write. !3005 (merged) floors the re-arm after Resolve at server.timeouts.write, as Step 16's blob route does, so a raised write timeout survives on a warm serve's body. The research section said Step 15's route does not floor its tail, and Step 15's Files entry named the re-arm to remote.FillTailBudget alone. Both now name the floor.
  • Step 16: the cross-repository blob criterion. The spec's criterion that two virtual repositories and a direct read of their shared remote issue one upstream GET names a blob. The traceability table gave it to Step 15's manifest route, and a manifest test cannot see a blob builder that holds its own flight registry. Step 16's Tests entry now carries the blob case, and Step 15 keeps its manifest twin.
  • S32 spec: the finder read is outside the statement count. The criterion "Statement count follows upstream kinds, not position count" gains one sentence. The read that maps repositories.id to container_virtual_repositories.id runs once per request before resolution starts, so 2 + K leaves it out. The plan already said so, and the spec was silent. The Step 4a finder (!2976 (merged)) issues that read.

An earlier revision also booked a Step 14 entry for coalesced fill booking. Step 14 merged in !2940 (merged) with its own shape (observeRemoteFetchFill), so that entry is gone.

User documentation

This MR also changes one line in docs/user/formats/oci.md, which publishes to docs.gitlab.com. The line now says you can pull images through a virtual OCI repository, because the virtual manifest handler (!3005 (merged)) and the virtual blob handler (!2984 (merged)) have merged. The two overview pages that name virtual repositories get the same update in !3062 (merged).

Merge timing

Merge this after !3005 (merged) (Step 15), whose tail floor the Step 15 entry above records. !3005 (merged) merged on 2026-09-28, so nothing blocks this MR now.

Related to #291 (closed)

🤖 Generated with Claude Code

Edited by Radamanthus Batnag

Merge request reports

Loading
Loading