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

What this changes

This wave of S32 plan updates records five Status rows and two sets of cross-step obligations. The first set came from the review of Step 11 (!2842 (merged)), and the second from the review of Step 12 (!2900 (merged)). A step branch cannot edit the plan, so each one goes on the entry of the step that owns it.

  • Step 14 names oci.NewVirtualResolution in the ManifestMaxPayload field comment in remote_operations.go. That comment says both constructors take their bounds from ociRemoteManifestBodyBounds. Step 14 builds the facade from the same selector, so the sentence goes stale at that step. The comment is a 22-line block that predates the comment caps, and the entry says so.
  • Steps 15 and 16 answer a malformed digest with 400 DIGEST_INVALID before any resolution runs. The manifest route uses readReferenceDigestInvalid, and the blob route uses gateRemoteBlobDigest. Step 11's mapping answers ErrVirtualHostedReference with 500 and relies on this check. Without the check, a client can drive 500s at ERROR with a bad digest.
  • Step 17 appends namespace_id and repository_id to the request's context logger in both virtual handlers. Step 11's error mapping writes no repository identity, so an operator can tie a virtual 503 to its repository only by a correlation_id join.
  • Status table: rows 4, 10a, 10b, 11, and 12 now record !2756 (merged), !2831 (merged), !2836 (merged), !2842 (merged), and !2900 (merged).

From the Step 12 review, the virtual download record that !2907 (merged) adds to S32:

  • Step 12 records a delivered read's download on the supplying position. Its Files entry adds virtual_assembly.go, virtual_assembly_test.go, emit.go, and docs/dev/storage-accounting.md, which !2900 (merged) already changes. It also adds remote_download.go, whose header lists the callers of record. !2900 (merged) does not change that file yet.
  • Step 14 carries the cache surface Steps 15 and 16 read on ociVirtualWiring: an oci.RemoteReadCacheFor and the remoteStalenessWindow(remoteCfg) value each virtual arm hands it. It also books VirtualPosition.RemoteRepository, the winner's remote row that the factory takes. Neither route step can declare a wiring field.
  • Steps 15 and 16 fill virtualServeRead.rows with the winning remote repository's RemoteReadCache, never the Phase 1 memo. Their builders hand virtualServeHandler.counters the process's CounterSink. Without the cache surface, the serve answers a remote winner 500. Without the sink, a hosted supplier books no download.
  • Step 17 adds the virtual serve to the downloads_count dispatchers named in docs/dev/observability.md.

Merge timing

Merge this after !2907 (merged), because Step 12's scope cites the download rule that !2907 (merged) adds to S32. Merge it before the Step 15 and Step 16 MRs open, because their entries gain an Acceptance criterion and the serve wiring here. !2917 (merged) gates the same two steps with the single_flight_wait_timeout budget term. The two MRs share no lines, so either can merge first.

Status rows 14 to 18 go in a later wave, once those MRs open.

Related to #291 (closed)

🤖 Generated with Claude Code

Edited by Radamanthus Batnag

Merge request reports

Loading
Loading