[Phase 3] Persist and expose MR ↔️ work item relations over GraphQL

What does this MR do and why?

Phase 3 of persisting MR ↔️ Work Item relationships (see #601174, Option B). Closes #601481.

Exposes the persisted relations (the link_type column added in Phase 1, written by the services in Phase 2) over GraphQL, and deprecates the experimental derived linkedWorkItems field in favour of the new persisted one. This MR is the GraphQL API surface only.

Stack status. Phases 1 and 2 are merged to master, so this now targets master. The mentioned-from-description backend logic (cache_merge_request_issues! rename + mentioned-persistence, the model validation relaxation, and the MENTIONED-creation restriction) has moved to Phase 4 (!241573), which targets this branch. The model rename is Phase 5 (!239615). Both stack on top of this MR.

Feature flag

  • explicit_mr_work_item_relations (wip, default off, group::project management). The mutations, the relations resolver, and the work item creation widget are gated on this flag — the same one the frontend uses (!238648 (merged)), consolidated per gitlab-org/plan-stage#456.
  • While disabled: the resolver returns null, both relation mutations raise resource-not-available, and the workItemCreate development widget is a no-op.

Read surface

  • New MergeRequestWorkItemRelation type (connection), backed directly by merge_requests_closing_issues rows — no PORO, mirroring the existing WorkItemClosingMergeRequest type on the work item side. Authorized per row by the existing read_merge_request_closing_issue policy, so confidential/unreadable work items are filtered out of the connection.
    • Fields: id (global ID), linkType, fromMrDescription, workItem (batch-loaded).
  • New workItemRelations field on MergeRequestType, capped at max_page_size: 500, with an optional types: filter.
  • linkedWorkItems is deprecated (Use 'workItemRelations' instead, 19.2); its runtime is unchanged during the deprecation window.

Mutations introduced by this MR

Mutation Arguments Behaviour
mergeRequestCreateWorkItemRelations projectPath, iid, workItemIds: [WorkItemID!]!, linkType (default MENTIONED) Links existing work items to an MR. Delegates to MergeRequests::WorkItemRelations::CreateService; returns the created workItemRelations and per-item errors.
mergeRequestDestroyWorkItemRelations projectPath, iid, ids: [MergeRequestsClosingIssuesID!]! (relation global IDs) Removes persisted relations. Delegates to MergeRequests::WorkItemRelations::DestroyService; returns removedRelationIds.
workItemCreate (extended) new optional developmentWidget: { mergeRequestIds: [MergeRequestID!]!, linkType } Links a newly created work item to one or more MRs in the same call — see below.

Both dedicated mutations are Mutations::MergeRequests::WorkItemRelations::{Create,Destroy} and authorize the granular update_merge_request token scope.

Requested in review on the frontend MR (!240762#note_3462809191): when a user creates a work item from an MR context, establish the link in the same workItemCreate call instead of forcing a follow-up mergeRequestCreateWorkItemRelations.

  • New Types::WorkItems::Widgets::DevelopmentCreateInputType (WorkItemWidgetDevelopmentCreateInput): mergeRequestIds: [MergeRequestID!]! (capped at MergeRequests::WorkItemRelations::BaseService::MAX_RELATIONS, referenced directly so the limit is not duplicated) and linkType (defaults RELATED).
  • New WorkItems::Callbacks::Development, auto-dispatched by the widget framework (via Widget.callback_class, no registration file needed). It reuses MergeRequests::WorkItemRelations::CreateService once per MR, so idempotency, the relation limit, and the granular create_merge_request_work_item_relation authorization are unchanged — creating a work item cannot link it to an MR you can't manage.
  • Gated per MR on explicit_mr_work_item_relations (actor: merge_request.project), consistent with the rest of the backend.

Other

  • by_link_types scope on the relations model (used by the resolver's types: filter) — see Database review below.
  • Regenerated GraphQL reference docs and introspection schemas.

Database review

The new query is a single read keyed on merge_request_id and bounded by the number of issues referenced in one MR description. Verified on a Database Lab production clone (snapshot 2026-06-18), worst-case MR (~2,960 rows), EXPLAIN (ANALYZE, BUFFERS), warm cache. Counts only, no row data; literal id shown as <mr>.

Read — by_link_types (resolver types: filter):

SELECT * FROM merge_requests_closing_issues WHERE merge_request_id = ? AND link_type IN (?);
Index Scan using index_mr_closing_issues_on_merge_request_id_and_link_type
  (cost=0.43..3036.00 rows=3111) (actual rows=2962 loops=1)
  Index Cond: ((merge_request_id = <mr>) AND (link_type = ANY ('{0,1,2}'::integer[])))
  Buffers: shared hit=90
  Execution Time: 0.817 ms

An index seek on the existing index_mr_closing_issues_on_merge_request_id_and_link_type. No new index or migration. ~database review requested.

How to set up and validate locally

bundle exec rspec \
  spec/graphql/types/merge_requests/work_item_relation_type_spec.rb \
  spec/requests/api/graphql/merge_request/work_item_relations_spec.rb \
  spec/requests/api/graphql/merge_request/linked_work_items_spec.rb \
  spec/requests/api/graphql/mutations/merge_requests/work_item_relations/create_spec.rb \
  spec/requests/api/graphql/mutations/merge_requests/work_item_relations/destroy_spec.rb \
  spec/graphql/types/work_items/widgets/development_create_input_type_spec.rb \
  spec/services/work_items/callbacks/development_spec.rb \
  spec/requests/api/graphql/mutations/work_items/create_spec.rb \
  spec/models/merge_requests_closing_issues_spec.rb

Specs cover: FF-on read (with link-type filtering and per-row authorization), FF-off returning null, create/destroy authorization + FF gating, the linkedWorkItems deprecation annotation, the workItemCreate development widget (links on create, FF-off no-op, over-limit error, unauthorized-MR error surfaced), and the by_link_types scope.

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist.

Edited by Jorge Tomás

Merge request reports

Loading