diff --git a/ee/config/feature_flags/gitlab_com_derisk/resolve_vulnerability_claude_3_7_rollout.yml b/ee/config/feature_flags/gitlab_com_derisk/resolve_vulnerability_claude_3_7_rollout.yml
new file mode 100644
index 0000000000000000000000000000000000000000..50a1877a6c284b5d4612c1e4834a9d6bc1100b40
--- /dev/null
+++ b/ee/config/feature_flags/gitlab_com_derisk/resolve_vulnerability_claude_3_7_rollout.yml
@@ -0,0 +1,9 @@
+---
+name: resolve_vulnerability_claude_3_7_rollout
+feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/523496
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/183607
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/523503
+milestone: '17.11'
+group: group::security insights
+type: gitlab_com_derisk
+default_enabled: false
diff --git a/ee/lib/gitlab/llm/ai_gateway/completions/resolve_vulnerability.rb b/ee/lib/gitlab/llm/ai_gateway/completions/resolve_vulnerability.rb
index 41800a443e5b177d126158ff9bd6f244910ae4e3..a0f0d8f1f1a903eeb8c82cf5b0bb6817736c6786 100644
--- a/ee/lib/gitlab/llm/ai_gateway/completions/resolve_vulnerability.rb
+++ b/ee/lib/gitlab/llm/ai_gateway/completions/resolve_vulnerability.rb
@@ -14,6 +14,8 @@ class ResolveVulnerability < Base
EmptyResponseError = Class.new(StandardError)
+ DEV_PROMPT = "0.0.1-dev"
+
override :execute
def execute
return unless valid?
@@ -52,6 +54,11 @@ def inputs
private
+ override :prompt_version
+ def prompt_version
+ DEV_PROMPT if Feature.enabled?(:resolve_vulnerability_claude_3_7_rollout, user)
+ end
+
def request!
response = perform_ai_gateway_request!
diff --git a/ee/spec/lib/gitlab/llm/ai_gateway/completions/resolve_vulnerability_spec.rb b/ee/spec/lib/gitlab/llm/ai_gateway/completions/resolve_vulnerability_spec.rb
index 89f8c81600ac0e3071d87b288be749f6821f3724..60122b7e7784fda5d325679e4fdf71022af418c8 100644
--- a/ee/spec/lib/gitlab/llm/ai_gateway/completions/resolve_vulnerability_spec.rb
+++ b/ee/spec/lib/gitlab/llm/ai_gateway/completions/resolve_vulnerability_spec.rb
@@ -25,6 +25,8 @@
build(:ai_message, :resolve_vulnerability, user: user, resource: vulnerability, request_id: 'uuid')
end
+ let(:prompt_version) { nil }
+
let(:options) { {} }
let(:logger) { instance_double(Gitlab::Llm::Logger) }
@@ -61,21 +63,26 @@ def allow_llm_client_to_raise_error(error)
def allow_llm_client_to_return_message(response)
identifier_names = vulnerability.finding.identifier_names.join("\n* ")
+ request_body = {
+ 'prompt_version' => prompt_version,
+ 'inputs' => {
+ filename: finding_location_file,
+ identifiers: "\n * #{identifier_names}\n",
+ name: vulnerability.title,
+ source_code: source_code,
+ vulnerability_description: vulnerability.description,
+ vulnerable_code: source_code
+ }
+ }.compact
+
expect_next_instance_of(Gitlab::Llm::AiGateway::Client) do |client|
expect(client).to receive(:complete_prompt)
.with(
base_url: Gitlab::AiGateway.url,
prompt_name: :resolve_vulnerability,
- inputs: {
- filename: finding_location_file,
- identifiers: "\n * #{identifier_names}\n",
- name: vulnerability.title,
- source_code: source_code,
- vulnerability_description: vulnerability.description,
- vulnerable_code: source_code
- },
+ inputs: request_body['inputs'],
model_metadata: nil,
- prompt_version: nil
+ prompt_version: request_body['prompt_version']
)
.and_return(response)
end
diff --git a/ee/spec/lib/gitlab/llm/completions/resolve_vulnerability/shared_examples.rb b/ee/spec/lib/gitlab/llm/completions/resolve_vulnerability/shared_examples.rb
index 9704e33a4d2c71b1391156bbf77e27caeaa2a71e..cb747bbfd2e94703362d9adb9f69a4c2e3866c8e 100644
--- a/ee/spec/lib/gitlab/llm/completions/resolve_vulnerability/shared_examples.rb
+++ b/ee/spec/lib/gitlab/llm/completions/resolve_vulnerability/shared_examples.rb
@@ -65,6 +65,7 @@ def expect_tracked_internal_event(event_name, status)
RSpec.shared_examples "a resolve vulnerability completion" do
before do
stub_licensed_features(security_dashboard: true)
+ stub_feature_flags(resolve_vulnerability_claude_3_7_rollout: false)
[:admin_all_resources, :resolve_vulnerability_with_ai].each do |permission|
allow(user).to receive(:can?).with(permission).and_return(true)
@@ -300,6 +301,38 @@ def expect_tracked_internal_event(event_name, status)
end
end
+ context "when passing in prompt version for Claude 3.7" do
+ let(:prompt_version) { "0.0.1-dev" }
+
+ before do
+ stub_feature_flags(resolve_vulnerability_claude_3_7_rollout: true)
+ end
+
+ it 'requests that a MR be created with the extracted patch' do
+ resolve.execute
+
+ expect(merge_request_service).to have_received(:new).with(
+ project,
+ vulnerability,
+ user,
+ llm_patch: code_patch,
+ description_options: description_options
+ )
+ end
+
+ it 'publishes the created merge request for the fix' do
+ resolve.execute
+
+ expect_published_graphql_content(mr_url)
+ end
+
+ it 'tracks internal event with success' do
+ expect_tracked_internal_event("track_mr_creation_from_vr", "success")
+
+ resolve.execute
+ end
+ end
+
context 'when the AIGW responds with a typed code block' do
let(:content) { "```java\n#{code_patch}\n```" }