Let code_review/v1 accept a checkout review from Duo CLI

What does this MR do and why?

Duo CLI is adding a local code review that runs the code_review/v1 foundational flow against the developer's checkout, as a headless, client-executed session. The flow's goal validator only accepted a merge request IID or URL (added in this merge request so a bad goal fails before LLM steps are billed), so a checkout run got a 400 at create time.

This merge request:

  • Renames the shared goal_validator_resolver attribute to start_request_validator, adding two keywords: additional_context: (the API request's additional context; always empty on trigger runs, which build their context after validation) and client_executed: (true when the caller runs the flow itself, mirroring FlowExecutionAuthorizer#client_executed?). The model method is now validate_start_request. The endpoint passes params[:additional_context]; client_executed is true on the plain create path unless start_workflow is set, false on the ai_catalog_item_consumer_id path, and false from the trigger service. Shared code stays unaware of code review or surfaces. Only code_review/v1 and bl_security declare the validator; bl_security just gains the unused keywords.
  • All checkout logic lives in the code_review/v1 definition. It reads surface from the code_review_context entry of additional_context (Content is a JSON object with a surface key). For checkout, it requires client execution and a non-blank (stripped) goal. Otherwise the existing merge request check runs unchanged. Content that is not a JSON object is ignored, so the merge request rule still applies.
  • Rejections (all 400, before a workflow is created):
    • Unknown surface: code_review_context.surface must be one of merge_request, checkout.
    • checkout on a server-started run (start_workflow: true or ai_catalog_item_consumer_id): Checkout reviews run on the client and cannot be started on the server.
    • Blank checkout goal: The goal for a checkout review must describe what to review.

No new API parameter, documentation or OpenAPI change, storage change, feature flag, or migration.

Notes on related behavior (unchanged by design):

  • Noteable linking: noteable_resolver still only matches a bare IID or a same-project merge request URL. A free-text checkout goal resolves to no noteable. An explicit merge_request_id or issue_id parameter is still honored, as for every flow. Edge case accepted: a checkout goal that happens to be a bare IID or a same-project merge request URL links that merge request.
  • On server-started runs with a linked merge request, the start service's BuildAdditionalContextService replaces the caller's code_review_context with the one Rails builds, dropping the surface, so the run is a merge request review. This cannot happen for a checkout run, since those are rejected server-side.
  • Flow configuration selection is unchanged: the CLI sends its own flow config coordinates to the Duo Workflow Service.
  • Privileges: no change needed. The create endpoint clamps client-supplied privileges against governance and never applies the definition's list.
  • Reporting: a checkout run is identifiable by workflow_definition = 'code_review/v1' plus a client environment (the CLI sends ide), since merge request runs arrive as ambient or web.

Follow-up to agree with the Code Review team (flow side, ai-assist project): the flow should treat an absent surface as merge_request, since Rails does not inject it.

References

Screenshots or screen recordings

Not applicable, backend and API only.

How to set up and validate locally

  1. Against GDK, create a workflow:

    curl --request POST --header "PRIVATE-TOKEN: $TOKEN" \
      --url "http://gdk.test:3000/api/v4/ai/duo_workflows/workflows" \
      --data "project_id=<id>&workflow_definition=code_review/v1&environment=ide" \
      --data "agent_privileges[]=8&agent_privileges[]=2&pre_approved_agent_privileges[]=8&pre_approved_agent_privileges[]=2" \
      --data-urlencode "goal=the working tree" \
      --data-urlencode "additional_context[][Category]=code_review_context" \
      --data-urlencode 'additional_context[][Content]={"surface":"checkout"}'

    Expect 201, agent_privileges [8, 2], environment ide; the stored goal is the working tree and merge_request_id is null.

  2. Same request without the additional_context lines. Expect 400 with the existing message about a merge request IID or URL.

  3. Same as step 1 plus start_workflow=true. Expect 400: checkout reviews run on the client and cannot be started on the server. No workflow is created.

  4. Same as step 1 with goal= . Expect 400: the goal must describe what to review.

  5. Same as step 1 with {"surface":"branch"}. Expect 400: code_review_context.surface must be one of merge_request, checkout.

  6. Merge request path unchanged: same as step 1 without additional_context and with goal=<open merge request IID>. Expect 201 and the merge request linked.

MR acceptance checklist

Evaluated against the MR acceptance checklist. No migration, no feature flag.

Edited by Alejandro Metke Jimenez

Merge request reports

Loading
Loading