Add the artifact registry repositories GraphQL connection with format and kind filters
What does this MR do and why?
The artifact registry repositories list renders from client-side Apollo resolvers. This adds the GraphQL surface behind it: an organization-rooted, keyset-paginated connection over the Artifact Registry client, with the format and kind filters the list's toolbar already drives.
ArtifactRegistryRepository is the shared type later slices extend. It carries only the fields the list renders: name, format, kind, visibility, downloadsCount, sizeBytes, and the nullable lastUpdatedAt. The counters are int64 at the registry, so both use BigInt rather than Int, which overflows above two gigabytes. description and the polymorphic settings arrive with the slice that renders them.
The type exposes no identifier. The registry's id is a UUID its own contract calls informational, URLs address a repository by name, and a raw UUID would violate the id-is-a-GlobalID convention. The Apollo cache keys on name instead, which is unique within the namespace and immutable.
The format, kind, and visibility enums decode every value the registry can return rather than the narrower set the current rollout creates. An enum missing a returnable value fails coercion and takes the whole page down with it. The registry rejects virtual and remote repository kinds today, and ADR-021 descopes public and internal visibility from the closed beta; which values the table renders is a view concern.
RepositoriesResolver reads the organization's slug, calls the client once per page, and hands the result to the keyset connection adapter, which turns the registry's Link-header cursors into pageInfo. An absent filter is omitted rather than sent as null.
Why the filters ship here rather than in a follow-up
The list's frontend landed first. Its query document declares format and kind against a client-side declaration of the connection in graphql/typedefs.graphql. Once the schema owns ArtifactRegistryRepository and the connection, that local declaration is a duplicate definition and the Apollo schema build fails. Removing it alone is not enough either: the document would then validate against the server field and fail on arguments it does not accept.
So three changes travel together, and none of them stands alone:
- the field gains the
formatandkindarguments, typedefs.graphqlnarrows to the fields with no server backing at all (description,images,packages) viaextend type,- the list's query variables move from
Stringto the enums.
The toolbar's values already matched the enum names: it selects and stores MAVEN, HOSTED, and siblings, and lowercases only for the route query. So no component or spec changes value handling.
The connection stays behind @client, so the list keeps rendering from the local resolvers. Binding the document to the schema, migrating the specs onto fixtures, and the browser check remain a later step.
Two authorization details worth a reviewer's eye
Authorization runs once, against the organization the connection hangs off. organization_policy.rb enables read_artifact_registry for any organization user, so the resolver adds no role pre-check and the registry filters the page itself.
The field then suppresses two per-node repeats, both because the nodes are plain value objects with no policy class and no group or project boundary:
skip_type_authorization: [:read_artifact_registry]cancels the redactor that the type-levelauthorizeinstalls on a connection. Without it,DeclarativePolicy.class_forraises on every populated page rather than filtering it. The type-level declaration itself is required by theGraphql/AuthorizeTypescop.authorize_granular_token skip_reason: :parent_authorizesopts the type out of granular token authorization, since a repository is reachable only through the field that already authorized.
References
- Plan:
docs/plans/monolith/2026-07-28-repositories-list.md, Step 1 (GraphQL repository type and organization connection) plus the filter half of Step 2 - Spec:
docs/specs/monolith/S04-repositories-list.md - The connection field follows the organization-rooted connection shape already on
EE::Types::Organizations::OrganizationType
The sort argument stays out of scope: nothing selects it yet.
Verified against a live Artifact Registry
Not only stubbed. A local registry was provisioned with a namespace and five repositories across Docker, Maven, npm, and OCI, covering all three visibility values.
| Check | Result |
|---|---|
| Populated page | All five repositories returned; every format and visibility coerced correctly |
Link header to pageInfo |
first: 3 returned hasNextPage: true with an endCursor; that cursor as after returned the remaining two with hasPreviousPage: true |
BigInt counters |
Serialize as strings, so a count above two gigabytes survives the round trip |
Nullable lastUpdatedAt |
null throughout with no coercion error |
| Non-member of the organization | null connection, no error, so the view never confirms the organization exists |
| Flag disabled | null, and the registry's access log shows no request issued |
The populated-page result is the one that matters for the authorization notes above: a per-node redactor would have turned it into a 500.
The Apollo schema union was also checked locally, since its CI job is what the duplicate definition breaks: the server schema plus typedefs.graphql builds with no duplicate definitions, and all eight artifact registry documents validate against the result.
How to set up and validate locally
-
Run an Artifact Registry instance and provision a namespace whose slug matches
Organizations::ArtifactRegistry::STUB_SLUG, then create a few repositories in it. -
Point the monolith at it, if not already the default:
Gitlab.config.artifact_registry.api_url # => "http://localhost:8080" in development -
Enable the feature flag for your organization:
Feature.enable(:artifact_registry_ui, Organizations::Organization.first) -
ArtifactRegistry::TokenExchange#token_forstill returnsnil, so the client raises before issuing any request. Until the real exchange lands, return the token your local registry was started with to exercise this end to end. -
Run the query in
/-/graphql-explorer:query ArtifactRegistryRepositories( $id: OrganizationsOrganizationID! $first: Int $after: String $format: ArtifactRegistryRepositoryFormat $kind: ArtifactRegistryRepositoryKind ) { organization(id: $id) { artifactRegistryRepositories( first: $first after: $after format: $format kind: $kind ) { pageInfo { hasNextPage hasPreviousPage endCursor } nodes { name format kind visibility downloadsCount sizeBytes lastUpdatedAt } } } }With variables:
{ "id": "gid://gitlab/Organizations::Organization/1", "first": 3, "format": "MAVEN" } -
Paste the returned
endCursorintoafterto page forward. -
Run the specs and the artifact registry frontend suite:
bundle exec rspec ee/spec/graphql/types/artifact_registry/repository_type_spec.rb ee/spec/graphql/resolvers/artifact_registry/repositories_resolver_spec.rb ee/spec/requests/api/graphql/organizations/artifact_registry_repositories_spec.rb yarn jest ee/spec/frontend/packages_and_registries/artifact_registry
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.
Related to #602638 (closed)