Step 4: Enforce read-only mode for GraphQL mutations
## Summary
Block GraphQL mutations that target namespaces in `maintenance` state. Rack middleware alone is insufficient for GraphQL because the namespace is embedded in the request body (all GraphQL requests are POST to `/api/graphql`).
> **POC approach:** This will be part of a single POC MR (does not need to merge to master). Maintenance mode will be toggled via Rails console.
## Dependencies
- **Depends on**: Step 1 (#591688) — state machine transitions
- **Parallel with**: Step 2 (#591689) and Step 3 (#591690)
## Context
Parent issue: https://gitlab.com/gitlab-org/gitlab/-/issues/590009
GraphQL mutations are all POST requests to a single endpoint, so the namespace-scoped middleware (Step 2) cannot determine the target namespace from the URL path alone. Enforcement must happen at the GraphQL layer.
## Tasks
- [ ] Investigate enforcement approaches:
- **Option A**: Custom `GraphQL::Analysis::AST::Analyzer` that inspects mutation arguments for namespace/group/project references and checks maintenance state
- **Option B**: Override in `Mutations::BaseMutation` to check namespace state before executing
- **Option C**: Add a shared concern to resolvers/mutations that checks the namespace of the resolved object
- [ ] Implement chosen approach
- [ ] Ensure GraphQL **queries** (reads) still work — only mutations should be blocked
- [ ] Return a structured GraphQL error with appropriate message and extensions (e.g., `{ "extensions": { "code": "NAMESPACE_MAINTENANCE" } }`)
- [ ] Add specs covering: mutation blocking, query passthrough, nested namespace resolution
## Key Files
- `app/graphql/mutations/base_mutation.rb`
- `app/controllers/graphql_controller.rb`
- `app/graphql/types/mutation_type.rb`
## Effort Estimate
Medium (2-3 days)
task