Skip to content

Supported GitLab Settings

The map of GitLab settings this role manages, what remains out of scope, and why. The generated capability matrix defines the resource, backend, and convergence counts. This narrative explains scope decisions and implementation limits.

Backend model: each resource declares transports that can reconcile it (rest/graphql/rails); gitlab_settings_backend is an ordered preference. The role selects the first capable transport, then requires that transport's channel to be ready rather than falling through to a later authority. A coverage gap fails strict mode; compatible records the operation as skipped. The default api preset is [rest, graphql]; Rails requires explicit selection.

Scope decision: all three tiers are in scope and the out-of-scope classification is ratified — ADR-0005. Every surface audited into scope — the originally-audited Tier 1–3 resources plus the surfaces later resolved in by re-audit or persona decision (project feature flags, the group SAML provider, and the GraphQL-served surfaces) — is implemented. This audit now tracks coverage against future GitLab releases and evolving persona scope.

Classification rules, per the role boundary (ADR-0001), the persona and scope model (ADR-0007), and the test-data split the role was extracted under.

Classify by the owning persona, not one viewpoint

The role provides capability across the full settings surface; each persona (SRE at instance scope, department head at group scope, team at project scope) declares what to converge within their authority. Whether a surface is "configuration" is judged from the perspective of the persona who owns it, not from an instance admin looking down. This dissolves the once-fuzzy "operational" exclusion for owner-declarable config.

  • Settings: configuration state a persona declares and expects to converge on resources they own — in scope. A surface counts even if it looks "operational" to a higher scope, provided the owning persona would declare it as desired state (project feature flags are the representative example — operational to an instance admin, GitOps-declared config to the owning team).
  • Content: work artifacts, not desired-state config — issues, merge requests, epics, wikis, snippets, releases, milestones, commits — out of scope regardless of persona.
  • Provisioning: in scope for the container principals — groups, projects, user accounts, and instance runners are created from their declarations and deleted with state: absent (each verified by a live feature probe). Imports and repository content stay out of scope.
  • Credentials: ad-hoc token and secret issuance — access tokens, personal access tokens, trigger tokens — out of scope (issuance belongs to callers or a test-data role; the role only carries secrets that are part of a setting, such as hook tokens). Deploy tokens and instance OAuth applications are the declarative exception: named, immutable resources reconciled by identity, with the one-time secret captured to a declared controller file — the same contract as instance-runner registration.
  • Data plane / infrastructure: package and registry bytes, repository files, clusters, agents, Geo nodes — out of scope regardless of persona. Instance runner registration and configuration is in scope (instance runners); runner fleets beyond that are not.
  • Read-only / diagnostic: statistics, metadata, search, Sidekiq, events, validation endpoints — nothing to declare — out of scope.

Implemented

All implemented resources share one desired-state schema. See the generated matrix for the current complete list and counts.

Surface Resources
Instance application settings; system hooks + URL variables + custom headers; appearance; plan limits; broadcast messages; CI/CD variables; topics; license; feature flags; audit event streaming destinations (GraphQL); compliance policy settings; organization settings (GraphQL); user accounts with custom attributes (schema); instance runners (schema); OAuth applications (secret captured once)
Group attributes (create/delete included); hooks + children; push rule; protected branches; custom attributes; integrations; MR approval settings; labels; badges; member roles; SSH certificates; protected environments; deploy tokens (token captured once); audit event streaming destinations (GraphQL); CI/CD variables; members; LDAP and SAML group links; SAML SSO provider (Rails); custom emoji, compliance frameworks, value streams, work item types, dependency proxy + TTL (GraphQL)
Project attributes; hooks + children; push rule; protected branches; protected tags; MR approval settings and rules; custom attributes; integrations; Pages settings and domains; error tracking; job token scope and allowlists; labels; badges; environments; protected environments; deploy keys; pipeline schedules and variables; registry repository/tag and package protection rules; external status checks; CI/CD variables; members; group shares; remote mirrors; freeze periods; feature flags + user lists; escalation policies, on-call schedules + rotations, security policies (GraphQL)

GraphQL-served surfaces (ADR-0008)

These have no REST endpoint and are reconciled over GraphQL from committed, version-pinned operation documents. They apply under the default api preset (which includes graphql). A rest-only preference is a strict coverage error or a compatible-mode skip.

Resource Scope Shape Notes
Custom emoji group collection (create+destroy) name→gid; url change = recreate (non-atomic)
Compliance frameworks group collection name→gid; default is single-per-group
Value streams group collection nested stages full-replace on update
Work item types namespace collection built-in types lock-guarded except per-namespace availability (enabled, GitLab 19.1+; upstream's 18.11 marker is wrong, verified live); partitioned updates split name/icon from the availability toggle
Dependency proxy + TTL group two update-only singletons always-present
Escalation policies project collection name→gid; ordered replace-all rules
On-call schedules + rotations project collection + nested child schedules ordered before escalation policies; rotations reconcile full config (timezone-aware)
Security policies project policy-document (replace or merge) one policy.yml, five arrays; full ownership by default, mode: merge co-owns; unlinked target skips
Organization settings organization converge-only singleton organizationUpdate; no structural lifecycle

Coverage by tier

The REST/Rails resources, all Implemented. Each row records what the resource needed beyond the uniform policy + planner + schema + tests cost, as identified when the audit was first taken. (The GraphQL-served surfaces are listed in the table above, not here.)

Tier 1 — Core settings gaps

The difference between "manages some settings" and "holistic". Ordered roughly by expected demand.

Resource Scope Catalog Needs beyond policy + planner Status
CI/CD variables instance (/admin/ci/variables), group, project Composite identity: variable uniqueness is key plus environment_scope; the reconciler's identity matching must support multi-key match and the filter[environment_scope] query. Values are secrets → sensitive_values path. Implemented
Integrations group, project (/integrations/{slug}) None — each integration is a path-parameterized singleton (PUT configure, DELETE disable); fields pass through per integration. Implemented
Labels group, project None — name-keyed collection with update and delete. Implemented
Badges group, project Identity by name (falling back to link_url). Implemented
Environments project Name-keyed collection; update by environment_id (item_param_field: id). Implemented
Protected environments project, group Access arrays share the protected-ref round-trip caveat (compare_ignore). Implemented
Deploy keys project Identity by title; can_push is the reconciled scalar. Implemented
Freeze periods project Identity by freeze_start/freeze_end pair or id; needs a match decision. Implemented
CI/CD job token scope + allowlist project Singleton (PATCH enabled) plus allowlist collection keyed by target_project_id — needs project-path → id resolution in the planner or module. Implemented
Remote mirrors project Identity by url (which embeds credentials on create → secret handling like hook tokens). Implemented
Pages settings + domains project Settings is a singleton; domains are a domain-keyed collection with certificate fields as secrets. Implemented
Group MR approval settings group None — singleton, mirrors the existing project resource. Implemented
Appearance instance (/application/appearance) None — singleton. Implemented
Plan limits instance None — singleton (plan_name as a body selector). Implemented
Broadcast messages instance Identity by message or id; needs a match decision. Implemented
License instance Special lifecycle: add (POST /license) and delete (DELETE /license/:id) only — no update; reconcile by comparing the active license fingerprint. Implemented
Topics instance Name-keyed collection; avatar upload is out of scope. Implemented

Tier 2 — Access management

Settings-adjacent and commonly expected in settings-as-code (the GitHub equivalent of collaborators/teams). In scope per ADR-0005: access management means settings on existing principals — declaring a member who does not exist is an error or skip, never an implicit user creation.

Resource Scope Catalog Needs beyond policy + planner Status
Members group, project Identity is user_id; declaring members by username requires a username → id lookup step. Access levels reuse the existing name table. Implemented
Share project with group project Group-path → id resolution. Implemented
LDAP group links group Keyed by cn/filter + provider. Implemented
SAML group links group Keyed by saml_group_name. Implemented
Member roles group (EE) Name-keyed collection. Implemented
Group SSH certificates group Title-keyed collection. Implemented
Group SAML SSO provider group (EE) Rails-only ActiveRecord model with no REST/GraphQL endpoint; backends: [rails]. Under the api default, strict mode fails and compatible mode records a skip; it applies under rails/all. First Rails-exclusive resource (ADR-0006). Instance SAML is boot-time deployment config, out of scope. Implemented

Tier 3 — Specialized and emerging

Resource Scope Catalog Needs beyond policy + planner Status
Feature flag toggles (/features) instance Set-by-name semantics; no read-modify-write (value writes are idempotent by construction). Implemented
Project feature flags + user lists project Name-keyed collections (/projects/{id}/feature_flags, /feature_flags_user_lists). Scalar fields active/description reconcile; strategies apply on create (nested-strategy reconciliation is a follow-up); version is GitLab-defaulted. In scope for the owning team persona per ADR-0007; declared-state-wins reconciles live tweaks — teams opt in knowingly. Distinct from the instance /features gates. Implemented
Error tracking settings project None — singleton. Implemented
Container registry repository protection rules project Pattern-keyed collection (repository_path_pattern). Implemented
Container registry tag protection rules project Pattern-keyed collection (tag_name_pattern); distinct endpoint from repository rules. Found by the 2026-07-16 re-audit — the "Protected container registry" umbrella name had hidden it. Implemented
Package protection rules project Pattern-keyed collection. Implemented
External status checks project Name-keyed collection with a write-only shared_secret. Found by the re-audit. Implemented
Compliance policy settings instance Admin singleton (/admin/security/compliance_policy_settings). Distinct from group compliance frameworks and security policies, which are served over GraphQL (see the GraphQL-served table above). Found by the re-audit. Implemented
Pipeline schedules project Description-keyed collection; variables sub-resource like hook children. Implemented
Audit event streaming destinations instance, group ✅ via GraphQL Live GitLab 19 removed the legacy REST endpoints; both surfaces ride the streaming API from committed GraphQL documents, identified by name with a write-only secret_token. Implemented

Explicitly out of scope

Per the classification rules: all content resources (issues, MRs, epics, boards, wikis, snippets, milestones, releases, discussions, notes), imports and repository storage moves, credential issuance beyond what a setting carries (access tokens, PATs, pipeline triggers), package registry data planes (Maven, NPM, PyPI, Conan, Debian, Go, Helm, RubyGems, NuGet, Terraform modules, Composer), runner fleets beyond instance-runner registration, clusters and agents (infrastructure), Geo nodes (deployment topology), user-scoped preferences (notification settings, to-dos, avatars), and read-only/operational endpoints (statistics, metadata, search, Sidekiq, service data, GLQL). Group, project, and user account provisioning moved in scope with their feature tracks, and the standalone-resources pass brought in deploy tokens, instance OAuth applications, and user custom attributes — declarative resource management with one-time secret capture, distinct from ad-hoc credential issuance.

Notification settings — assessed, out. The notification-settings API (global, group, and project levels) acts on the authenticated user's own preferences; GitLab has no admin surface for managing another user's notification levels. A role running as a service identity could only ever manage that service account's notifications, which is not instance configuration. They stay out of scope with the other user-scoped preferences.

If any of these later prove to be settings in a customer's sense (for example runner configuration as opposed to registration), they re-enter through this audit, not ad hoc.

The 2026-07-16 re-audit also confirmed several near-misses as correctly out of scope: access requests (group + project) are a membership-approval workflow with no idempotent declarative form; error tracking client keys are DSN-style credential issuance; pipeline triggers and access tokens are credential issuance. Deploy tokens and instance OAuth applications, originally grouped with these, moved in with the standalone-resources pass: each is a named, immutable, declaratively identified resource whose secret the role captures once and never reads back.

GraphQL follow-ups (deferred)

The GraphQL-served surfaces (table above, details) are implemented; three refinements are deliberately deferred:

  • Security-policy merge modedone (2026-07-31): mode: merge preserves policies authored outside the role (removals happen only through per-policy state: absent, and undeclared arrays are left untouched), while the default stays full-replace ownership. Convergence still uses named APPEND/REPLACE/REMOVE operations, one policy per mutation.
  • On-call rotation config reconciliationdone (2026-07-31): the schedule read carries full rotation config and drift converges through the update mutation, timezone-aware.
  • Per-namespace work-item-type availabilitydone (2026-07-31): enabled reconciles through the availability toggle with a variant deep read selected only when a declaration carries the field, so older supported versions keep working and built-ins accept exactly this one mutation past the lock-guard. GitLab's Experiment is not yet functional on any lab version (absent through 18.11; on 19.1 the toggle is accepted but not applied), so live convergence waits on a 19.2+ instance.

Resolved scope decisions

Project-level feature flags + user listsin scope (2026-07-17). A flag's rollout strategy looks operational from an instance admin's view, but the owning team may legitimately want deterministic, auditable GitOps control of their own project, and a settings-as-code role should provide the capability and let the owning persona decide whether to declare it (ADR-0007). Implemented (2026-07-18); the caveat that GitOps management reconciles live tweaks is documented for teams to opt in knowingly. Distinct from the already-implemented instance /features gates.

What a new surface costs

The architecture makes each surface small and uniform. A REST surface needs:

  1. A policy entry in module_utils/gitlab_resource_policy.py (paths, identity, write-only fields, compare exclusions) — it inherits backends from the capability rule (rest, plus rails when the executor handles it natively).
  2. A planner mapping in module_utils/gitlab_settings_payload.py, secrets separated.
  3. A schema page in docs/schema/ and unit/integration coverage.

A Rails-only surface (the group SAML provider is the first) adds an apply_settings.rb handler and declares backends: (rails,); a GraphQL surface adds committed files/graphql/*.graphql documents, a gitlab_graphql_policy entry, and (for a genuinely new shape) a reconciler kind — see GraphQL-served settings. The consistency gates force every surface to have a policy, a planner mapping, docs, and (for GraphQL) committed operation documents before it can ship.

Review trigger

Re-run this audit when the catalog is regenerated against a new GitLab release (new endpoints appear in the diff), or when a consumer requests a surface listed as out of scope.