AR delete surface/Step 6: Version delete mutation
What does this MR do and why?
This is Step 6 of the merged 19-step "monolith artifact delete surface" plan (plan MR: gitlab-org/ops/artifact-registry!2201 (merged)).
It adds one GraphQL mutation: ArtifactRegistryVersionDelete (Mutations::ArtifactRegistry::Versions::Delete), which deletes one version of a Maven or npm package.
It calls the client method #delete_version, which merged in Step 1 (!253251 (merged)). Nothing reached that method from the schema until now. It copies the shape of the merged ArtifactRegistryArtifactDelete mutation from the S05 track (!252723 (merged)).
Behaviour notes:
- Behind the
artifact_registry_uifeature flag, off by default, and markedexperiment: { milestone: '19.4' }. No changelog entry, because the flag is off. - Takes only
name(the repository name) andid. There is noformatargument: the format comes from a single repository read that also serves as the payload. There is nokindargument and no kind check, because Artifact Registry reads the repository's kind itself and decides between permanently deleting a hosted version and evicting a remote cached copy. - Declares
authorize_granular_token skip_reason: :external_service_authorizes, because Artifact Registry is the authorization boundary here, not the monolith. - Artifact Registry answers this route with
202and no body. That is acceptance, not completion. The payload carries no count and does not claim the delete finished. The version list is the source of truth after a delete. - A nil repository read raises not-available, so a hidden repository is not confirmed to exist.
- Versions exist only on Maven and npm repositories. A container (docker/oci) repository is refused on the same
#packages?predicate the read resolvers already gate on, rather than falling through to the client'sguard_format!, whose message names aformatkeyword that is not an argument of this mutation. locale/gitlab.potcarries the new translated refusal message.doc/api/graphql/reference/_index.mdandpublic/-/graphql/introspection_result.jsonare regenerated from the schema.
Scope change. This MR originally also added ArtifactRegistryFileDelete. It has been removed. A reviewer asked on the plan MR whether the UI should offer per-file delete (gitlab-org/ops/artifact-registry!2201 (comment 3773665009)), and the product designer confirmed it should not (gitlab-org/ops/artifact-registry!2201 (comment 3779379507)). The decision was already recorded in the design truth doc (https://gitlab.com/gitlab-org/ci-cd/package-stage/unified-artifact-management/-/blob/main/design/design-truth.md#format-data-model--files-versions-tags-deletes), which says not to offer per-file delete inside a version, because deleting a single file (for example a .pom) corrupts the version for consumers. A follow-up MR removes the now-unused #delete_file method from the client (!253712). Whether Artifact Registry keeps its own delete-file REST route is a separate open question the designer raised with product, and it is not decided here.
Known gap. The plan's Dependencies section says Steps 6 through 10 wait on a spec promotion MR that has not been raised yet. The two Artifact Registry specs that own this surface, docs/specs/monolith/S14-version-list.md and docs/specs/monolith/S06-version-detail.md in https://gitlab.com/gitlab-org/ops/artifact-registry, still place every delete in Phase 2 and specify no GraphQL surface, no arguments, and no error mapping for them. The mutation name, arguments, and error mapping here therefore come from the merged plan rather than from either spec, and the promotion MR will need to record them.
References
- Step 6 work item (this step): #627396 (closed)
- Parent scoping issue, owns the full delete surface: #627172
- Plan MR, merged, source of the mutation shape used here: gitlab-org/ops/artifact-registry!2201 (merged)
- Step 1, merged, the client method this calls: !253251 (merged)
- S05 precedent, merged, the mutation this copies: !252723 (merged)
- Designer's confirmation that the UI will not offer per-file delete: gitlab-org/ops/artifact-registry!2201 (comment 3779379507)
- Follow-up MR removing the unused client method: !253712
- Epic: &21052
Screenshots or screen recordings
This is a GraphQL-only change with no UI. The recording below drives the mutation through the GraphQL explorer (GraphiQL) against a live local Artifact Registry.
The recording runs the five scenarios listed in the validation section below, in order.
How to set up and validate locally
These checks let a reviewer reproduce directly, all run on this branch and passing.
bundle exec rspec ee/spec/graphql/mutations/artifact_registry/versions/delete_spec.rb
# 3 examples, 0 failures
bundle exec rspec ee/spec/requests/api/graphql/mutations/artifact_registry/versions/delete_spec.rb
# 10 examples, 0 failures
bundle exec rubocop ee/app/graphql/mutations/artifact_registry ee/spec/graphql/mutations/artifact_registry ee/spec/requests/api/graphql/mutations/artifact_registry
# 26 files inspected, no offenses detected
bundle exec rake gitlab:graphql:compile_docs
bundle exec rake gitlab:graphql:schema:dump
bundle exec rake gitlab:graphql:generate_all_introspection_schemasBeyond the specs, the mutation was driven through the GraphQL explorer at /-/graphql-explorer against a live local Artifact Registry (built from the artifact-registry repo, on http://localhost:8080, against this GDK's Postgres, Redis and JWT issuer), on a real npm package published with the real npm CLI. Every block below is captured output from the recorded run.
-
Publish two real versions into a hosted npm repository:
npm publish --registry http://localhost:8080/gdk-local-test/npm/fmccawley-s6v-1788532464/ # + fmccawley-s6v-validation-pkg@1.0.0 # + fmccawley-s6v-validation-pkg@1.0.1 -
artifactRegistryVersionDeleteon version 1.0.0:{ "data": { "artifactRegistryVersionDelete": { "errors": [], "repository": { "name": "fmccawley-s6v-1788532464", "format": "NPM", "kind": "HOSTED" } } } } -
Delete the same version again. Artifact Registry answers
404, which surfaces as a mutation error rather than a success:{ "data": { "artifactRegistryVersionDelete": { "errors": [ "artifact not found" ], "repository": null } } } -
A repository that does not exist. Its absence is hidden and nothing is deleted:
{ "errors": [ { "message": "The resource that you are attempting to access does not exist or you don't have permission to perform this action", "path": ["artifactRegistryVersionDelete"] } ] } -
A container (docker) repository. Refused before any delete request is sent:
{ "errors": [ { "message": "Only Maven and npm repositories have versions.", "path": ["artifactRegistryVersionDelete"] } ] } -
The schema keeps the version delete input type and no longer carries the file delete one:
{ "data": { "versionDelete": { "name": "ArtifactRegistryVersionDeleteInput" }, "fileDelete": null } }
Because a 202 is acceptance rather than completion, the version list is the only real proof. Re-reading Artifact Registry's version list after the delete returns only 1.0.1, so 1.0.0 is gone. Both repositories created during the run were deleted afterwards and confirmed gone (a follow-up GET returned 404 on each).
The top-level errors in scenarios 4 and 5 are shown here trimmed to the message and path that GraphiQL displays, because the explorer's response pane wraps them in an Apollo client error object with a long JavaScript stack.
MR acceptance checklist
Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.