Add ai_workflows scope to work item write REST endpoints

What does this MR do and why?

Opts the work-item write REST endpoints into the ai_workflows OAuth scope so that agent sessions running with the composite ai_workflows token can complete common work-item write flows.

Today an agent can read a work item (WorkItems::Show already allows ai_workflows) but gets 403 insufficient_scope when it tries to create, update, delete, or comment on one. This was hit in practice: a session asked to "make an epic" couldn't, and the human had to promote the issue manually (see References).

The same write capability is already exposed to the broader api scope, and the equivalent GraphQL mutations (WorkItems::Create / WorkItems::Update) already allow ai_workflows. This change closes the REST gap without granting any capability the agent doesn't already have through another surface.

Changes

Add include ::API::Concerns::AiWorkflowsAccess + allow_ai_workflows_access (matching the existing pattern in WorkItems::Show, API::Issues, API::Epics) to:

  • lib/api/work_items/create.rb (POST)
  • lib/api/work_items/update.rb (PATCH)
  • lib/api/work_items/notes.rb (POST create note)

lib/api/work_items/delete.rb is deliberately left out: DELETE stays 403 insufficient_scope for ai_workflows tokens. GraphQL's workItemDelete mutation only allows the api scope, and delete_spec.rb pins that REST stays consistent with it.

lib/api/concerns/ai_workflows_access.rb now also allows PATCH (previously GET/HEAD/POST/PUT only). That change applies to every class that includes the concern; none of the others (WorkItems::Show, API::Issues, API::Epics, API::Commits, API::Glql, the Orbit/internal endpoints) has a PATCH route today, so it's additive only for work items.

creation.rb and update.rb now build a Gitlab::Auth::ScopeValidator from the current request and pass it into WorkItems::CreateService / WorkItems::UpdateService as scope_validator:. A quick action outside ALLOWED_AI_QUICK_ACTIONS (e.g. /close) in a description returns 403 for ai_workflows tokens instead of executing silently, matching how create_note_for already behaves in notes.rb.

Notes:

  • lib/api/work_items/discussions.rb and lib/api/work_items/linked_items.rb are not reachable under the ai_workflows scope. allow_access_with_scope grants are per endpoint class, so including the concern on create.rb/update.rb/notes.rb doesn't extend to sibling classes. Bringing those two in is out of scope here; tracked in https://gitlab.com/gitlab-org/gitlab/-/work_items/630767.
  • Adds request specs exercising each write endpoint with an oauth_access_token scoped to [:ai_workflows], plus spec/lib/api/concerns/ai_workflows_access_spec.rb pinning the verb matrix directly on the concern (read and write verbs allowed, DELETE rejected).

References

Screenshots or screen recordings

N/A — backend REST authorization change, no UI.

How to set up and validate locally

  1. Enable the feature flag (the work-items REST API is behind work_item_rest_api):
    Feature.enable(:work_item_rest_api)
  2. Create an OAuth access token for a user with the ai_workflows scope.
  3. Create a work item via REST using that token:
    curl --request POST \
      --header "Authorization: Bearer <ai_workflows_token>" \
      --url "https://gitlab.example.com/api/v4/projects/<project_id>/-/work_items" \
      --header "Content-Type: application/json" \
      --data '{ "title": "Agent task", "work_item_type_name": "task" }'
  4. Confirm the request returns 201 Created (previously 403 insufficient_scope).
  5. Update the same work item with the same token:
    curl --request PATCH \
      --header "Authorization: Bearer <ai_workflows_token>" \
      --url "https://gitlab.example.com/api/v4/projects/<project_id>/-/work_items/<iid>" \
      --header "Content-Type: application/json" \
      --data '{ "title": "Agent task (updated)" }'
    Confirm the request returns 200 OK (previously 403 insufficient_scope, since the concern didn't allow PATCH before this MR).
  6. Retry the create with a quick action in the description instead of a plain title:
    curl --request POST \
      --header "Authorization: Bearer <ai_workflows_token>" \
      --url "https://gitlab.example.com/api/v4/projects/<project_id>/-/work_items" \
      --header "Content-Type: application/json" \
      --data '{ "title": "Agent task", "work_item_type_name": "task", "description": "/close" }'
    Confirm the request returns 403 with the quick-actions-not-allowed message, not a work item that got silently closed.
  7. See spec/lib/api/concerns/ai_workflows_access_spec.rb for the verb matrix pinned directly on the concern.

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 Andrew Fontaine

Merge request reports

Loading
Loading