docs(specs): refuse a different handle on an activated organization in S10

What does this MR do and why?

Amends monolith/S10 so a repeat activation that carries a different handle, sent to an organization that is already activated, is refused as a conflict. Today it resolves silently to the existing registry.

Today's behavior disagrees with the spec's own principle. Criterion 4 says the loser of a concurrent different-handle race surfaces AR's 409 "rather than resolving to the winner's registry". The same request sent a moment after the winner, not concurrently, currently gets success and the other handle back (observed live against GDK and a local AR; details in gitlab#619522). The "repeat request against an already-mapped organization" sentence was written in the same-handle context. Nothing in the spec grants idempotency for a different handle.

Changes to docs/specs/monolith/S10-activation-and-deactivation.md

Section Change
Provisioning trigger, Validation row Scoped to organizations with no mapping row; a repeat runs neither check
Behavior, mapping-row step 4 Drops the repeat-request sentence, so the step covers only the concurrent uniqueness race
Behavior, new repeat-request passage Checks the mapping row first and makes no provisioning call. Resolves the handle live, never from the cache. Outcomes: same handle resolves to the existing registry; different handle is refused as a conflict naming the claimed handle and carrying the resolved registry; purged is refused as re-onboarding not being available; a failed resolution maps by cause (401/403 not-available, transport or 5xx service-unavailable); an AR 404 is refused as service-unavailable. The submitted handle is never persisted, logged, forwarded, or echoed.
Behavior, caching paragraph No decision reads the cache, so an emergency handle change reaches display only
Acceptance criterion 3 One RSpec case per repeat arm, plus a cached-entry-disagrees case. No criterion is renumbered.
Setup-page criteria The page enforces exactly Rails' syntactic rules; the conflict refusal replaces the form with a link to the registry
Error cases New row: organization already activated under a different handle
Security considerations The handle-validation bullet names the repeat path as its one exception
Out of scope Re-onboarding: the claim precedes the repoint, and the flow owns the purged repeat arm
Follow-ups New row for the monolith implementation and the plan step 2 note, both carried by gitlab#619522
Resolutions New entry recording the decision and two rejected alternatives. Appended at the end, so existing ordinals are unchanged.

Decisions recorded here

  • Never grant an uncompared handle. A failed resolution, a 404, and a purged namespace all refuse rather than resolve to the existing registry.
  • Live resolution for the comparison. One AR read on a human-paced mutation path, so the cache's staleness and negative caching never decide a claim.
  • Purged is refused, not provisioned. Provisioning a new handle before the out-of-scope repoint exists would orphan the new namespace against the surviving mapping row.
  • Reachability. The setup page renders only without a mapping row, so the case is reachable from a page loaded before activation (for example, a second tab) or through the mutation directly.
  • The plan is not touched. Step 2 of docs/plans/monolith/2026-08-04-activation-and-deactivation.md already shipped (gitlab-org/gitlab!250653 (merged)) with the idempotent behavior, and a merged step's entry is a record of what shipped. The Follow-ups row carries the note it owes.

Out of scope

  • Warning an organization with zero or several top-level groups before it reaches the handle form: pre-existing, filed as gitlab-org/gitlab#630929.
  • Whether the permanence callout satisfies ADR-015's acknowledgment: pre-existing, already routed to the ADR-015 author in Follow-ups.

ADR check

  • ADR-022 (namespace decoupling): consistent. The handle is immutable, and Rails persists only the UUID. The comparison reads the cached resolution and adds no column.
  • ADR-014 (frontend to AR): the refusal reuses the mutation's payload errors, and the service-unavailable arm follows the existing error mapping.
  • Freshness: scripts/adr-freshness.sh reports no upstream change to 014 or 022.

Not affected

  • docs/testing/: no e2e scenario covers activation. The monolith implementation MR will revisit this.
  • docs/user/: this is a spec-only change, so no customer-visible surface changes in this MR.
  • Configuration, OpenAPI/Bruno: not touched.

How to verify

Read the diff against criterion 4 and the concurrent different-handle paragraph it mirrors.

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

Edited by Narendran

Merge request reports

Loading
Loading