Add artifact registry GraphQL mutation foundation and repository read

What does this MR do and why?

Adds the GraphQL foundation for Artifact Registry repository CRUD (monolith/S07 Step 1): a base mutation, the single-repository read resolver, and the description/settings fields on the shared ArtifactRegistryRepository type. This is the seam the create/update/delete mutations build on.

  • Base mutation (Mutations::ArtifactRegistry::Base): a template that resolves the organization from context[:current_organization], gates on the artifact_registry_ui flag and the organization's read_artifact_registry ability, acquires the request-memoized client, and wraps the call in the S03 error rendering. Concrete mutations implement #resolve_artifact_registry and return their payload fields; the base supplies the standard errors array, so a mutation cannot ship an ungated write or leak a client error by forgetting to wrap the call. A flag-off or non-member request raises a top-level ResourceNotAvailable before any client call. Artifact Registry remains the authoritative per-operation authorizer; the Rails gate here is a membership floor, not a per-operation pre-check. Each concrete mutation declares its own authorize_granular_token skip_reason: :external_service_authorizes.
  • Single-repository read (Resolvers::ArtifactRegistry::RepositoryResolver): resolves one repository by the organization slug and name, returning null on a read-404 (edit prefill).
  • Shared client acquisition (ArtifactRegistry::AcquiresClient concern): the flag check, client acquisition, and slug forwarding shared by the base resolver and the base mutation; each supplies the organization through #artifact_registry_organization.
  • Type fields: adds description and the polymorphic settings object to the shared ArtifactRegistryRepository type. settings is non-null: the client defaults it to an empty object, so it is always present.
  • Frontend typedefs: removes the now server-backed description field and the Organization.artifactRegistryRepository read from the Vue app's client-side Apollo typedefs (graphql/typedefs.graphql). The merged Vue write UI declared these as local @client extensions while there was no backend; now that the schema provides them, keeping the local declarations would collide during the Apollo schema build. The write flows still run on the local mock resolvers through the remaining @client operations.

The shared ArtifactRegistryRepository type and its enums are owned by monolith/S04, merged in !248690 (merged). This MR only adds the two experiment fields to that type and the single-read field on the organization; it introduces neither the type nor the enums.

All fields and arguments are experiment (milestone 19.3), behind the dark artifact_registry_ui flag, so there is no user-facing change and no changelog.

References

  • Spec: docs/specs/monolith/S07-repository-crud.md (Approved), GraphQL surface
  • Plan: docs/plans/monolith/2026-07-17-repository-crud.md (merged), Step 1
  • Shared type and enums dependency: !248690 (merged) (monolith/S04), merged
  • Follow-up: #609504 (closed) — type settings with a union/interface before GA
  • ADR-014 (frontend to Artifact Registry), ADR-021 (authorization)

Screenshots or screen recordings

N/A. Schema only, behind a dark flag; the one frontend change removes now-redundant client typedefs and has no visible effect.

How to set up and validate locally

  1. Point the client at a local Artifact Registry service and enable the flag (adjust the token stub as needed for your setup):
    org = Organizations::Organization.first
    Gitlab.config.artifact_registry['api_url'] = 'http://127.0.0.1:8090'
    Feature.enable(:artifact_registry_ui, org)
  2. Query the single-repository read for an existing repository:
    query = <<~GQL
      query($id: OrganizationsOrganizationID!, $name: String!) {
        organization(id: $id) {
          artifactRegistryRepository(name: $name) { name description settings }
        }
      }
    GQL
    user = org.organization_users.first.user
    pp GitlabSchema.execute(query,
      variables: { 'id' => org.to_global_id.to_s, 'name' => 'my-repo' },
      context: { current_user: user, current_organization: org }).to_h
  3. Confirm a missing repository resolves to null (pass a name that does not exist), and that with the flag off the field resolves to null with no client call.

Unit and request specs cover the base mutation gate, the resolver read paths, and the two added type fields:

bundle exec rspec \
  ee/spec/graphql/mutations/artifact_registry/base_spec.rb \
  ee/spec/graphql/resolvers/artifact_registry/repository_resolver_spec.rb \
  ee/spec/graphql/types/artifact_registry/repository_type_spec.rb \
  ee/spec/requests/api/graphql/organizations/artifact_registry_repository_spec.rb

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist.

Edited by Narendran

Merge request reports

Loading
Loading