AR GraphQL single-artifact fields (monolith/S14 plan: 5/25)
What does this MR do and why?
Adds the two single-artifact GraphQL fields for monolith/S14 Step 5: package(id:) and image(id:) on ArtifactRegistryRepositoryDetails. Each takes the AR artifact ID, reads the repository and organization off the RepositoryPresenter, calls the matching client method (Client#package / Client#image), and returns the element wrapped in ArtifactPresenter so a later child connection can reach its context.
Both resolve null on a 404 (existence-hiding: a missing artifact and a forbidden one are indistinguishable) and, before any client call, when the requested field does not match the repository's format (a caller selects both fields to resolve one deep link before the format is known; the mismatched one resolves null with no request). The base resolver's flag gate and RendersErrors mapping give the flag-off, authorization, and service-unavailable outcomes. A per-field FieldCallCount budget of 1 bounds aliased re-selection; it does not bound the operation's total fan-out (tracked in #624954).
Two fields rather than one artifact(id:) union: the S14 spec mounts the versions and manifests connections on the element types, so the schema needs the concrete package and image element types as return types regardless, and a union would not remove them. The plan's Key design decisions section also rejects rooting the reads at the repository with the artifact ID as an argument, because it contradicts the spec's Connections section and leaves those element types with no connection. See the plan and spec.
This branch was rebased onto master after the packages and images connections, the ArtifactPresenter seam, and both element types (ArtifactRegistryPackage, ArtifactRegistryImage) merged. So the diff is now just the two single-artifact fields, their resolvers, the specs, and the regenerated GraphQL schema artifacts on top of that foundation.
References
- Plan: monolith/S14, Step 5
- Spec: S14 single-artifact reads
- Related to #618409 (closed)
Multiversion compatibility
The image/package fields carry @gl_introduced, but the id: argument cannot. This is safe: the whole surface is behind the dark artifact_registry_ui flag and same-origin, the frontend selections still carry @client until the handover lands, and the fields resolve null when the flag is off, so no cross-version request reaches an older backend.
Screenshots or screen recordings
N/A - backend GraphQL fields, no UI.
Database changes
None. No migrations, models, or queries.
e2e
No e2e scenario is affected: this adds read-only GraphQL fields behind a dark flag, with no UI or route change (per the plan's testing strategy).
How to set up and validate locally
The client talks to Artifact Registry over HTTP, so the script below stubs the endpoint with WebMock, enables the flag for a throwaway organization, and executes the query through GitlabSchema (no browser, no real AR). It runs inside a rolled-back transaction. Paste it into rails console:
require 'webmock'
require 'rspec/mocks/standalone'
include WebMock::API
WebMock.enable!
WebMock.disable_net_connect!(allow: ['gdk.test', '127.0.0.1', 'localhost'])
ActiveRecord::Base.transaction do
s = SecureRandom.hex(4)
org = FactoryBot.create(:organization, path: "arv-#{s}", name: "AR #{s}")
user = FactoryBot.create(:user, username: "arv-#{s}", email: "arv-#{s}@example.com")
FactoryBot.create(:organization_user, organization: org, user: user)
slug = Organizations::ArtifactRegistry::STUB_SLUG
base = 'http://artifact-registry.test'
Gitlab.config.artifact_registry['api_url'] = base
Feature.enable(:artifact_registry_ui, org)
allow_any_instance_of(ArtifactRegistry::TokenExchange).to receive(:token_for).and_return('tok')
repo_name = 'maven-releases'
art_id = 'a1b2c3d4'
repo_url = "#{base}/api/v1/#{slug}/repositories/#{repo_name}"
pkg_url = "#{repo_url}/maven/packages/#{art_id}"
img_url = "#{repo_url}/maven/images/#{art_id}"
jh = { 'Content-Type' => 'application/json' }
stub_request(:get, repo_url).to_return(status: 200, headers: jh,
body: { 'id' => 'r1', 'name' => repo_name, 'format' => 'maven', 'kind' => 'hosted',
'visibility' => 'private', 'downloads_count' => 0, 'size_bytes' => 0, 'settings' => {} }.to_json)
stub_request(:get, pkg_url).to_return(status: 200, headers: jh,
body: { 'id' => art_id, 'group_id' => 'com.example', 'artifact_id' => 'core' }.to_json)
stub_request(:get, img_url).to_return(status: 200, headers: jh, body: { 'id' => art_id, 'name' => 'x' }.to_json)
query = <<~GQL
query($id: OrganizationsOrganizationID!, $name: String!, $aid: ID!) {
organization(id: $id) {
artifactRegistryRepository(name: $name) {
package(id: $aid) { ... on ArtifactRegistryMavenPackage { id groupId artifactId } }
image(id: $aid) { id name }
}
}
}
GQL
vars = { 'id' => org.to_global_id.to_s, 'name' => repo_name, 'aid' => art_id }
res = GitlabSchema.execute(query, context: { current_user: user }, variables: vars)
raise "errors: #{res['errors']}" if res['errors']
d = res.dig('data', 'organization', 'artifactRegistryRepository')
raise '1 FAIL' unless d.dig('package', 'id') == art_id && d.dig('package', 'artifactId') == 'core'
puts '1 OK: package field returns the Maven package'
raise '2 FAIL: image not null' unless d['image'].nil?
img_hits = WebMock::RequestRegistry.instance.requested_signatures.hash.keys.count { |sig| sig.uri.to_s.include?('/images/') }
raise '2 FAIL: image endpoint was called' unless img_hits.zero?
puts '2 OK: image field resolves null with no client call (format mismatch)'
raise ActiveRecord::Rollback
end
WebMock.disable!
puts 'ALL OK'Expected output:
1 OK: package field returns the Maven package
2 OK: image field resolves null with no client call (format mismatch)
ALL OKYou can also run the specs directly:
bundle exec rspec ee/spec/graphql/resolvers/artifact_registry/package_resolver_spec.rb ee/spec/graphql/resolvers/artifact_registry/image_resolver_spec.rb ee/spec/graphql/types/artifact_registry/repository_details_type_spec.rb ee/spec/requests/api/graphql/organizations/artifact_registry_artifact_spec.rbSuggested labels
~"type::feature", ~backend, ~"Category:Artifact Registry"
MR acceptance checklist
Evaluate this MR against the MR acceptance checklist.
Feature flag artifact_registry_ui is dark; field descriptions are schema text, not i18n. No changelog (dark).