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 protobufApproval.ApprovalSource; protoAPPROVAL_SOURCE_UNSPECIFIEDmaps to omitting the argument.- Optional
approvalSourceargument onupdateDuoWorkflowToolCallApprovals, threaded throughUpdateToolCallApprovalsServiceinto the audit event'sadditional_details(.compact— nil source omitted; fully additive/backward-compatible). - New
toolCallApprovalMatchGraphQL field (matched/matchType:EXACT_HASH|PATTERN/matchedPattern), backed by newToolCallApprovals#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
gopbpseudo-version bump to the post-merge ai-assist SHA (the pinned module predatesapproval_source;protojson DiscardUnknownsilently drops it) — follows ai-assist!6353. Local E2E used a temporaryreplace, not shipped here. - Descoped follow-ups: structured
matched_patternkey inadditional_details; re-emitting the source on silent reuse of a stored approval; vendoredgitlab-duo-workflow-service-clientgem bump (not on this path).
References
- Work item: gitlab#603370; siblings: gitlab-lsp#2673, ai-assist#2407
- Merge order: ai-assist!6353 (proto + regenerated bindings) merges first, then this MR and gitlab-lsp!3803 (independent of this MR; its
supportedSinceInstanceVersionbump needs this MR's shipping milestone).
Screenshots or screen recordings
Not applicable — backend-only (GraphQL API + audit events), no UI.
How to set up and validate locally
-
Enable session tool approval:
project.project_setting.update!(tool_approval_for_session_enabled: true)(Rails console). -
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 } } -
Verify:
AuditEvent.last.details[:additional_details] # => { approval_source: "user_explicit" }. -
Query the match field and confirm
EXACT_HASHfor stored args,PATTERN+ winning pattern for a stored glob,matched: falseotherwise:{ duoWorkflowWorkflows(workflowId: "gid://gitlab/Ai::DuoWorkflows::Workflow/<ID>") { nodes { toolCallApprovalMatch(toolName: "run_command", toolCallArgs: "{\"command\":\"ls\"}") { matched matchType matchedPattern } } } } -
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_EXPLICITarrived at DWS on the wire and Rails recorded exactly oneuser_explicitaudit 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 (toolCallApprovalMatchincl. winning-pattern case) — targeted local run post-rebase: 421 examples, 0 failures. - Documentation: GraphQL reference regenerated;
duo_tool_call_approvedaudit 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/
environmentwork unbundled during the 2026-08-05 rebase (superseded by master'sGovernanceSurface); this MR is audit-only. - No feature flag: additive optional audit metadata, no behavior change for existing callers (judged unnecessary; reviewer input welcome).