Re-evaluate approval policies on vulnerability triage

Problem

Issue #560563 (closed) (the "target-branch triage" gap): when a vulnerability is triaged (dismissed / resolved) through the Vulnerability Report, the merge request approval policy is not re-evaluated. The merge request stays blocked until a new pipeline runs.

This is most visible when a CVE database update introduces findings on the default/target branch: those findings can only be triaged in the Vulnerability Report (not the MR security widget), so the policy never re-evaluates and the MR stays blocked despite everything being triaged.

Root cause

Only the MR-security-widget path (Security::Findings::DismissService) schedules SyncFindingsToApprovalRulesWorker. The vulnerability-level services (Vulnerabilities::DismissService, Vulnerabilities::BulkDismissService, and the Vulnerabilities::BaseStateTransitionService subclasses such as ResolveService) change vulnerability state but never trigger policy re-evaluation.

Change

  • Add a shared concern Security::ScanResultPolicies::SyncOnVulnerabilityStateChange that schedules SyncFindingsToApprovalRulesWorker for the project's latest_ingested_security_pipeline (nil-guarded).
  • Include it in Vulnerabilities::DismissService, Vulnerabilities::BaseStateTransitionService (covers ResolveService, Confirm, RevertToDetected), and Vulnerabilities::BulkDismissService (per project, inside the existing after_commit).
  • Performance: the worker deduplicates on pipeline_id (until_executing, including_scheduled: true), so bulk triage within a project collapses to a single re-evaluation.

Testing

  • Specs added for dismiss, resolve, and bulk-dismiss asserting the worker is scheduled with the project's pipeline id, plus a nil-pipeline guard case.
  • dismiss_service_spec + resolve_service_spec + confirm_service_spec + revert_to_detected_service_spec pass locally (no regression from the base-class change). The bulk-dismiss example runs under :elastic in CI.

Known limitation / follow-up

Resolution of target-branch MRs flows through SyncFindingsToApprovalRulesService#merge_requests_targeting_pipeline_ref, which is gated on pipeline.latest?. latest_ingested_security_pipeline is normally the latest default-branch pipeline, so this resolves in the common case; relaxing that guard (mirroring the source-branch handling in !240937 (closed)) is a separate follow-up.

References

Merge request reports

Loading