Step 2: Create namespace-scoped read-only middleware
## Summary
Create middleware that blocks write HTTP requests (POST/PATCH/PUT/DELETE) for namespaces in `maintenance` state. This is the core enforcement mechanism for TLG read-only mode on web and REST API routes.
> **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 3 (#591690) and Step 4 (#591691)
- Step 3 (error page) integrates into this middleware for rendering responses
## Context
Parent issue: https://gitlab.com/gitlab-org/gitlab/-/issues/590009
The existing `Gitlab::Middleware::ReadOnly` (`lib/gitlab/middleware/read_only.rb`) blocks writes at the **instance** level. We need a similar mechanism scoped to individual **namespaces**.
## Tasks
- [ ] Create `Gitlab::Middleware::NamespaceReadOnly` (or extend existing `ReadOnly` middleware)
- [ ] Implement namespace resolution from request paths:
- Parse path to extract top-level namespace segment (strip known prefixes like `/api/v4/`, `/groups/`, etc.)
- Use `Gitlab::PathRegex.full_namespace_route_regex` and `route_hash` for accurate matching
- For API routes, extract from route params (`:namespace_id`, `:group_id`, `:id`)
- [ ] Look up root namespace and check `effective_state == :maintenance`
- [ ] Implement caching strategy for namespace state lookups (request-store + short Redis TTL to avoid per-request DB hits)
- [ ] Return 503 with maintenance message and `Retry-After` header for blocked requests
- [ ] Reuse existing allowlist pattern from `ReadOnly::Controller` (internal routes, sessions, sidekiq admin, etc.)
- [ ] Register middleware in the Rack stack
- [ ] Add specs covering: write blocking, read passthrough, allowlisted routes, cache behavior, nested namespace resolution
## Key Files
- `lib/gitlab/middleware/read_only.rb` (reference pattern)
- `lib/gitlab/middleware/read_only/controller.rb` (reference: allowlists, route_hash, error responses)
- `ee/lib/ee/gitlab/middleware/read_only/controller.rb` (reference: 503 maintenance response pattern)
- `lib/gitlab/path_regex.rb` (namespace path parsing)
- `config/initializers/rack_attack.rb` or middleware registration location
## Design Decisions
- **Option A (Recommended)**: New standalone middleware — cleaner separation, doesn't couple with instance-level read-only
- **Option B**: Extend existing `ReadOnly::Controller#read_only?` — simpler but mixes concerns
## Effort Estimate
Medium (3-5 days)
task