Expose static definitionSteps on CdApplicationFlowDefinition

What does this MR do?

Adds a computed definitionSteps field to the CdApplicationFlowDefinition GraphQL type that exposes the static step tree of a flow definition, parsed from its YAML definition without requiring a rollout or persisting any Cd::RolloutStep rows.

This mirrors the existing Cd::Rollout.rolloutSteps field, which shows runtime step state during an active rollout, but definitionSteps shows the static structure of the flow definition itself for preview/planning purposes.

Key design points:

  • No database rows: Steps are built purely in memory from the flow definition's YAML each time the field is queried (via Cd::ApplicationFlowDefinitions::DefinitionSteps::Builder).
  • Batched environment resolution: The environment field on each step is batch-resolved across all nodes of all flow definitions in the response (one query total), avoiding N+1.
  • Nested tree: Stages carry their child steps under a steps field, matching the rollout-steps shape.
  • Authorization: Delegates to the flow definition's existing :read_cd_application_flow_definition policy.

GraphQL API

New type: CdDefinitionStep

Mirrors CdRolloutStep but without runtime fields (id, state, startedAt, finishedAt, error):

type CdDefinitionStep {
  path: String!
  parentPath: String
  stepType: String!
  name: String
  params: JSON
  environment: CdEnvironment
  steps: [CdDefinitionStep!]
}

New field on CdApplicationFlowDefinition:

type CdApplicationFlowDefinition {
  # ... existing fields ...
  definitionSteps: [CdDefinitionStep!]
}

Example query

query {
  organization(id: "gid://gitlab/Organizations::Organization/1") {
    cdApplications {
      nodes {
        applicationFlowDefinitions {
          nodes {
            id
            version
            definitionSteps {
              path
              stepType
              name
              params
              environment { id name }
              steps {
                path
                stepType
                environment { id name }
              }
            }
          }
        }
      }
    }
  }
}

Example response:

{
  "data": {
    "organization": {
      "cdApplications": {
        "nodes": [
          {
            "applicationFlowDefinitions": {
              "nodes": [
                {
                  "id": "gid://gitlab/Cd::ApplicationFlowDefinition/42",
                  "version": 3,
                  "definitionSteps": [
                    {
                      "path": "0",
                      "stepType": "com.gitlab.cd.steps.stage",
                      "name": "production",
                      "params": null,
                      "environment": null,
                      "steps": [
                        {
                          "path": "0.0",
                          "stepType": "com.gitlab.cd.argo.rolling.deploy",
                          "environment": {
                            "id": "gid://gitlab/Cd::Environment/7",
                            "name": "production"
                          }
                        }
                      ]
                    },
                    {
                      "path": "1",
                      "stepType": "com.gitlab.cd.steps.wait",
                      "params": { "seconds": 30 },
                      "environment": null,
                      "steps": []
                    }
                  ]
                }
              ]
            }
          }
        ]
      }
    }
  }
}

Screenshot_2026-08-18_at_11.43.30

Validation steps

  1. Enable the feature flag (if in development/test): Feature.enable(:ai_native_deploy)
  2. Create a flow definition with nested steps:
mutation {
  cdApplicationFlowDefinitionCreate(input: {
    applicationId: "gid://gitlab/Cd::Application/1"
    definition: """
      steps:
        - type: com.gitlab.cd.steps.stage
          name: production
          steps:
            - type: com.gitlab.cd.argo.rolling.deploy
              environment: production
        - type: com.gitlab.cd.steps.wait
          seconds: 30
    """
  }) {
    applicationFlowDefinition {
      id
      version
      definitionSteps {
        path
        stepType
        name
        params
        environment { name }
        steps {
          path
          stepType
          environment { name }
        }
      }
    }
  }
}
  1. Verify the static tree is returned (2 top-level nodes: 1 stage with 1 nested deploy, 1 wait).
  2. Verify environment resolution: The nested deploy step's environment should resolve to the Cd::Environment matching "production" (if it exists), or null (if it doesn't).
  3. Verify no persistence: Run Cd::RolloutStep.count before/after querying — it should stay zero (steps are purely in-memory).
  4. Verify batching (no N+1): Query multiple flow definitions' definitionSteps in one request and confirm only one Cd::Environment query runs (check query logs or ActiveRecord::QueryRecorder).
  5. Verify unparseable YAML returns null:
  • Create a flow definition with malformed YAML (e.g., "steps: [\n")
  • Query its definitionSteps → should return null
  1. Verify empty definition returns []:
  • Create a flow definition with "steps: []"
  • Query its definitionSteps → should return []

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.

Related to #616289

Merge request reports

Loading
Loading