test(oci): drive a docker pull through a hosted-and-remote virtual repository (S32 plan: 18/20)
What does this MR do?
Adds the hermetic end-to-end harness for a container virtual repository: a docker pull-shaped sequence (tag, index, child manifest, config and layers) driven through the dispatcher against a virtual repository that lists one hosted and one remote upstream. It also adds the e2e catalog sections and the run recipe's virtual pull journey. This is Step 18 of the S32 container virtual plan.
internal/format/oci/virtual_e2e_test.golists the hosted upstream at position 1 above the remote at position 2, with an in-process fake registry behind the remote. The hosted repository holds the child manifest, its config and the first layer. The remote's registry holds the whole image. A cold pull resolves each leg to its supplier and pins the exact upstream calls per leg. A warm pull issues zero upstream requests and commits no fill. The file carries no build tag and touches no database, so it runs with the unit suite.docs/testing/e2e/oci.mdanddocs/testing/e2e/docker.mdeach gain a## Virtual repositoriessection. The out-of-scope line for virtual repositories is gone from both.- The container association routes shipped with S17 Phase 6 (
6e883a40d), so the "Upstream associations stay out of scope." sentence the plan asked for was already false. Both catalogs' scope paragraphs now say those routes ship. Their## Upstream associationssection is !2859 (merged)'s (S17 Phase 6 Step 42), whose rows have that stack's route tests as evidence, so this MR does not add one. driver.shgainspull-container-virtualunder the pull-journey anchor Step 14 seeded, andsmokeruns it. To reuse the existing seeding,seed_container_virtualis split into its create and its association, andwrite_remote_layeris lifted out ofcmd_seed_remote. Neither split changes what the existing callers do.
The branch was rebased onto main at 99423bb35 before pushing, to take S30 Step 17's entry in the SKILL.md verification log. Both sides only appended to that log, so the resolution keeps both entries. The branch's per-file contribution is identical before and after the rebase.
Plan and spec amendments
- Step 18 upstream order. The harness lists hosted first, not the
[remote_a, hosted_b]order of S32's "A child manifest resolves on its own" criterion. With a remote listed first, the resolver probes that remote for every by-digest read only the hosted upstream holds, so "the second pull issues no upstream request" cannot hold. Step 15 already covers the child-manifest criterion in the spec's order (TestVirtualManifest_ChildResolvesFromAnotherPosition). - S32 warm-digest criterion. That probe contradicts S32's "Warm digest pull short-circuits", which says a digest held by any upstream resolves at Phase 1. The resolver follows S13's Phase 2 instead. The spec amendment is its own MR for the spec author to review: !3028 (merged). Step 18's plan text cites it as a proposal, which stays true whichever MR merges first.
- Catalog scope. Step 18's Files entry,
### e2e catalog, and the e2e scenario impact paragraph said to narrow the scope sentence to upstream associations. They now say to remove it, and that S17 Phase 6's Step 42 adds the association section. - Step 18 recipe anchors and seams. The Files and Shared seams entries now name the pull-journey anchor Step 14 seeded, and record the two seeding-helper splits.
- Testing Strategy. Step 18 is removed from the integration-tagged list, because its harness has no build tag.
- Size. ~740 becomes ~1,350, as measured.
- Status note. The note under the Status table said step MRs never edit it.
docs/dev/conventions.md(### Plan status-table updates) says a step MR records its own row, so the note now says that, and this MR records row 18.
Merge order
- !3014 (merged) merges first. This MR rebases once it lands. Both MRs append an entry after S30 Step 17's in the
SKILL.mdverification log, so the resolution keeps both entries. !3014 (merged) does not change the smoke count, so 94 stays correct. - This MR merges before !2941 (merged). !2941 (merged) rebases onto it. Both fill rows of the S32 Status table, and the resolved table is: 14 !2940 (merged), 16 !2984 (merged), 17 !3031 (merged), 18 !3029 (merged).
- !2859 (merged) owns the catalogs'
## Upstream associationssection. Whichever of the two merges second removes !2859 (merged)'s "Every … client journey … stays out of scope" sentence, because this MR puts the virtual pull journey in scope.
Acceptance coverage
| Criterion | Evidence |
|---|---|
A docker pull-shaped journey across a hosted and a remote upstream succeeds cold and warm |
TestVirtualProxy_E2EDockerPullSequence (both subtests) |
| The second pull issues no upstream request | TestVirtualProxy_E2EDockerPullSequence: the call snapshot and the fill count are unchanged from the cold pass |
| Both catalog files carry the new section, and neither lists a virtual client journey as out of scope | docs/testing/e2e/oci.md, docs/testing/e2e/docker.md |
driver.sh smoke passes at its new count |
smoke: 94 passed, 0 failed, on the rebased branch (logged in SKILL.md) |
Beyond the plan's criteria, the harness also covers:
- a stale tag confirmed by a conditional Probe (
TestVirtualProxy_E2EStaleTagRevalidatesOn304); - a token-auth upstream, where the token the Probe exchanged for serves the Fetches after it (
TestVirtualProxy_E2ETokenAuthUpstreamExchangesOnce); - the write-verb
405s (TestVirtualProxy_E2EWriteVerbsAreRefused); - a remote refusing every read, where the hosted absence decides the answer and nothing the upstream sent reaches the client (
TestVirtualProxy_E2ERefusingUpstreamLeaksNothing); - a winning remote whose Fetch answers 403, relayed as
DENIEDwith nothing the upstream sent reaching the client (TestVirtualProxy_E2EWinnerFetchRefusalLeaksNothing).
Size
The diff is past the 500-line ceiling:
| Group | Added | Removed |
|---|---|---|
Go test (virtual_e2e_test.go) |
957 | 0 |
Run recipe (driver.sh, SKILL.md) |
328 | 14 |
e2e catalog (oci.md, docker.md) |
60 | 9 |
| Plan | 44 | 27 |
Splitting would not help. The step is a test step: the harness is the test, and it drives one journey through fixtures that only make sense together. The recipe and the catalogs describe the same journey, and the plan puts all three in this step.
e2e scenarios and user documentation
- e2e catalog: updated as described above.
- User documentation: not affected. This MR changes no customer-visible surface.
docs/user/formats/oci.mdis changed by !2941 (merged).
Run recipe
.claude/skills/run-artifact-registry/ changes because the recipe gains a virtual pull journey and its 14 smoke checks. The evidence is the smoke re-run above. No /skill-creator benchmark applies: the change adds a journey the recipe drives, not a change to how the skill triggers. The recipe rides with the step, as it did in !3005 (merged) and !2984 (merged), because Step 18's Files entry puts it in this MR.
Related to #291 (closed)