Implement Organizations Read-Only Mode
## Problem statement We need to be able to disable write access to a given Organization while their migration (eg. to a Protocell) is in progress. This is **not** the same as instance-wide Maintenance Mode: the Organization's content (groups, projects, settings etc) should remain readable but not editable, with a banner indicating the Organization is in read-only mode. The design is formalized in [ADR 010: Organization Read-Only Mode](https://handbook.gitlab.com/handbook/engineering/architecture/design-documents/organization/decisions/010_organization_read_only_mode/). In summary: * A per-Organization state on `Organizations::Stateful`: `active → read_only_initialization → read_only` (and back), with a `reason` recorded for audit (migration, isolation, incident, billing, legal) * Enforcement keyed off `Current.organization` at every write surface: controllers, REST API (Grape), GraphQL mutations, `Gitlab::GitAccess`, Container Registry, LFS, and Sidekiq * Org-scoped Sidekiq jobs drain; cron workers skip read-only Organizations; BBMs are paused on the source Cell * A cutover readiness contract gates the data-copy step on a fully drained source Cell * An authentication exemption keeps sign-in working (existing `users` row updates allowed; new `users` row creation blocked) The POC (gitlab-org/gitlab!228743) validated the approach and will not be merged; production implementation builds on the merged unified state machine (gitlab-org/gitlab!230909). ## Exit criteria * Write requests (web UI, GraphQL mutations, REST API, git push, registry push, LFS upload) to an Organization in `read_only_initialization` or `read_only` fail with a structured error (`503` + `Retry-After` for time-bounded reasons, `403` otherwise); reads continue to work * Authentication remains available; no new `users` rows are created for a read-only Organization * Org-scoped Sidekiq jobs drain and cron workers skip the read-only Organization, observable via structured logs * A cutover readiness check reports when the source Cell is fully drained for the Organization * New pipelines are blocked and in-flight CI jobs are cancelled on entering read-only * All pages owned by the Organization show a prominent read-only banner * Rollout is gated by environment- and Organization-scoped feature flags, default-off on Self-Managed/Dedicated <!-- STATUS NOTE START --> ## Status 2026-06-25 # Org Read-Only Mode — Weekly Status ([&20404](https://gitlab.com/groups/gitlab-org/-/work_items/20404)) *Core write-surface enforcement merged dark behind the feature flag; Sidekiq resolver and auth-exemption follow-ups in flight, plus the epic broken down into cutover/runbook/worker child issues.* 🕐 **total hours spent this week by all contributors**: 20 🎉 **achievements**: * Merged [!241161](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/241161) — core write-surface enforcement across web controllers, REST/Grape API, GraphQL mutations, and `Gitlab::GitAccess` git push, gated on `Organization#read_only?` and behind the `organization_read_only_enforcement` flag (default-off, verified dark). Closes [#603366](https://gitlab.com/gitlab-org/gitlab/-/work_items/603366), building on the model layer in [!240492](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/240492). * Opened [!242175](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/242175) — `Gitlab::SidekiqMiddleware::OrganizationResolver`, the foundational resolver for the Sidekiq track (returns org / `:cross_org` / `:unresolved`). * Opened [!242344](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/242344) (MR A) — exempt auth controllers from enforcement so sign-in/SSO/token POSTs aren't blocked while an org is read-only ([#602813](https://gitlab.com/gitlab-org/gitlab/-/work_items/602813)). * Filed rollout tracking issue [#603377](https://gitlab.com/gitlab-org/gitlab/-/work_items/603377) for the `organization_read_only_enforcement` flag. * Broke the epic into child issues: [#603918](https://gitlab.com/gitlab-org/gitlab/-/work_items/603918) (ApplicationWorker default org resolution), [#603919](https://gitlab.com/gitlab-org/gitlab/-/work_items/603919) (operator runbook & rollback docs), [#603920](https://gitlab.com/gitlab-org/gitlab/-/work_items/603920) (destination-Cell write-conflict window at cutover). 🚫 **blockers**: * Open design question on [#603920](https://gitlab.com/gitlab-org/gitlab/-/work_items/603920): whether the destination Cell must treat the incoming Organization as `read_only_initialization` until cutover completes, to avoid source/destination split-brain. * Per-worker opt-in for `OrganizationResolver` doesn't scale to ~1,000+ workers; the org-resolvability CI contract ([#602816](https://gitlab.com/gitlab-org/gitlab/-/work_items/602816)) depends on the auto-resolution lever in [#603918](https://gitlab.com/gitlab-org/gitlab/-/work_items/603918) landing first. ▶️ **next**: * We can start FF rollout per [#603377](https://gitlab.com/gitlab-org/gitlab/-/work_items/603377): enable for internal/test Organizations on GitLab.com, then expand by cohort. * Land [!242344](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/242344) (MR A) and follow up with MR B (block new-user-row creation in `Gitlab::Auth::OAuth::User`). * Advance the Sidekiq track on top of [!242175](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/242175): [#603918](https://gitlab.com/gitlab-org/gitlab/-/work_items/603918) default resolution, cron-skip middleware ([#602817](https://gitlab.com/gitlab-org/gitlab/-/work_items/602817)), cron worker audit ([#602818](https://gitlab.com/gitlab-org/gitlab/-/work_items/602818)). * Resolve the destination-Cell read-only decision ([#603920](https://gitlab.com/gitlab-org/gitlab/-/work_items/603920)) and draft the operator runbook ([#603919](https://gitlab.com/gitlab-org/gitlab/-/work_items/603919)). _Copied from https://gitlab.com/groups/gitlab-org/-/epics/20404#note_3491045289_ <!-- STATUS NOTE END -->
epic