docs(specs): redirect a stale-tab claim with a banner in S10

What does this MR do and why?

Amends monolith/S10 so a claim refused because the organization is already activated under a different handle sends the owner on with an informational banner, instead of replacing the setup form with an alert and a status-dependent link.

That refusal comes from a stale setup page, one loaded before the organization was activated elsewhere: in another tab, or by another owner. gitlab-org/gitlab!258136 implements today's text, and its review held the alert until design reviewed it (blocking thread). The design review proposed redirecting to Repositories with an info banner. The reply routes that redirect through the setup URL, so the server keeps deciding where an activated organization lands, and proposes a status-neutral title, because the registry may be disabled by then.

The setup page now reloads the slug-less landing route (/o/:organization_path/-/artifact_registry, which is also the setup page's URL) with a dismissible informational banner. The organization has a mapping row by then, so the route answers from the registry's state, as it does for any visit. The banner names the handle already claimed, says the submitted handle was not claimed, and names no status. The spec does not fix its wording.

Unchanged: a claim carrying a different handle that overlaps the activating one still gets AR's 409 inline under the handle field, and the activate mutation's contract (payload errors plus the resolved registry, merged in gitlab-org/gitlab!258132 (merged)) stays as it is. Only the already-activated refusal carries the registry, which is what tells the two apart.

A monolith MR cannot edit this repository, so the amendment rides its own MR, and gitlab-org/gitlab!258136 is reworked after it merges.

The stale-tab amendment (ea28eb378)

Section Change
References, Design Adds the design review and its reply as the source for the refusal's return
The mapping row, different-handle repeat It is the only refusal carrying the resolved registry, which tells the setup page to return the owner to the landing route and gives the banner its handle
Activation page, intro A member who follows a stale link reaches the registry's state, not always their repositories
Activation page table, setup-page row Adds the reload with an informational banner, and its design sources
Activation page, new paragraph Which refusal triggers the reload and which stays inline; the landing route answers by state; the banner survives the redirect, names the claimed handle, says the submitted one was not claimed, and names no status; its wording comes from design
Navigation A member who reaches the landing route for a registry that does not serve reads finds the status named there
Criterion 34 Rewritten for the reload and banner. Test note: the success case, plus the refusal with a read-serving and a non-read-serving registry, asserting the same reload and banner
Error cases "Organization already activated under a different handle": the reload and banner, then the landing route's state. "User lacks the update ability": a member reaches the registry's state
Resolutions The landing-route entry lists the reload among the requests its redirect serves. The refused-repeat entry follows the mapping-row change. A new final entry records the decision and two rejected alternatives; it is appended, so existing ordinals are unchanged

Scope and size

Both maintainer rounds read the whole spec, so most of this MR corrects S10 text the stale-tab change did not touch. It also corrects the two S04 sentences those corrections make wrong. Round 2 also audited the whole spec against gitlab-org/gitlab master 20845b36074b, the other monolith specs, ADR-015, ADR-022 and the S33 contract, so the next reader starts from shipped facts.

The diff is past 500 lines: S10 +547/−294, S04 +33/−41, and the spec index +2/−2, documentation only. Splitting would not help. The corrections touch the same sections of S10 as the amendment, so a second open MR would have to be realigned with this one on every round.

Review round 1 (151a2edba)

The maintainer review read the whole spec, so most of this round corrects S10 text the stale-tab change did not touch. Each thread has a reply naming what moved.

Thread Change
Anchor-mismatch 409 The Error cases row and the Ruby client paragraph name both causes, a different-handle claim overlapping another in-flight claim or the lost write; only the lost write needs recovery
Banner channel Names refreshCurrentPageWithAlerts as the writer and GlobalAlerts as the reader that renders an entry once and clears it, the destination pages, and a new "Monolith global alerts" dependency
Banner destination Criterion 34 adds a :js feature case asserting the banner on the repositories route; the non-serving destination follows once criterion 14's state exists
Banner accessibility Both banners render as design-system alerts in live regions (success: alert, informational: status) with a keyboard-operable dismiss; "announced" is not claimed for the status region
Phase 1 row Includes the informational banner and the reload
AR 404 on resolution The landing route's unresolved-handle row and the Error cases 404 row answer service-unavailable, matching criterion 21, whose test note gains an AR 404 case
Setup page raised error Keeps the form and the typed handle and shows a page-level alert, as setup_form.vue does; criterion 33
Disable flow Stated as shipped: case- and whitespace-insensitive comparison, the confirmation closing and clearing on confirm, the inert control until the call and the re-read return, the accepted one-page-load navigation lag; criteria 15 and 17
Lost-write recovery The operator resolves the UUID and checks the owner anchor and slug before writing the row; corrects "AR never returns either" anchor, since the namespace read returns the owner anchor
Provisioning log Names the entries' fields and that neither carries a request header or the service token; criterion 8
Cache staleness The cached status and the cached handle both route; only the repeat-request comparison resolves the handle live
Query field Null when there is no mapping row or its resolution fails, and when the flag is off
Summary Scopes the GitLab API posture to the lifecycle calls and names the Phase 2 statistics read
Credential Names the activate mutation's ArtifactRegistry::ServiceCredential.new and why the keyword is mandatory
Pre-activation disclosure Scoped to before activation; once activated, every member can learn a registry exists, by design
Statistics figures Resolution 21, the Phase 2 row and Dependencies no longer say the figures wait on anything: gitlab-org/gitlab#627138 (closed) closed on 2026-10-02 with them merged
Billing-anchor refusal Deferred by the operator: a Follow-ups row names gitlab-org/gitlab#630929, whose scope now includes the submit-time rendering and the mutation discriminator it needs

Operator decisions in this round: document the shipped disable flow rather than change it; defer the billing-anchor rendering to gitlab-org/gitlab#630929; service-unavailable for an AR 404 on resolution; verify the UUID before the recovery write.

Review round 2 (c478ebda5)

Thread or finding Change
Phase 2 summary cell Lists the read-only suspended experience
Statistics booked as Phase 2, and the figures' priority call Moved into Phase 1, since the field (gitlab!256610), S04's header (gitlab!256618) and the figures (gitlab!256621) shipped. Covers the Phases table, Summary, endpoint table (a GET /api/v1/:slug/statistics row), dependency rows, type section and settings section. Resolution 22 records the move, and criterion 37 covers the figures
UUID "not exposed" and the type table The table lists the fields S10 declares, under their shipped names and nullability. id and userPermissions are named as other work's. slug and createdAt are never null on a resolved registry, and the shipped unknown answer to an AR 404 is a Follow-ups row (gitlab#632608)
Post-action re-read Named: the query field, re-resolving from AR after the cache invalidation, rendered instead of the payload. The success message is announced once it returns, and a refusal's or raised error's message is announced without a re-read. Criterion 17
Redaction gap Not added: ErrorReporter scrubs the configured token (be5e842f81e2), and gitlab-org/ops/artifact-registry#881 is closed. The log claim is scoped to the two log lines, a Security bullet states the scrubbing, and criterion 29 asserts it
"Amended in the same MR" S01 carries the rule in its States table, Navigation entries and activation-route resolution
"Immediately" The next read reflects the action, and Rails-rendered surfaces catch up on the next page load
Two names for one element "The where-the-handle-appears disclosure" throughout
Stale S03 line range Cites the Out of scope bullet
Zero-or-several-groups refusal Recorded as deliberate in the Phase 1 row and Error cases, and covered by criterion 33 (gitlab#630929)
Claim versus disable friction Settled by the amended ADR-015 (internal-handbook!8882): submitting the form is the acknowledgment. Resolutions 19 and 20, the ADR-015 reference and Out of scope follow it, and the "discrepancy" Follow-ups row is gone
Untestable focus clause Deleted. Both outcomes are full page loads
Settings section failed resolution Criterion 18, including the failed re-read after a successful action
Settings route gates Criterion 35
Only-gate claim and the bearer fallback Stated as holding where AR has a service token configured. A token_exchange deployment without one accepts an end user's token on an internet-facing surface (S33). The flag gates Rails, not AR, so it is not the precondition, and #692 removes the fallback
S02 methods as a later phase Approved (shipped), citing the build-out plan, and the stale "separate class" history removed. S03 is marked shipped too
gitlab#623320 not in Follow-ups A row covering four status-dependent surfaces (criteria 14, 15, 18, 19, 25 and 36), with the issue widened to match
Repositories route's informational state Criterion 36, and a "Not serving reads" row in S04's states table

From the audit, all verified at 20845b36074b:

  • Behavior stated as shipped:
    • the client instances: the per-user memoizer, the organization's service client, and the provisioning service's own client, where a slug-keyed call on the service client raises ArgumentError;
    • the repositories route on a failed resolution, which mounts the app on a well-formed handle;
    • cache collapse, which happens only for an expiring entry;
    • the repeat request's cache invalidation;
    • 429 as unavailability;
    • the mutations' organization, which comes from the request context;
    • the owner anchor's organizations.uuid;
    • the statistics resolver's own error log;
    • the overview link's history.
  • Out of date:
    • gitlab#603023 is closed;
    • #383's deferral reason has expired;
    • the lease/confirm Out of scope bullet (#310 (closed) and #311 (closed) closed);
    • #268's URL decision;
    • the criterion 3 Follow-ups row, now the plan note alone.
  • Follow-ups rows for the gaps between spec and code:
    • gitlab#632608, the AR 404;
    • gitlab!258136, the setup reload;
    • gitlab#631281, untranslated refusals;
    • gitlab#632844, the billing anchor type, which is sent as group against ADR-022's top_level_group;
    • gitlab-org/gitlab#632845 (confidential, GitLab team members only), ADR-015's collision check, rate limiting, and reason-free refusals.
  • Criterion test notes:
    • criterion 1 asserts the six anchor values literally;
    • criterion 3 asserts that the submitted handle is never logged or echoed;
    • criterion 12 follows the shipped cache window;
    • criteria 19, 20, 21, 27 and 29 cover the unconfigured base URL, the field's gate, the outage path's handles, the credential keyword, and POST retries.
  • Writing discipline: confidential issues (gitlab#593818 and gitlab#593942) are written as inline references, and the cross-section positional references are now links.

S04 (docs/specs/monolith/S04-repositories-list.md)

  • The monolith/S10 dependency row and the Phase 2 aggregate-header row record the field and the header as shipped.
  • The states table gains "Not serving reads" for the repositories route reached directly.
  • S04's Namespace statistics section: round 3 moved its phase wording into this MR (see below). !3073 fills the statistics plan's Status table.

Review round 3 (4735a42d3)

This round fixes only what the earlier review commits made inconsistent, plus two small wording fixes. Findings outside this MR's change are deferred to #1583, and the scope note proposes that further whole-spec findings go there too.

Thread Change
S04 still says Phase 2 S04 puts the field and the aggregate header in Phase 1 throughout: Summary, Phase 2 section, Namespace statistics, the four statistics criteria, Out of scope, Follow-ups, and the Aggregate header resolution. The spec index rows for S04 and S10 follow
URL shape both decided and unsettled One phrasing: the helper holds the URL shape fixed for the closed beta
Caching resolution Adds the invalidation after a repeat request's live read
ADR-015 record claim Replaced with this spec's own record of the submission
Figures' loading and freshness The figures come with the section's query and reflect its latest read, and the confirmation does not re-read them. Covered by criterion 37
Settings entry shown to every viewer "Shown for an owner" in the navigation table, in criteria 15 and 19 (with a non-owner case), and in the prose
UUID reaches the browser A Security bullet: it reaches readers and payloads, and is not a capability where a service token is configured
unknown has no owning layer The status row names the layers, and criterion 31 asserts the substitution
Two flag-gate threads The Feature flag section leaves which rows gate turning the flag on to the rollout issue, gitlab#605113. The gitlab#623320 row's bar reads "turned on"
S02 and S03 stale (two threads), lost-write recovery criterion Deferred to #1583

Merge order with !3073: both edit one paragraph of S04's Namespace statistics section. Either order works. Whichever MR merges second keeps !3073's own-document text and this MR's phase wording. The phase commit that had been on !3073 (3f8c3930f) is reverted there in c21b9029a.

Review round 4 (05ee30cb9)

Thread Change
S04's "Not serving reads" row reads as shipped Marked not yet implemented until gitlab#623320 closes, and the row says S10's criterion 36 covers it, since the route renders it rather than the view
Flag gate stated twice The bar is deleted from the gitlab#623320 row, so the Feature flag section's pointer to gitlab#605113 is the only statement
Plan still says nothing merged Corrected in !3073 (Status table, the "None has merged" paragraph, and, as of 296afec0c, the plans index and scope line). Review requested there
Section waits on the statistics read Recorded as a decision: the skeleton until the read returns, the accepted cost (a slow read delays the whole section, while a failed one resolves the field null), and the rejected read-on-open alternative in Resolution 22. Criterion 18 adds the loading state

Decisions recorded here

  • Reload the landing route, not the repositories route. The landing route already answers an activated organization by state (its table, criteria 14 and 25, and the landing-route resolution), so the setup page keeps no copy of that rule. A registry that serves reads still lands on Repositories, as the design review drew.
  • A status-neutral banner. A title naming a status, such as "already enabled", is false for a disabled registry, and for any status other than the one it names. The spec states the constraints and leaves the wording to design. The latest copy note in the design thread keeps "already enabled", so this MR's design review is where the title is settled. Its preferred verb, "claimed", is adopted.
  • Two refusal cases in the test note. One case cannot show that the setup page ignores the status, so criterion 34 asks for a read-serving and a non-read-serving registry.
  • Round 2:
    • the statistics capability is Phase 1, because it shipped;
    • spec-versus-code gaps are recorded as Follow-ups rows rather than by weakening requirements;
    • the shipped repositories-route outage mount and cache window are adopted as the spec, since both serve the rule's stated purpose;
    • the AR 404 field behavior stays a divergence, per the round-1 decision;
    • ADR-015's obligations are recorded rather than specified here.

Three pre-existing sentences corrected

The activation page's intro, the Navigation section, and the Error cases "User lacks the update ability" row each had the landing route redirect every activated organization to the repositories route. The activation-page table, criteria 14 and 25, and the landing-route and navigation resolutions all have it name a non-read-serving status itself. The reload relies on that rule, so the three now agree with it.

Follow-ups filed or updated

  • Round 1: gitlab#632608 (filed), gitlab#630929 (scope widened), gitlab#623320 (reopened).
  • Round 2:
    • gitlab#632844, filed: the billing anchor type, AR-Blocks::Closed-Beta, because the value is permanent.
    • gitlab-org/gitlab#632845 (confidential, GitLab team members only), filed: ADR-015's obligations.
    • gitlab#623320 widened to the four surfaces (note).
    • gitlab#632608 widened to the field, its cache expiry, and the repositories route's answer (note).
  • Round 3: #1583, filed, for the deferred spec corrections.
  • Not changed here, now in #1583:
    • S02's redaction paragraph, which still calls the closed gitlab-org/ops/artifact-registry#881 live;
    • S33, which still names #356 (closed) for removing the bearer fallback where #692 is the open item.

Out of scope

For the implementing MR

The setup page is served at the landing route's URL, so the reload targets the current page. refreshCurrentPageWithAlerts in app/assets/javascripts/lib/utils/url_utility.js exists for that case. Its comment warns that visitUrlWithAlerts to the current URL can be a same-document navigation, which skips the reload that renders the alerts. Criterion 34's :js feature case for a non-serving registry waits on the landing route's informational state, gitlab-org/gitlab#623320; the read-serving case does not. S10's Follow-ups name each shipped gap and its issue.

Governing ADRs

  • ADR-014 (frontend to AR): the reload is a landing-route visit like any other. It reads the persisted mapping and the cached resolution, and the browser makes no AR request.
  • ADR-015 (slug policy, internal): S10 now follows the amended text: atomic registration, with submitting the form as the acknowledgment. The three obligations it does not yet specify are recorded and tracked.
  • ADR-022 (namespace decoupling): the banner names the handle AR resolved for the stored UUID. The anchor table's top_level_group matches the ADR, and the shipped group is tracked in gitlab#632844.
  • Freshness: scripts/adr-freshness.sh reports two upstream changes since the mirror. ADR-007's Maven and npm foreign-key indexes do not touch the namespace columns S10 cites. ADR-004's staged rate-limit delivery names no provisioning limit.

Not affected

  • docs/user/: the user docs describe claiming a handle but not this refusal.
  • docs/testing/: no e2e row covers activation.
  • docs/plans/: the activation plan's Step 15 cites criterion 34 for its success half, and criterion 32's focus clause, which this MR deletes. Every step of that plan has merged, and a merged step's entry records what shipped, so it stays.

How to verify

  • Read criterion 34 and the Error cases row against the activation-page table. Every state the reload can reach is a mapping-row state the landing route already answers.
  • For round 2, each Follow-ups row names the criterion the shipped code misses and the issue tracking it.
  • markdownlint, Vale, and lychee pass on both files.

Related to gitlab-org/gitlab#619522 (closed)

Edited by Narendran

Merge request reports

Loading
Loading