Spread scheduled scan policies without time_window on SaaS
What does this MR do and why?
Scheduled scan execution policy rules authored directly in YAML without a time_window start with no delay, while the policy editor already sets a time window on new schedules by default. This applies a default delay (1 hour, randomly distributed, capped to the next cron run) to rules that omit one, behind a SaaS-only feature and a gitlab_com_derisk feature flag.
Security::OrchestrationPolicyRuleSchedule#time_window_seconds now falls back to 3600 seconds (1 hour, randomly distributed) when the rule has no time_window. The existing effective_time_window still caps that value to the time left until the next cron run, so short-cadence schedules are never delayed past their own next run. #time_window is unchanged and still returns only the user-configured value, because it feeds an analytics property that means "the user set a time window." Security::ScanExecutionProjectSchedule now delegates time_window_seconds to the rule schedule instead of aliasing it to time_window, so it picks up the same default.
Every scheduling path gets its delay from time_window_seconds via effective_time_window: the legacy enqueue in RuleScheduleService, per-project schedule creation in CreateProjectSchedulesService, the self-healing path, and rescheduling of ScanExecutionProjectSchedule. Changing the one method covers all of them.
The new default is gated by a SaaS-only feature, scheduled_scan_default_time_window (self-managed and Dedicated never get it), and a gitlab_com_derisk feature flag, scan_execution_policy_default_time_window, off by default. The flag actor is the policy configuration source's root namespace, so it can be rolled out one top-level group at a time. No changelog entry, since the flag is off by default.
Reviewable LOC: 39 (plus 40 in tests)
Context for LLM agents
Rejected alternatives:
- Jitter only inside the existing 15-minute cron run: too short compared with the windows used by rules that set one (the policy editor defaults to 10 hours).
- Changing
#time_windowitself instead of overriding#time_window_seconds(previously an alias of it): would change the meaning of the existing analytics property that reads it as "user configured a time window." - A
Gitlab.com?check instead of a SaaS feature: GitLab's EE principles require a SaaS feature for new SaaS-only behavior, not a hardcoded environment check. - Gating on the feature flag alone, with no SaaS feature: self-managed instances would inherit the default once the flag is eventually removed.
Invariants to preserve in follow-up work:
time_windowmust keep returning only the user-configured value.- The default value must stay capped by
effective_time_window(never delay a pipeline past its own next scheduled run). - The policy editor already sets a time window on new schedules by default, so this default only affects rules authored directly in YAML that omit
time_window.
References
Resolves https://gitlab.com/gitlab-org/gitlab/-/work_items/631141
Screenshots or screen recordings
Not applicable. Backend-only change, no UI.
How to set up and validate locally
- Open a Rails console with
GITLAB_SIMULATE_SAAS=1. - Enable the flag:
Feature.enable(:scan_execution_policy_default_time_window). - Take a
Security::OrchestrationPolicyRuleSchedulewhose rule has notime_windowand calleffective_time_window. It returns3600, or the time until the next cron run if that's shorter. - With the flag disabled, or without
GITLAB_SIMULATE_SAAS=1, the same call returnsnil.
Covered by specs in ee/spec/models/security/orchestration_policy_rule_schedule_spec.rb: no time window off SaaS (nil), no time window on SaaS with the flag on (3600), no time window with the flag off (nil), and an explicit time_window taking precedence over the default on SaaS.
Owed elsewhere
- Feature flag rollout issue: TBD.
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.