Add approval source to Duo Workflow tool call approval audit events

What does this MR do and why?

Rails leg of the cross-repo approval-source audit feature (work item #603370). Today every approval path — explicit user click, PreToolUse hook, auto-mode, pre-approved config — produces an identical duo_tool_call_approved audit event, so compliance cannot tell WHO/WHAT approved a Duo Workflow tool call. This MR records the source:

  • Types::Ai::DuoWorkflows::ToolCallApprovalSourceEnum (USER_EXPLICIT, PRETOOLUSE_HOOK, AUTO_MODE, PREAPPROVED_CONFIG) — 1:1 with the DWS protobuf Approval.ApprovalSource; proto APPROVAL_SOURCE_UNSPECIFIED maps to omitting the argument.
  • Optional approvalSource argument on updateDuoWorkflowToolCallApprovals, threaded through UpdateToolCallApprovalsService into the audit event's additional_details (.compact — nil source omitted; fully additive/backward-compatible).
  • New toolCallApprovalMatch GraphQL field (matched / matchType: EXACT_HASH | PATTERN / matchedPattern), backed by new ToolCallApprovals#approval_match; approved? refactored to reuse it. No consumers yet by design — DWS consumption is a follow-up.
  • Regenerated GraphQL reference docs + introspection schemas. Changelog: added, EE: true.

Design note — mechanism vs provenance: enum values name the proximate decision mechanism only. Policy provenance (which config/policy pre-approved the tool) ships later as a separate policy_ref details key (phase 2). The GLOB_PATTERN_MATCH/PREAPPROVED_TOOL values in the work item text are superseded by this source-vs-match split (toolCallApprovalMatch carries the match mechanism).

JSONB storage of the source on tool_call_approvals was descoped — the audit event is the system of record.

Scope note (2026-08-05 rebase): the previously bundled governance-surface/environment REST work (work item #606343) has been removed from this MR. Master superseded it with the Ai::ToolRules::GovernanceSurface design (a7e9e830, HITL tool governance for CI/CD runner flows), so keeping the bundled hunks would have regressed master. The remaining client-supplied-environment routing (IDE/CLI → local_access) will ship as its own MR. This MR is now audit-feature-only: no db/, REST, or OpenAPI changes.

Known follow-ups

  • Workhorse chat path needs a gopb pseudo-version bump to the post-merge ai-assist SHA (the pinned module predates approval_source; protojson DiscardUnknown silently drops it) — follows ai-assist!6353. Local E2E used a temporary replace, not shipped here.
  • Descoped follow-ups: structured matched_pattern key in additional_details; re-emitting the source on silent reuse of a stored approval; vendored gitlab-duo-workflow-service-client gem bump (not on this path).

References

Screenshots or screen recordings

Not applicable — backend-only (GraphQL API + audit events), no UI.

How to set up and validate locally

  1. Enable session tool approval: project.project_setting.update!(tool_approval_for_session_enabled: true) (Rails console).

  2. Run the mutation in GraphiQL (/-/graphql-explorer) against a Duo Workflow you own:

    mutation {
      updateDuoWorkflowToolCallApprovals(input: {
        workflowId: "gid://gitlab/Ai::DuoWorkflows::Workflow/<ID>",
        toolName: "run_command",
        toolCallArgs: "{\"command\":\"ls\"}",
        approvalSource: USER_EXPLICIT
      }) { errors }
    }
  3. Verify: AuditEvent.last.details[:additional_details] # => { approval_source: "user_explicit" }.

  4. Query the match field and confirm EXACT_HASH for stored args, PATTERN + winning pattern for a stored glob, matched: false otherwise:

    { duoWorkflowWorkflows(workflowId: "gid://gitlab/Ai::DuoWorkflows::Workflow/<ID>") {
        nodes { toolCallApprovalMatch(toolName: "run_command", toolCallArgs: "{\"command\":\"ls\"}") {
          matched matchType matchedPattern } } } }
  5. Full chat-path E2E (CLI → LSP → Workhorse → DWS) additionally needs Workhorse built against post-merge gopb bindings (see Known follow-ups). Verified live end-to-end 2026-07-28: USER_EXPLICIT arrived at DWS on the wire and Rails recorded exactly one user_explicit audit event.

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist.

  • Tests: mutation request spec, service spec (audit additional_details), model spec, GraphQL request spec (toolCallApprovalMatch incl. winning-pattern case) — targeted local run post-rebase: 421 examples, 0 failures.
  • Documentation: GraphQL reference regenerated; duo_tool_call_approved audit event type already documented.
  • Changelog trailer present (Changelog: added, EE: true).
  • Backward compatibility: new argument and field optional/additive; nil source omitted from audit details.
  • Scope: governance-surface/environment work unbundled during the 2026-08-05 rebase (superseded by master's GovernanceSurface); this MR is audit-only.
  • No feature flag: additive optional audit metadata, no behavior change for existing callers (judged unnecessary; reviewer input welcome).
Edited by Dylan Bernardi

Merge request reports

Loading
Loading