Approval-policy rules can silently fail to resync after an interrupted policy sync

Summary

Three related gaps in approval-policy resync (link_policy/unlink_policy/resync event handling) can leave a project without its MR approval-policy rules, with nothing in the application logs to explain why.

  1. Orphaned scan_result_policy_read blocks all future resyncs. A row can be left behind by an interrupted resync (partial failure, retry, timeout) holding the same (config, project, policy_idx, rule_idx, action_idx) uniqueness slot as the row a later resync tries to create, but with a stale approval_policy_rule_id. The "already exists?" guard in Security::ScanResultPolicies::ApprovalRules::CreateService keys off approval_policy_rule_id, so it never finds the stale row, and create! raises ActiveRecord::RecordInvalid on the DB uniqueness validation instead.
  2. Non-atomic unlink. Security::Policy#unlink_project! deleted the ApprovalPolicyRuleProjectLink join row in its own transaction; the matching ApprovalProjectRule row was deleted separately, later. Between the two, the rule row existed with no join row, and EE::MergeRequest#linked_report_approver_project_rules treats that as "unlinked" and silently skips syncing it onto merge requests.
  3. Silent discard. Security::SyncProjectPolicyWorker's sidekiq_retry_in discards a job that raised RecordInvalid, permanently for a resync event, with no retry, and never logged it. Combined with (1), a project can stop resyncing its approval rules indefinitely with zero trace in the application logs.

Impact

A merge request approval policy can go missing for an in-scope project with no error anywhere a support engineer or admin would normally look. Recovery requires an unrelated event to trigger a fresh resync, and even that isn't guaranteed to succeed if the retry hits the same orphaned row.

Fix

!252506 (closed) fixes all three: reclaim a stale scan_result_policy_read by its real uniqueness key instead of raising, delete the project's approval_project_rules in the same transaction as the link, and log before discarding a RecordInvalid job.

  • #557737 — adjacent, acknowledged report of the same failure class (approval rules silently not created/updated on policy sync)
Edited by 🤖 GitLab Bot 🤖