docs(plans): add the monolith S06 Phase 2 container manifest detail plan

Summary

monolith/S06's spec owns Phase 2 outright, so no spec work precedes this. What was missing was the contract: AR served no single-manifest read, because the by-digest route was DELETE only and the manifests list carries no digest filter, so a detail page could not address its own manifest in a bounded number of requests. docs(specs): declare the S17 container-redesign... (!2317 - merged) declared that contract on 2026-09-04, which is what makes the page buildable.

This plan decomposes the Docker and OCI manifest detail page into 17 steps. The Vue surface runs on a local Apollo layer mirroring the declared contract field for field; the client read, the two schema steps and the teardown replace it once Container manifest reads for the UI: detail by ... (#1150) implements the routes.

Important: the plan is partly retrospective, and it says so rather than pretending otherwise. Steps 0a, 0b, 1 and 1a opened before this MR was raised and 0a, 0b and 1 have merged; Steps 2, 3 and 4 opened while this MR was in review. So this plan gated none of them. That departs from docs/dev/agentic-development.md's "No step MR may open before the plan lands on main", knowingly. Each carries its real state inline, in the shape docs/plans/monolith/2026-08-20-repository-detail-remote.md established for the same situation.

What changed in review

Twenty-three findings from @rchanila and @narendran-kannan, across 23 threads. The four blocking ones and the two scope changes are worth a reviewer's attention on the second pass:

  1. The Manifest payload tab is now deferred within the plan, not withdrawn from it. The withdrawal rested on a precedent that does not exist — it cited "the reason Phase 1's npm Manifest tab went", and that tab did not go: it is in S06's Phase 1 tabs table, has its own ### Manifest tab (npm) section, an acceptance criterion, and is ### Step 19 of the Phase 1 plan with an unfilled Status row. It also renders the filtered package.json off a GraphQL field rather than a raw-bytes endpoint, so the borrowed gate was never its gate. Since S06's Phase 2 table lists "the raw Manifest tab" inside The Docker and OCI page itself, dropping it would leave the spec promising a capability no plan builds. Step 7 keeps its place with its two real gates named: #1150 serving the route, and a settled GraphQL shape for a raw-bytes resource in an area ADR-009 leaves unreconciled.
  2. Step 11 folds into Step 15. The list row link now lands in the same MR as the real read behind it, matching the standup decision that the manifest list should not link to the detail page until the GraphQL integration is complete. Step 15 gains manifests_table.vue, its spec, and router/index_spec.js; its :js feature spec drives list-to-detail over real data instead of a deep link, which is better coverage than either step had alone. The S11 node has left the diagram and the row records where the work went.
  3. Step 2 and Step 3 cited spec sections that describe the Phase 1 page. S06's tab table carries a Maven row and an npm row and no container row; its sidebar section lists Repository, Size, Published, Source — no Image, no Platforms, no Referenced by, and Size and Published second and third where this page puts them fifth and sixth. Both steps now say the composition comes from the prototype, and ## Open Questions carries the ask, since under guardrail 1 an unspecified surface is a stop-and-ask.
  4. The kebab writes are Phase 2, not "a later phase". S06's Header says "every item the design puts there is a write or a download that arrives with Phase 2". They are out of this plan, which is a different claim, and docs/plans/monolith/2026-09-01-artifact-delete-surface.md already owns them behind a spec promotion MR. That plan's Steps 13–15 also edit repositories/versions/manifests_table.vue, which Step 15 now does too — recorded per guardrail 26, because no pipeline reports it.
  5. Step 1a is new, for gitlab-org/gitlab!254224 (merged). It came out of a review thread on Step 1 and puts a versions/ literal on the version detail route, so neither route can claim the other's segment. It supersedes the reasoning in this plan's route finding, though not its conclusion.
  6. Only Step 15 is gated on #1150 serving its routes. The preamble had claimed the whole schema track was. Steps 12 to 14 test over stubbed HTTP and a stubbed client, so they can be written, reviewed and merged dark — the same argument the Vue track runs on. That takes three steps off another team's critical path.

Corrections found by re-checking each claim against its source, rather than reasoning from the plan's own account: the client signatures are namespace, repository and format scoped and the shortened forms were two arguments short; ManifestType carries seven fields via ExposesElementId, not six, and the referrers table renders three; the manifest kind labels are not in constants.js at the basis commit, Step 1 is what lifts them out; check_introspection_sync diffs both introspection files and aborts on either, so the "moves only on a deprecation" reasoning was wrong and what actually keeps the no-deprecated file still is that experiment is implemented through the deprecation mechanism, a condition that lapses at GA; ImageResolver composes ResolvesOnRepository only, with AcquiresClient and RendersErrors inherited; a null manifest sets the not-found state per S06's ### States and ADR-021, not the error state; children is capped at 200 per index while parent_digests has no cap of its own; and four spec files were missing from the shared-file sweep, all of them on its greppable half.

## Open Questions said "None." while five confirmations were outstanding in the body; it now lists them with who owes each. Headings are title case per the template and AGENTS.md, and ## Research Findings moves to the position the template gives it.

!2361 has merged, so the docs/plans/README.md conflict note this description used to carry is resolved: this branch is rebased on main and its row sits after #1150's.

Governing ADRs

  • ADR-014 (frontend to Artifact Registry) governs and this conforms. The browser stays 100% GraphQL: the local Apollo layer is a client-side resolver over the same schema shape, never a REST call. Step 13's resolver acquires its client through the cached helper on the loaded repository rather than constructing one, and every file the plan authors is under ee/.
  • ADR-009 (API design) governs the addressing. The route carries the full sha256:<hex> digest because that is how the contract addresses a manifest. Its unreconciled treatment of raw-bytes routes is Step 7's second gate.
  • ADR-021 (authorization) governs the not-found state, which is existence-hiding: the field resolves null and the view never distinguishes missing from unauthorized. Step 9's arms were corrected to match.

Checked rather than assumed. No deviation, so no handbook amendment is needed.

Testing

A plan document ships no code. markdownlint-cli2, vale --minAlertLevel=error and lychee --include-fragments all pass, and every relative link and anchor resolves.

Per-step verification lives in each step's Acceptance and Tests entries. The plan-level strategy is in ## Testing Strategy: Jest on both Vue versions, because a router resolve() assertion in this very work passed on Vue 2 and failed on Vue 3; lint at --max-warnings 0, which is what the pre-push hook uses; and both variants covered wherever they differ, with the three steps that have only one variant to cover now naming themselves rather than leaving the claim to be checked against them. The accessibility section names its one exception: the row link now lands in Step 15, which has no story, so its coverage is that step's :js feature spec over the click path.

e2e scenario impact: none. The catalogs cover AR's own protocol and management surfaces, and this plan adds no AR-side behavior.

Related to Container manifest reads for the UI: detail by ... (#1150)


Written by GitLab Duo

Edited by Zack Cuddy

Merge request reports

Loading
Loading