Organization-level Runner (Admission) Controller
## Overview Implement organization-level Runner (Admission) Controllers, extending the existing instance-level implementation ([&19660](https://gitlab.com/groups/gitlab-org/-/work_items/19660)) to support organizations as a first-class entity. This enables organizations to manage their own runner controllers independently, with full cell-awareness for GitLab Cells architecture. ## Background Instance-level runner controllers were implemented in [&19660](https://gitlab.com/groups/gitlab-org/-/work_items/19660) with: - `ci_runner_controllers` and `ci_runner_controller_tokens` tables in `gitlab_ci_cell_local` schema - Instance-level and runner-level scopings - External REST API, internal Job Router API, audit events, PDI events, CLI commands, and client-go support Organization-level runner controllers extend this model to the `gitlab_ci` schema (sharded by `organization_id`), enabling multi-tenant runner admission control that is cell-aware and routable. ## Key Design Principles 1. **Cell-aware**: All models live in `gitlab_ci` schema sharded by `organization_id`. Tokens must be routable via the HTTP Router with organization-encoded prefixes. 2. **Mirrors instance-level patterns**: Follows the same controller → token → scoping model established in Phase 1, adapted for organization ownership. 3. **Scoping hierarchy**: Organization-level controllers support scoping at: - **Organization level** — applies to all groups/projects in the organization - **Organization runner level** — applies to specific organization-owned runners - **Group level** — applies to all projects within a group - **Project level** — applies to jobs in a specific project - **Group runner level** — applies to specific group-type runners - **Project runner level** — applies to specific project-type runners 4. **Downward application**: Scopes always apply "downward" in the hierarchy. ## Database Schema ```mermaid graph TD subgraph org_scoped["gitlab_ci — shard: organization_id"] org_ci_runner_controllers["org_ci_runner_controllers"] org_ci_runner_controller_tokens["org_ci_runner_controller_tokens"] org_ci_runner_controller_org_level_scopings["org_ci_runner_controller_org_level_scopings"] org_ci_runner_controller_runner_level_scopings_org_type["org_ci_runner_controller_runner_level_scopings<br/>(organization runners)"] org_ci_runner_controller_runner_level_scopings_group_type["org_ci_runner_controller_runner_level_scopings<br/>(group runners)"] org_ci_runner_controller_runner_level_scopings_project_type["org_ci_runner_controller_runner_level_scopings<br/>(project runners)"] org_ci_runner_controller_scopings_group["org_ci_runner_controller_scopings_group"] org_ci_runner_controller_scopings_project["org_ci_runner_controller_scopings_project"] group_type_ci_runners["group_type_ci_runners<br/>(existing)"] project_type_ci_runners["project_type_ci_runners<br/>(existing)"] org_ci_runner_controllers -->|has_many| org_ci_runner_controller_tokens org_ci_runner_controllers -->|has_one| org_ci_runner_controller_org_level_scopings org_ci_runner_controllers -->|has_many| org_ci_runner_controller_runner_level_scopings_org_type org_ci_runner_controllers -->|has_many| org_ci_runner_controller_runner_level_scopings_group_type org_ci_runner_controllers -->|has_many| org_ci_runner_controller_runner_level_scopings_project_type org_ci_runner_controllers -->|has_many| org_ci_runner_controller_scopings_group org_ci_runner_controllers -->|has_many| org_ci_runner_controller_scopings_project org_ci_runner_controller_runner_level_scopings_group_type -->|FK: runner_id| group_type_ci_runners org_ci_runner_controller_runner_level_scopings_project_type -->|FK: runner_id| project_type_ci_runners end style org_scoped fill:#0d1117,stroke:#a78bfa,stroke-width:2px ```
epic