Create Read-Only mode for top-level groups while transferring to Organizations
## Problem Statement When transferring a top-level group (TLG) to an Organization, we need a way to temporarily place the TLG in read-only mode to avoid data integrity failures during the transfer. For smaller/trivial TLGs, the transfer is complete in seconds. Larger groups will take longer, however, and we need to prevent accidental writes or background tasks which may modify data that is currently being exported. We may be able to achieve this using Policies, and there is overlap with ~"Category:Groups & Projects" work in support of State Management. As well as the TLG itself (eg. its metadata and settings), we should prevent all cascading writes to its: * Child groups * Child projects * Users We must also identify and block any write attempta via our own internal processes, eg. * cron jobs * scheduled tasks * cleanup jobs ## Exit criteria * We should be able to trigger a state change on a specific TLG that prevents any write attempts from succeeding against its ancestors * API calls against the TLG and its descendants should return a generic error if they are write requests. * We should also prevent any internal GitLab-initiated processes from writing to the TLG's data: * **Stretch goal**: Users should still be able to browse the TLG and its ancestors via the web, but without write permissions * Alternatively: All TLG-related webpages should show a generic maintenance message if we're unable to deliver a true read-only UI/UX. ## DRI @chen-gitlab <!-- STATUS NOTE START --> ## Status 2026-03-12 :clock1: **total hours spent this week by all contributors**: 12 :tada: **achievements**: - Steps 1-3 of the POC (gitlab-org/gitlab#590009) are now code-complete with green pipelines and awaiting review: - gitlab-org/gitlab!226399 (Step 1: State machine transitions for `maintenance` state) — pipeline passing, in review by @abdwdd - gitlab-org/gitlab!226983 (Step 2: Namespace-scoped read-only middleware) — pipeline passing, in review by @abdwdd - gitlab-org/gitlab!227343 (Step 3: Web UI maintenance error page) — pipeline passing, ready for review - Closed 2 deferred child tasks as out of scope for the POC per @abdwdd's feedback: gitlab-org/gitlab#591692 (admin toggle) and gitlab-org/gitlab#591693 (background write path audit) :issue-blocked: **blockers**: - gitlab-org/gitlab!226399 and gitlab-org/gitlab!226983 have unresolved review discussions that need to be addressed before merging - we might need to rethink the middleware approach as @rutgerwessels pointed out that [the organization resolution is implemented in the Controller layers and runs _after_ Rails / Grape set up the routing and no middleware is used](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/226983#note_3162312112). :arrow_forward: **next**: - Address review feedback on Steps 1-2 MRs - Begin gitlab-org/gitlab#591691 (Step 4: GraphQL mutation enforcement) — the last remaining in-scope POC task - As an alternative to the middleware approach, create an `EnforcesNamespaceReadOnly` concern (similar to the existing `EnforcesStepUpAuthenticationForNamespace`) and include it in `Groups::ApplicationController` and `Projects::ApplicationController` as a `before_action` - this is being discussed - Target completing the full POC (all 4 steps) by end of milestone 18.11 _Copied from https://gitlab.com/groups/gitlab-com/gl-infra/-/epics/1866#note_3152612951_ <!-- STATUS NOTE END -->
epic