Update CD SendToWorkflowChannel idempotency key
What does this MR do?
This MR fixes the idempotency key in Cd::Rollouts::ResolveGateService so that repeated approval gates on the same rollout step no longer collide and get dropped.
When an operator approves or rejects a paused step in a rollout, the service records the decision in the transition journal and pushes it to KAS so the workflow can resume, using an idempotency key to guard against duplicate or retried requests. The key is built from the rollout ID, the step position, the ID of the specific approval request being resolved, and the decision itself. Including the request ID matters because a single step can ask for approval more than once, for example once for policy enforcement and again if the deploy outcome is inconclusive, and each of those requests needs its own key even though the rollout, position, and decision are the same. Without it, a second approval on the same step looked identical to the first and got treated as a duplicate by KAS/relay. This is a service-only change, with no migrations, API, or UI impact.
How to test
1. Seed data (Rails console)
org = Organizations::Organization.first
application = Cd::Application.create!(organization: org, name: "demo-app")
version_set = Cd::VersionSet.create!(application: application, organization: org, name: "v1")
user = User.find_by(username: "root")
rollout = Cd::Rollout.create!(
application: application, organization: org, version_set: version_set,
state: :in_progress, workflow_ref: "wf-demo"
)
step = Cd::RolloutStep.create!(
rollout: rollout, path: "0.1", step_type: Cd::RolloutStep::APPROVAL_STEP_TYPE
)
request_approval_1 = Cd::RolloutTransition.create!(
rollout: rollout, organization: org, event: "request_approval",
from_state: "in_progress", to_state: "in_progress", rollout_step: step, principal: "system"
)
puts "rollout_id=#{rollout.id} user_id=#{user.id} step_id=#{step.id} request_approval_1_id=#{request_approval_1.id}"2. Trigger two sequential approvals (Rails console)
rollout = Cd::Rollout.find(rollout_id)
user = User.find(user_id)
step = Cd::RolloutStep.find(step_id)
# resolves the first open gate
Cd::Rollouts::ResolveGateService.new(rollout, current_user: user, status: :approved).execute
key_1 = "rollout-gate:#{rollout.id}:0.1:#{request_approval_1_id}:approve"
# a second approval request opens on the same step (e.g. inconclusive deploy outcome)
request_approval_2 = Cd::RolloutTransition.create!(
rollout: rollout, organization: rollout.organization, event: "request_approval",
from_state: "in_progress", to_state: "in_progress", rollout_step: step, principal: "system"
)
Cd::Rollouts::ResolveGateService.new(rollout, current_user: user, status: :approved).execute
key_2 = "rollout-gate:#{rollout.id}:0.1:#{request_approval_2.id}:approve"
puts "key_1=#{key_1}"
puts "key_2=#{key_2}"Expected: the two approvals produce distinct idempotency keys despite identical rollout, step position, and decision, e.g. rollout-gate:123:0.1:11:approve and rollout-gate:123:0.1:13:approve - so KAS/relay processes both instead of dropping the second as a duplicate.
References
https://gitlab.com/gitlab-org/gitlab/-/work_items/624822
Screenshots or screen recordings
No UI change - backend-only.
How to set up and validate locally
- Check out this branch on a local GDK with the CD EE feature enabled and follow the steps above in a Rails console.
- Run
bundle exec rspec ee/spec/services/cd/rollouts/resolve_gate_service_spec.rband confirm all examples pass. - Run
bundle exec rubocop ee/app/services/cd/rollouts/resolve_gate_service.rb ee/spec/services/cd/rollouts/resolve_gate_service_spec.rband confirm there are no offenses.
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.