diff --git a/.github/workflows/pr-review-merge-scheduler.yml b/.github/workflows/pr-review-merge-scheduler.yml index e4d6b8737a..849ae8a9f1 100644 --- a/.github/workflows/pr-review-merge-scheduler.yml +++ b/.github/workflows/pr-review-merge-scheduler.yml @@ -73,7 +73,23 @@ on: default: "" type: string schedule: - - cron: "*/30 * * * *" + # scan-pr-queue's own repository-local heartbeat. org-queue-sweep below + # explicitly excludes ContextualWisdomLab/.github from its target list + # (a PR in THIS repository, including one editing the governance + # workflows themselves, is never covered by the org-wide sweep), so this + # is the only periodic fallback for this repository's own PR queue. It + # also plugs a real event-coverage gap shared by every repository: + # required checks such as Security Scan and SAST Semgrep have no + # workflow_run listener anywhere in this file, so a PR where either is + # the last required check to go green has no event-driven re-wake at + # all. Offset by 30 minutes from org-queue-sweep's "0 * * * *" tick so + # the two heartbeats do not collide. Lengthened from */30 to hourly for + # the same Actions-capacity reason, and by the same lever, as the + # org-queue-sweep hourly cadence below (see + # docs/doctoring/actions-queue-saturation-hourly-sweep.md, #1630) — do + # NOT remove it outright; that would leave this repository's own queue + # with zero fallback heartbeat. + - cron: "30 * * * *" # Hourly org-wide sweep cadence for the org-queue-sweep job below. Target # repositories only receive scheduler runs on PR events, review/security # workflow completion, and protected-branch pushes; a PR whose approval or diff --git a/tests/test_actions_queue_saturation_scheduler_cadence.py b/tests/test_actions_queue_saturation_scheduler_cadence.py index fbf8f45547..fa5ce36370 100644 --- a/tests/test_actions_queue_saturation_scheduler_cadence.py +++ b/tests/test_actions_queue_saturation_scheduler_cadence.py @@ -31,3 +31,23 @@ def test_repository_scheduler_keeps_event_driven_wakes() -> None: assert "pull_request_review:" in workflow assert "workflow_run:" in workflow assert "repository_dispatch:" in workflow + + +def test_scan_pr_queue_heartbeat_is_hourly_and_offset_not_removed() -> None: + """scan-pr-queue's own repository-local heartbeat must not be dropped. + + org-queue-sweep excludes ContextualWisdomLab/.github from its target + list by name, so scan-pr-queue's own cron is the sole periodic fallback + for this repository's PR queue (and for any required check, such as + Security Scan or SAST Semgrep, with no workflow_run listener anywhere in + this file). It must be lengthened to hourly for the same capacity reason + as org-queue-sweep, not deleted, and offset from org-queue-sweep's + "0 * * * *" tick so the two heartbeats do not collide. + """ + workflow = WORKFLOW.read_text(encoding="utf-8") + assert '- cron: "30 * * * *"' in workflow + assert '*/30 * * * *' not in workflow + schedule_block = workflow.split(" schedule:", 1)[1].split( + " repository_dispatch:", 1 + )[0] + assert schedule_block.count('- cron:') == 2 diff --git a/tests/test_required_workflow_queue_contract.py b/tests/test_required_workflow_queue_contract.py index 056e47a678..a1cb4d77fe 100644 --- a/tests/test_required_workflow_queue_contract.py +++ b/tests/test_required_workflow_queue_contract.py @@ -988,8 +988,8 @@ def test_org_queue_sweep_covers_target_repositories_on_a_heartbeat() -> None: visible reason when it cannot mutate sibling repositories. The sweep runs hourly so an approval that lands after a PR's last event is auto-updated/merged promptly instead of idling indefinitely. Its cron has a - distinct concurrency key from the separate 30-minute scan, and the job has - enough runtime headroom to finish a complete organization walk. + distinct concurrency key from the separate scan-pr-queue heartbeat, and the + job has enough runtime headroom to finish a complete organization walk. """ workflow = workflow_text("pr-review-merge-scheduler.yml")