Add GraphQL endpoint for workflow-workflow links

What does this MR do and why?

Adds GraphQL support for Ai::DuoWorkflows::WorkflowWorkflow links (session to session). This follows the same pattern as the existing work item, merge request, note, and pipeline links.

  • New DuoWorkflow.workflowLinks connection, with an optional linkType filter.

  • New DuoWorkflowWorkflowLink type with linkType, linkedWorkflow, workflow, and createdAt fields.

  • New DuoWorkflowWorkflowLinkType enum (currently only SOURCE, meaning the session this one was restarted from).

  • New Ai::DuoWorkflows::WorkflowWorkflowPolicy, which uses the session's policy (same as the other link policies).

  • linkedWorkflow uses DuoWorkflowType authorization, so it returns null when the user can't read the linked session.

  • New DuoWorkflow.inverseWorkflowLinks connection for the reverse direction (sessions that link to this one, for example the sessions restarted from it), backed by a new inverse_workflow_links association on Workflow.

  • DuoWorkflow now declares authorize_granular_token for read_duo_workflow at the user boundary and is removed from authorization_todo.txt. Granular tokens were denied at the parent type, so workflowLinks was unreachable and the required granular-token spec passed with an empty node list.

Both directions use DuoWorkflowWorkflowLink. On an inverse link, workflow is the other session and linkedWorkflow is the queried session.

References

How to set up and validate locally

  1. Run a developer flow locally that takes a while (giving you enough time to cancel it). e.g. @duo-developer-gitlab-duo - Wait 2 min and then check the time and report back.

  2. On the right sidebar, open the session and cancel it.

  3. Confirm the session is in cancelled state and note the workflow ID:

w = Ai::DuoWorkflows::Workflow.last
w.status_name

=> :stopped

w.id
  1. From the command line, restart the session:
curl --request POST \
--header "PRIVATE-TOKEN: <GDK_PAT_TOKEN>" \
--url "http://gdk.test:3000/api/v4/ai/duo_workflows/workflows/<WORKFLOW_ID>/restart"
  1. When the session completes, note the new workflow ID as well.

  2. Run this query in GraphQL with the second workflow ID, and confirm it has a link to the original workflow ID:

   query {
     duoWorkflowWorkflows(workflowId: "gid://gitlab/Ai::DuoWorkflows::Workflow/NEW_ID") {
       nodes {
         id
         workflowLinks {
           nodes {
             linkType
             createdAt
             linkedWorkflow { id goal humanStatus }
           }
         }
       }
     }
   }

Example:

{
  "data": {
    "duoWorkflowWorkflows": {
      "nodes": [
        {
          "id": "gid://gitlab/Ai::DuoWorkflows::Workflow/4904",
          "workflowLinks": {
            "nodes": [
              {
                "linkType": "SOURCE",
                "createdAt": "2026-10-05T19:59:56Z",
                "linkedWorkflow": {
                  "id": "gid://gitlab/Ai::DuoWorkflows::Workflow/4903",
...
  1. Run this query with the original workflow ID, and confirm it has a link to the second (restarted) workflow ID:
query {
  duoWorkflowWorkflows(workflowId: "gid://gitlab/Ai::DuoWorkflows::Workflow/ORIGINAL_ID") {
    nodes {
      id
      inverseWorkflowLinks {
        nodes {
          linkType
          createdAt
          workflow { id }
          linkedWorkflow { id goal humanStatus }
        }
      }
    }
  }
}

Example:

{
  "data": {
    "duoWorkflowWorkflows": {
      "nodes": [
        {
          "id": "gid://gitlab/Ai::DuoWorkflows::Workflow/4903",
          "inverseWorkflowLinks": {
            "nodes": [
              {
                "linkType": "SOURCE",
                "createdAt": "2026-10-05T19:59:56Z",
                "workflow": {
                  "id": "gid://gitlab/Ai::DuoWorkflows::Workflow/4904"
...

Authorization note

DuoWorkflow gains authorize_granular_token permissions: :read_duo_workflow, boundary: :user, boundary_type: :user, matching WorkflowBranchesResolver and WorkflowLinkBaseType. The assignable permission group only allows the user boundary. DuoWorkflow was already gated by authorize :read_duo_workflow, so this grants nothing beyond what an api-scoped legacy PAT already had.

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.

Edited by Roman Eisner

Merge request reports

Loading
Loading