docs(plans): plan the per-user token-exchange credential
What
Plans gitlab#623370:
ArtifactRegistry::TokenExchange#token_for mints a real
gitlab-artifact-registry-audience JWT through
Authn::TokenExchange::TokenIssuer, so the client's per-user management-API
methods stop failing closed before a request is issued.
Adds docs/plans/monolith/2026-08-26-s02-per-user-token-exchange.md and amends
the specs the change falsifies.
Plan shape
One step, in the monolith. The seam is a single method, its collaborator
already exists, and ArtifactRegistry::Client already defaults to this class —
making the default functional is the whole wiring, so neither Client nor
CachesClient changes.
Four decisions the seam left open
- Mint in process, not over HTTP. A GraphQL resolver holds no credential to
authenticate a self-directed
POST /api/v4/token_exchangewith, which decides it ahead of any efficiency argument;Authz::ArtifactRegistry::BaseServiceis the in-tree precedent. The plan then answers each of the four controls the endpoint wraps around the mint rather than leaving them unmentioned: two are redundant here, and the rate limit andgate_token_exchange_endpointflag are deliberately not reproduced over a surfaceartifact_registry_uialready gates per organization. slugcomes out of the signature. The shipped issuer carries no resource claim, so there is nothing to pass it to, andClient#user_request— its only caller — drops the argument with it.- Memoize per user, which follows from the above: with no resource in the
token, a mint is keyed by the user alone. No cross-request store, because the
token carries
organization_role. - The home-organization limitation is pinned by a test, not worked around.
Spec amendments
monolith/S02 across five sections — its per-user seam is documented as
returning no credential, and its ADR-014 OQ#2 row as open. One Out-of-scope line
each in monolith/S03 and monolith/S04, which both defer the real exchange to
"the S02 credential seam and ADR-014 open question #2".
Two findings a reviewer should weigh
- The token names the acting user's home organization.
TokenIssuer#organization_uuidreads@user.organization.uuid. A user acting on an organization they are a member of but not homed in therefore presents a token naming a different organization than the namespace addressed. This is the same defect the closed-beta roadmap records against #473, one layer down. Out of scope here (it is an issuer change), filed separately. - What the change exposes. AR authenticates
/api/v1and then applies no role check, so Rails' ownread_artifact_registryauthorization becomes the only access control over the per-user surface. Acceptable for a closed beta gated per organization, and stated so it is weighed rather than discovered.
Not in scope
The service credential (gitlab#617724, independent — different method, either merge order), resource-scoped tokens, the cross-organization identity gap, the stub slug (gitlab#619494), and AR-side enforcement (#652 (closed)).
Related to gitlab-org/gitlab#623370 (closed)