AR client single-artifact reads (monolith/S14 plan: 1/25)

What

Adds the two single-artifact reads to ArtifactRegistry::Client -- #package and #image -- for monolith/S14, Step 1 (version-list slice). Each issues the AR artifact-detail endpoint (packages/:package_id / images/:image_id under the repository format), returns the format's value object, returns nil on a not-found outcome, and maps every other outcome through the Client::Error hierarchy. A shared private artifact_path helper appends the encoded artifact id to artifacts_path, and a shared #artifact reader centralises the guards and the response handling.

Reuses the existing MavenPackage, NpmPackage, and Image value objects; no new value object.

Behaviour

  • 404 to nil (existence-hiding): a missing artifact and a forbidden one are indistinguishable, and the path is deliberately silent (no error-tracking event) because a 404 on a user-supplied deep-link id is the ordinary not-found outcome.
  • Malformed id to nil, no request: a blank or dot-segment id (including a non-String whose to_s is a dot segment) resolves to the same not-found outcome as a real 404, per the S14 spec ("AR answers 404 rather than 400 so exposes no id-syntax oracle"). slug and repository_name still raise ArgumentError, because they are server-supplied, not URL input.
  • Hollow success body to UnavailableError: a 200 {} whose id is blank is an AR serialization fault, not a present artifact -- it fails loud rather than resolving a hollow record whose id would feed the Step 2/3 sub-collection reads. Mirrors #namespace.
  • Guard order: guard_format! before segment/id handling, both before any token exchange.

Review feedback addressed

Changes from the @mkhalifa3 review round (commit 13b1bd0a):

  • dot_segment? now compares value.to_s, so a non-String id whose to_s is . or .. cannot reach the wire; this also hardens the slug/name checks that share the helper. Added Symbol rows to the guard specs.
  • Both credential-redaction assertions now assert the full jwt_credential instead of its first 40 characters, catching partial-token leaks.
  • The invalid-format contexts for #package and #image now reuse the shared rejecting an invalid argument before any request example instead of inline bodies.

Two suggestions are tracked as follow-ups rather than expanding this dark client-read MR:

  • Aggregate signal for mass detail-read 404s: #621776.
  • Harden ErrorReporter credential redaction against non-canonical echoes: #621777.

Downstream note

The malformed-id behaviour (nil, not raise) is spec-compliant and improves Steps 5-7, which already resolve nil to the not-found view; those stacked branches inherit it on rebase with no behaviour break.

Validation (run locally, all green)

  • bundle exec rubocop --force-exclusion ee/lib/artifact_registry/client.rb ee/spec/lib/artifact_registry/client_spec.rb -- no offenses
  • bundle exec rspec ee/spec/lib/artifact_registry/client_spec.rb -- 353 examples, 0 failures

References

Feature flag artifact_registry_ui is dark; the client is not flag-gated. No changelog (dark), no i18n (library code).

The plan Status row for Step 1 lives in the artifact-registry repo (docs/plans/monolith/2026-08-11-version-list.md) and is filled by a companion docs(plans) MR there -- a monolith MR cannot touch it.

Edited by Narendran

Merge request reports

Loading
Loading