Add policy list to the policy store GraphQL type

What does this MR do and why?

Summary

This change adds the ability to query security policies stored in GitLab's policy store through the GraphQL API. Previously, policies could only be accessed via the REST API; now they can also be fetched using GraphQL queries.

A new GovernPolicy type is introduced, exposing policy details such as name, description, trigger type, rules, actions, enforcement mode, and lifecycle state. Users can retrieve all policies for an organization, with an optional filter to return only policies that respond to a specific trigger type.

Access is restricted to organization owners, and the feature is marked as experimental (introduced in GitLab 19.4). The change also includes proper permission checks and error handling — for example, returning null for groups (which don't hold policies) or when the user lacks the required access rights. Tests covering various access scenarios, filtering, and schema execution are included.

Closes https://gitlab.com/gitlab-org/gitlab/-/work_items/613019.

References

Verification

  • Local: type + resolver specs green (10 examples, 0 failures), including a schema-execution example resolving organization { policyStore { policies { id } } } through the real schema, a group-parent null case, and separate axes for the feature flag, instance setting, license, and organization filter.
  • Local: bundle exec rake gitlab:permissions:validate → GraphQL permissions are valid (covers the granular-token doc row for GovernPolicy and the required shared-example coverage of read_govern_policy); RuboCop clean; pre-push hooks graphql_introspection_check, permissions-verify, and graphql_docs passed.
  • The request spec (ee/spec/requests/api/graphql/organizations/policy_store_policies_spec.rb) is judged by CI, including the granular-token shared example seeded with a real policy.
  • Adversarial review verdict on the pre-reshape diff, verbatim: pass with findings — accepted findings are deliberate experiment-stage decisions: Int ids matching the REST entity (the gem value object is not ActiveRecord), a plain list rather than a connection, and a String triggerType (unknown values return an empty list where REST returns 400; specced explicitly as a divergence). The reshape onto PolicyStore also resolves the review's naming-split finding (governPolicies vs policyStore*).

Screenshots or screen recordings

No UI changes.

How to set up and validate locally

  1. In rails console, activate the experiment (Ultimate license required):

    Feature.enable(:security_policies_v2)
    Gitlab::CurrentSettings.update!(policy_store_experiment_enabled: true)
  2. Seed a policy for your organization (id 1 in GDK):

    Gitlab::PolicyStore.create(organization_id: 1, name: 'Test policy', trigger_type: 'deployment_requested')
  3. As an organization owner or admin, run in GraphiQL (/-/graphql-explorer):

    { organization(id: "gid://gitlab/Organizations::Organization/1") { policyStore { policies(triggerType: "deployment_requested") { id name triggerType mode lifecycleState } } } }
  4. Expect the seeded policy. As a non-owner organization user, expect policies to be null while the catalog fields on policyStore still resolve.

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist.

Edited by Artur Fedorov

Merge request reports

Loading
Loading