Add monthly cadence option to scan execution policy rule mode

Why are we doing this work

Scan execution policies support scheduled scans via a cadence cron expression. The backend already accepts monthly cadences (for example 0 0 1 * * or 0 0 1,15 * *) — the CadenceChecker regex, Gitlab::Ci::CronParser, and the rule-mode cron gate (cron-validator) all pass them, and the docs document monthly scheduling.

However, the rule-mode policy editor only offers daily and weekly cadences. A user who wants a monthly schedule must drop to YAML mode. This was a deliberate scoping decision when the schedule rule builder was first added (see merge request !95192 and issue 359886: "limit users to only specific schedules, allowing a user to use YAML mode to do anything more complicated"). The equivalent pipeline execution schedule policy editor was later built with first-class monthly support, so scan execution policies are now inconsistent.

There is also a latent correctness gap: a monthly cron opened in rule mode is currently mis-classified as daily, and editing silently rewrites and narrows the schedule.

Proposal

Add Monthly as a selectable cadence in the scan execution policy rule-mode schedule builder, mirroring the pipeline execution schedule policy UI:

  • Add monthly to the cadence/period dropdown.
  • When monthly is selected, show a multi-select Days of month dropdown (1-31), generating cadences like 0 9 1,15 * *.
  • Reuse the day-of-month selection pattern from the pipeline execution schedule policy editor.

Because the builder represents schedules as a fixed cron shape, also close the round-trip gap: any cadence the builder cannot represent exactly (ranges, steps, L, minute offsets, arbitrary cron) should fall back to YAML mode rather than being silently coerced.

Implementation plan

  • Frontend only — no backend, GraphQL, database, or feature-flag changes (the backend already supports monthly cadences).
  • Add a monthly period key and label to the scan execution constants.
  • Extend the cron helpers to build and parse a day-of-month field, and add a 3-way (daily/weekly/monthly) period detector. Fix the existing mis-classification where a monthly cron reads as daily.
  • Update the schedule rule component to render the monthly period option and a multi-select days-of-month dropdown, with time preserved across period switches.
  • Extract the shared getMonthlyDayOptions helper so both scan execution and pipeline execution schedule policies use one source.
  • Guard rule-mode entry so non-representable cadences route to YAML mode (disable the rule section) instead of silently rewriting.
  • Update the scan execution policy documentation cadence section.

Acceptance criteria

  • A user can select Monthly in the scan execution policy rule-mode schedule builder.
  • Selecting monthly reveals a multi-select days-of-month control (1-31); the generated cadence uses the day-of-month field (for example 0 9 1,15 * *).
  • An existing monthly policy opened in rule mode displays its correct cadence (no longer mis-shown as daily) and edits without data loss.
  • Selecting the time or days does not reset the previously selected hour when switching cadence period.
  • A cadence that rule mode cannot represent exactly (ranges, steps, L, minute offsets) disables the rule section and can still be edited in YAML mode.
  • Jest coverage for the cron helpers (build/parse round-trip, period detection, representability) and the schedule rule component (monthly selection, day sorting, parse-on-load).
  • Documentation updated to reflect monthly as a rule-mode option.

Verification steps

  1. Upload a GitLab Ultimate license.
  2. Go to a project, then Secure > Policies > New policy > Scan execution policy.
  3. Add a schedule rule and select the Monthly cadence.
  4. Select one or more days of the month and a time; confirm the YAML shows the expected cadence (for example 0 9 1,15 * *).
  5. Switch to YAML, enter a cadence with a range (for example 0 0 1-5 * *), and confirm rule mode is disabled and directs you to YAML.

References