GraphQL surface: remote settings read type, write path, and test-connection mutation (monolith/S07 Steps 2-4)

Part of epic &22455 (Repository CRUD, monolith/S07). Covers Steps 2-4 of the Phase 2 (remote) plan, docs/plans/monolith/2026-08-20-repository-crud-remote.md in the artifact-registry repo.

The whole GraphQL schema track for remote repositories: the typed settings read, the settings write path on both write mutations, and the test-connection mutation. All dark behind artifact_registry_ui; no changelog, no i18n. Every step here also regenerates and commits the four artifacts graphql-verify checks: doc/api/graphql/reference/_index.md, both public/-/graphql/introspection_result*.json files, and app/assets/javascripts/graphql_shared/possible_types.json.

Read the gates before sequencing. Step 2 is ungated and blocks frontend work, so land it first. Step 4 is ungated too. Step 3 sits behind both AR contract gates, and neither holds its merge request.

Implementation details

Step 2: Remote settings read type and the health-status enum (type::feature)

  • ee/app/graphql/types/artifact_registry/remote_settings_type.rb (Create) and health_status_enum.rb (Create). The read type is flat: one type carrying every field, with the ones a format does not define resolving to null. It declares no credential field of any kind, so a query cannot select one.
  • repository_type.rb (Modify): settings becomes the new type, null: true. The Graphql/JSONType disable and the comment deferring the typed shape both go. Re-typing in place is permitted by the experiment exemption and the disabled flag.
  • The enum declares UNKNOWN, HEALTHY, and UNHEALTHY. An unrecognized status maps to UNKNOWN at the resolver rather than reaching coercion, because a GraphQL enum fails the whole read on an undeclared value and would take the edit prefill with it.
  • Names: graphql_name 'ArtifactRegistryRemoteSettings' and ArtifactRegistryHealthStatus. Types::HealthStatusEnum already exists with graphql_name 'HealthStatus', so the prefix avoids a collision.
  • Specs: the two new type specs, repository_type_spec.rb (Modify), and the repository read request spec (Modify) for the per-format prefill cases.

Step 3: Remote settings write path (type::feature)

  • Three per-format input types (Maven, npm, container) carrying the writable fields only, plus credentials and the boolean removeCredentials.
  • ee/app/graphql/mutations/concerns/artifact_registry/accepts_remote_settings.rb (Create): the exactly-one-input rule and the input-to-AR-hash mapping, shared by both mutations. Neither credential field maps to an absent key, credentials maps to the object, and removeCredentials maps to an explicit nil. Both together are rejected before any call to AR.
  • create.rb and update.rb (Modify): the settings arguments, and the credentials-cleared field on the update payload, which reports an event rather than durable state.
  • Zero inputs and several inputs are both resolver-side validation errors, because GraphQL has no input unions.
  • The Maven input alone carries the snapshot revalidation flag, matching AR's contract. It gets no form control in this phase, by decision. Adding one later is additive.
  • This step is bundled by an explicit decision in the plan: graphql-ruby omits an input type no argument references, so a seam-only merge request would ship types absent from the schema, and splitting the create wiring out falls below the step floor.

Step 4: Test-connection mutation (type::feature)

  • test_connection.rb (Create) over Mutations::ArtifactRegistry::Base, mounted with experiment in ee/app/graphql/ee/types/mutation_type.rb. Identity-only input; the payload carries reachability, the upstream HTTP status or null, and the stored health status and timestamp as they read after the probe.
  • A probe that ran answers with a verdict, so an unreachable upstream is a successful mutation carrying a not-reachable verdict, not a payload error. Errors are reserved for the outcomes with no verdict: AR's 404 and AR's 500. Both fall out of the monolith/S03 error-rendering concern unchanged, so this step adds no bespoke mapping.
  • Name: graphql_name 'ArtifactRegistryRepositoryTestConnection'.

Acceptance

Step 2:

  • The read resolves a remote repository's upstream URL, artifact cache window, stored-credentials flag, last health status, and last health check time.
  • The metadata cache window resolves for a Maven and an npm remote and is absent for a Docker and an OCI one.
  • settings resolves null on every hosted and virtual repository, and a blank settings hash resolves null.
  • A request selecting a username, password, or token fails schema validation rather than returning null.
  • A health status the resolver does not recognize resolves UNKNOWN with the rest of the query intact.

Step 3:

  • A remote create sends kind=remote with the upstream URL and the writable cache fields, and returns the created repository with its settings.
  • A metadata cache window on a Docker or OCI remote reaches AR, and its rejection surfaces in the payload errors rather than being dropped client-side.
  • An update sends only the settings fields supplied, so a credential rotation carries no URL.
  • The three credential writes each reach AR in their own form, one request-spec case per row of the spec's credential table, and supplying both credential fields is rejected with no call to AR.
  • Zero and several inputs are each validation errors with no call to AR, while a Maven-only field on the npm input fails schema validation instead of reaching the resolver.
  • An update whose URL change cleared the credentials surfaces AR's signal on the payload, while a later read of the same repository does not report it.

Step 4:

  • The mutation returns the verdict on an existing remote repository.
  • An unreachable upstream is a successful mutation carrying a not-reachable verdict.
  • A 404 from AR is a payload not-found error; a 500 raises the top-level service-unavailable error.
  • A flag-off call raises a top-level ResourceNotAvailable with no client call.

Dependencies

  • The Phase 2 plan merge request must merge before any merge request here opens.
  • Steps 3 and 4 depend on the client issue (plan Step 1). Step 4 also depends on Step 2, for the health enum.
  • Step 2 blocks plan Step 6, in the "Remote create route ... and the prefilled source section" issue. That is the only edge from this issue into the frontend track before the closing step.
  • monolith/S03 (GraphQL foundation) merged: ArtifactRegistry::RendersErrors and the client-acquisition concern, which Step 4 composes unchanged.
  • AR contract gates on Step 3 only, both open at plan time: CreateRepositoryRequest.kind admitting a non-hosted kind (create arm), and UpdateRepositoryRequest declaring settings (update arm). Neither holds the merge request. The specs stub the client and pass regardless; what waits on a gate is the path working against a running AR.