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 mode — done (2026-07-31):
mode: mergepreserves policies authored outside the role (removals happen only through per-policystate: absent, and undeclared arrays are left untouched), while the default stays full-replace ownership. Convergence still uses namedAPPEND/REPLACE/REMOVEoperations, one policy per mutation. - On-call rotation config reconciliation — done (2026-07-31): the schedule read carries full rotation config and drift converges through the update mutation, timezone-aware.
- Per-namespace work-item-type availability — done (2026-07-31):
enabledreconciles 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 lists — in 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:
- A policy entry in
module_utils/gitlab_resource_policy.py(paths, identity, write-only fields, compare exclusions) — it inheritsbackendsfrom the capability rule (rest, plus rails when the executor handles it natively). - A planner mapping in
module_utils/gitlab_settings_payload.py, secrets separated. - 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.