Artifact delete mutations: versions, manifests, files, and tags (spec and scoping)

Summary

The Artifact Registry (AR) UI design calls for row-menu delete actions on the hosted version list page and the version detail page, covering versions, container manifests, files, and tags. None of the GraphQL mutations, client methods, or specs for this exist in the monolith today. The Artifact Registry Go service already serves all the endpoints needed. This is a scoping issue: it records the spec updates needed, the endpoint surface to implement, and the shape of the work. It is not an implementation MR.

Delete at artifact granularity (whole packages, whole images, and bulk delete-all on each) is covered separately by issue 626611 (monolith/S05 Steps 1-3), through the ArtifactRegistryRepositoryArtifactsDelete and ArtifactRegistryArtifactDelete mutations. The client delete methods behind them merged as !252683 (merged); the mutations themselves are in !252723 (merged), still open. That scope is not revisited here. It is the precedent to copy: both mutations are kind-neutral (no kind argument, no kind check, because AR reads kind off its own repository row and decides whether the request permanently deletes a hosted artifact or evicts a remote cached copy), both use authorize_granular_token skip_reason: :external_service_authorizes because AR is the authorization boundary, and payloads carry no count and make no completion claim because AR answers 202 (accepted, not completed).

Spec updates needed

No new spec file is being added. The delete surface is tracked by amending the two existing specs that already own the affected pages.

  1. Ownership split. There are 9 delete operations, split by the page that owns the affordance. monolith/S14 (version list) owns 6: deleteVersion, bulkDeleteVersions, deleteContainerManifest, bulkDeleteContainerManifests, deleteContainerTag, bulkDeleteContainerTags. monolith/S06 (version detail) owns 3: deleteFile, bulkDeleteFiles, deleteNpmDistTag. deleteVersion is the one operation both pages need, for the list row menu and the detail page kebab menu. S14 defines the client method and the GraphQL mutation for it, and S06 declares the reuse in its ### Cross-spec table rather than redefining it. Reuse by cross-spec reference is already the established pattern here: S06's existing monolith/S14 row reads "Provides the version and manifest element types, the publish-attribution join this slice reuses, and the table this slice's rows link from", and that same row already carries a reciprocal amendment to S14 for an unrelated field. Amending a sibling spec inside one change set is therefore normal practice in this spec set, not a new pattern.
  2. Amend docs/specs/monolith/S14-version-list.md. Line 188 holds a deferral row for row-menu write actions (delete a version, delete a manifest, manage or edit tags), deferred to S17 Phase 4. Split it: the delete half moves in-scope, carrying S14's 6 operations. The tag add-and-move half stays deferred. Also update the ### Cross-spec table's S17 Phase 4 row, State column, from Planned to Implemented, since Phase 4 is complete.
  3. Amend docs/specs/monolith/S06-version-detail.md. Line 279 (write actions: delete version, npm dist-tag removal) currently says the route answers 501 until its handler lands. That is no longer true; both actions move in-scope, with delete version reusing S14's definition per the split above. Line 280 (the delete file action) already says the route exists and answers 202, deferring only on the kebab menu not being rendered; it now carries deleteFile and bulkDeleteFiles as in-scope. Line 281 (the add-and-move half of the npm Manage tags modal) stays deferred and unchanged, since the blocker (no npm dist-tag upsert route in the contract) is still real. Also update the ### Cross-spec table's S17 Phase 4 row, State column, from In progress to Implemented.
  4. Amend docs/specs/monolith/S05-repository-detail.md. This spec still says the two artifact-delete mutations address a remote repository only and have no hosted counterpart, and separately holds hosted artifact deletion out of scope. Both statements contradict the shipped kind-neutral design. This is already recorded as owed by work item 626611 and has not yet been raised.

One plan file in docs/plans/monolith/ will cover the whole delete surface across both specs, rather than one plan per spec, because the dependency graph sequences the client methods, then the mutations, then the frontend, and that chain is shared between the two pages. Plan granularity is already not one-to-one with specs in this repo: spec S02 has four plan files (2026-07-02-ar-ruby-client.md, 2026-08-06-s02-gitlab-api-namespace-client.md, 2026-08-20-s02-service-token-credential.md, 2026-08-26-s02-per-user-token-exchange.md). The artifact-registry repo follows a spec MR, then a plan MR, then implementation, so the spec amendments above need to land before the plan work starts.

Endpoints to cover

All routes are prefixed /api/v1/{slug}/repositories/{repository_name}. Routes below are relative to that prefix.

operationId Method + route (relative to prefix) Owning spec Needed by
deleteVersion DELETE .../{format}/versions/{version_id} monolith/S14, reused by S06 Version list row menu, version detail
bulkDeleteVersions POST .../{format}/packages/{package_id}/versions/bulk_delete monolith/S14 Version list multi-select
deleteContainerManifest DELETE .../{format}/images/{image_id}/manifests/{digest} monolith/S14 Manifest list row menu
bulkDeleteContainerManifests POST .../{format}/images/{image_id}/manifests/bulk_delete monolith/S14 Manifest list multi-select
deleteFile DELETE .../{format}/files/{file_id} monolith/S06 Version detail files tab
bulkDeleteFiles POST .../{format}/versions/{version_id}/files/bulk_delete monolith/S06 Version detail files tab multi-select
deleteContainerTag DELETE .../{format}/images/{image_id}/tags/{tag_name} monolith/S14 Manage tags (container), delete half
bulkDeleteContainerTags POST .../{format}/images/{image_id}/tags/bulk_delete monolith/S14 Manage tags (container), delete half
deleteNpmDistTag DELETE .../npm/tags/{tag_id} monolith/S06 npm dist-tag removal, version detail

The contract source of truth is api/openapi/v1.yaml on main in the gitlab-org/ops/artifact-registry repo.

AR also serves remote arms for these operations, evidenced by handler tests named maven_remote_version_delete_test.go, npm_remote_version_delete_test.go, container_remote_manifest_delete_test.go, container_remote_tag_delete_test.go, maven_remote_file_delete_test.go, and npm_remote_file_delete_test.go. The S05 kind-neutral pattern carries over here too: no mutation in this scope should branch on kind.

Not in scope: tag creation and updates. Every one of the 9 operations above is a delete, and the scope is deliberately named for that rather than for "writes". AR does serve one non-delete artifact write next to them, upsertContainerTag (PUT .../{format}/images/{image_id}/tags/{tag_name}, handler internal/managementapi/container_tag_upsert.go, served with no stub), and the design's "manage or edit tags" affordance needs it. It is excluded here for two reasons. It has a different shape from the 9 (which all reduce to confirm, call, re-read the list), and its npm counterpart does not exist: docs/specs/monolith/S06-version-detail.md line 281 records that the add-and-move half of the npm Manage tags modal needs a dist-tag upsert route the contract does not declare at all. Tag management therefore carries an unresolved contract gap that should not block the deletes. The delete half of tag management (deleteContainerTag, bulkDeleteContainerTags, deleteNpmDistTag) is in scope above; the add and move half is not, and needs its own issue.

AR-side readiness

The upstream gate has cleared. AR work item 313, "S17 Phase 4: artifact writes (delete, bulk)" is closed and labelled workflow::complete, last updated 2026-08-26.

Handler files on origin/main in the artifact-registry repo, with the date of their most recent commit:

  • internal/managementapi/bulk_versions.go and internal/managementapi/bulk_files.go: 2026-08-19
  • internal/managementapi/dist_tag_delete.go: 2026-08-21
  • internal/managementapi/version_delete.go and internal/managementapi/file_delete.go: 2026-08-27
  • internal/managementapi/container_tag_delete.go: 2026-08-27
  • internal/managementapi/container_manifest_delete.go: 2026-08-28

No 501 / not-implemented stubs remain in any of those files. The earliest AR release containing the last of these handlers is v1.423.0. That is the minimum AR version the monolith needs to integrate against for the full surface. Which AR version is actually deployed to a given environment is a separate thing to confirm, not assumed here.

General shape of the work

Phased at a high level. A plan MR will break this into numbered steps with a dependency graph.

  • Phase A, specs. The four spec items above. Blocks everything else.
  • Phase B, plan. A plan file in docs/plans/monolith/ in the artifact-registry repo, breaking the surface into steps with a dependency graph, following the layout of the existing 2026-08-11-version-list.md and 2026-08-18-version-detail.md plans.
  • Phase C, client. New delete methods on ee/lib/artifact_registry/client.rb. The existing reads (versions, version_files, manifests) and the S05 writes (bulk_delete_artifacts, delete_artifact) are the pattern to follow. Open questions: whether the singles and the bulks share a helper, and whether any of these routes needs to opt out of the retry middleware the way S05's single-artifact delete did (a replayed DELETE is a hazard).
  • Phase D, GraphQL mutations. New mutations under ee/app/graphql/mutations/artifact_registry/, composing the existing Mutations::ArtifactRegistry::Base, mounted in ee/app/graphql/ee/types/mutation_type.rb. Copy the S05 shape: kind-neutral, granular-token skip, no count in the payload. Open question: how many mutations the 9 routes collapse into, since tags and dist-tags are a different shape from versions, manifests, and files.
  • Phase E, frontend. Row menus and confirmation modals on the version list and version detail pages, plus multi-select where a bulk route backs it. Currently unowned: no issue under either epic covers a delete affordance.

Nothing in flight

A check on 2026-09-01 found no monolith spec, plan, client method, mutation, issue, or MR covering any of the 9 endpoints above.

  • The 10 issues under epic 23331 (Version list, monolith/S14) are all reads and UI.
  • The 6 issues under epic 23399 (Version detail, monolith/S06) are all reads and UI.
  • The S14 non-blocking follow-ups issue 614161 has 21 recorded rows, none concerning delete.
  • Of the 24 open merge requests labelled Category:Artifact Registry, none touches these routes.

Caveat: this sweep covers pushed branches, filed issues, and specs on main. It would not see unpushed local work.

Acceptance for this issue

This is a scoping issue. Acceptance is about the spec and plan landing, not about shipped mutations.

  • docs/specs/monolith/S14-version-list.md is amended: the delete half of the line 188 row moves in-scope carrying its 6 operations, and the Cross-spec S17 Phase 4 State reads Implemented.
  • docs/specs/monolith/S06-version-detail.md is amended: the line 279 and 280 rows move in-scope carrying its 3 operations, delete version is declared as reusing S14's definition, the line 281 add-and-move row stays deferred, and the Cross-spec S17 Phase 4 State reads Implemented.
  • docs/specs/monolith/S05-repository-detail.md is amended to remove the "remote only, no hosted counterpart" and "hosted deletion out of scope" statements.
  • A plan file exists in docs/plans/monolith/ with steps and a dependency graph for the delete surface.
  • Follow-up implementation issues are filed per step group, including an explicit owner for the frontend affordances.
Edited by Fiona McCawley