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 whoseto_sis 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").slugandrepository_namestill raiseArgumentError, because they are server-supplied, not URL input. - Hollow success body to
UnavailableError: a200 {}whoseidis 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 comparesvalue.to_s, so a non-String id whoseto_sis.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_credentialinstead of its first 40 characters, catching partial-token leaks. - The invalid-format contexts for
#packageand#imagenow reuse the sharedrejecting an invalid argument before any requestexample 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
ErrorReportercredential 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 offensesbundle exec rspec ee/spec/lib/artifact_registry/client_spec.rb-- 353 examples, 0 failures
References
- Plan: monolith/S14 plan, Step 1
- Spec: S14 single-artifact reads
- Related to #618405 (closed)
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.