Add per-project scheduling for scan execution policy

What does this MR do and why?

Add per-project scheduling for scan execution policy

Add Security::ScanExecutionPolicies::ScheduleWorker, a cron worker that runs every minute and enqueues CreatePipelineWorker for each project schedule whose next_run_at has passed.

This mirrors the PEP pattern from PipelineExecutionPolicies::ScheduleWorker.

The worker is gated behind the scan_execution_policy_per_project_scheduling feature flag, which should be enabled after all project schedule rows have been self-healed by the creation flag from the previous commit.

When the scheduling flag is enabled, RuleScheduleService stops enqueuing CreatePipelineWorker directly (the ScheduleWorker handles it), eliminating the need for perform_in with random delays.

References

  1. Persist per-project schedules for scan executio... (!227096 - merged)
  2. 👉 Add per-project scheduling for scan execution p... (!227101)
  3. Draft: Sync schedules when policy changes (!227102)

Database

Security::ScanExecutionProjectSchedule.runnable_schedules.ordered_by_next_run_at.including_rule_schedule_and_project

There are no records yet on production, this table is new.

SELECT "security_scan_execution_project_schedules".*
FROM "security_scan_execution_project_schedules"
WHERE "security_scan_execution_project_schedules"."next_run_at" < '2026-03-18 12:09:14.529049'
ORDER BY "security_scan_execution_project_schedules"."next_run_at" ASC,
         "security_scan_execution_project_schedules"."id" ASC

Plan (from local): https://explain.depesz.com/s/TCqD

How to set up and validate locally

  1. First, enable the feature flag for SEP project schedule creation
    Feature.enable(:scan_execution_policy_project_schedule_creation)
  2. Create a project with a scan execution policy that runs on schedule
    scan_execution_policy:
      - name: Secrets on schedule
        description: ''
        enabled: true
        actions:
          - scan: secret_detection
            template: latest
            variables:
              SECURE_ENABLE_LOCAL_CONFIGURATION: 'false'
        rules:
          - type: schedule
            cadence: 0 0 1 * *
            branch_type: default
            timezone: Europe/Zurich
        skip_ci:
          allowed: true
    
  3. Trigger the schedule pipeline manually via rails console
    schedule = Security::OrchestrationPolicyRuleSchedule.last
    schedule.update_columns(next_run_at: 1.day.ago) && Security::OrchestrationPolicyRuleScheduleWorker.new.perform    
  4. Verify that project schedule record was created with schedule.project_schedules
  5. Now, enable the feature flag for the scheduling based on SEP project schedules
    Feature.enable(:scan_execution_policy_per_project_scheduling)
  6. Update the project schedule created before to have next_run_at in the past
    ps = Security::ScanExecutionProjectSchedule.last
    ps.update_columns(next_run_at: 1.day.ago)
  7. Wait a minute and verify that a pipeline is created (same as the one before which was scheduled by OrchestrationPolicyRuleScheduleWorker)
  8. Now, with both feature flags enabled, verify that OrchestrationPolicyRuleScheduleWorker doesn't trigger the pipeline for this project
    schedule = Security::OrchestrationPolicyRuleSchedule.last
    schedule.update_columns(next_run_at: 1.day.ago) && Security::OrchestrationPolicyRuleScheduleWorker.new.perform    

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.

Related to #592731

Edited by Martin Cavoj

Merge request reports

Loading