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-07-30
# Implement Organizations Read-Only Mode — Weekly Status Update
*Sign-in now survives read-only mode (writes paused while an Organization is moved between Cells); the remaining write-blocking work is in review.*
## 🕐 total hours spent this week by all contributors: [number of hours]
## 🎉 achievements
* [!243972](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/243972) merged (2026-07-27), exempting sign-in, SSO, password reset and identity verification so users can still authenticate to read a read-only Organization. After some discussion with @abdwdd we might revert this to enforce a blanket controller wide block for now and figure out the best approach moving forward.
* [!247588](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/247588) fixes `read_only_reason` never persisting, which had every blocked write returning a permanent 403 instead of 503 + Retry-After.
* [!247591](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/247591) gives GraphQL mutations and git push the same 503-for-time-bounded / 403-for-indefinite responses the web and API surfaces already return.
## 🚫 blockers
* [!247589](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/247589) and [!247591](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/247591) are still in drafts
* [!247591](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/247591) is stacked on [!247589](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/247589) and cannot merge until it lands, so the 503/403 fixes are queued behind it.
* Feature-flag rollout [#603377](https://gitlab.com/gitlab-org/gitlab/-/work_items/603377) cannot start on internal Organizations until those three MRs merge.
## ▶️ next
* Take [!247588](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/247588), [!247589](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/247589) and [!247591](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/247591) out of draft and get reviewers assigned.
* Confirm [#604825](https://gitlab.com/gitlab-org/gitlab/-/work_items/604825) auth-time cascading writes stay blocked and test-covered now that [!243952](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/243952) is closed in favour of the shared facility.
* Start [#603377](https://gitlab.com/gitlab-org/gitlab/-/work_items/603377) on internal Organizations once enforcement lands, then watch the blocked-write logs before widening cohorts.
* Keep syncing with @rutgerwessels on [#599101](https://gitlab.com/gitlab-org/gitlab/-/work_items/599101), giving cron workers an Organization context so they skip read-only Organizations.
* @abdwdd is taking on refining the epic to reduce scope for a more realistic delivery time
_Copied from https://gitlab.com/groups/gitlab-org/-/epics/20404#note_3620731174_
<!-- STATUS NOTE END -->
epic