diff --git a/.github/workflows/agent-review-runtime-quality-ci.yml b/.github/workflows/agent-review-runtime-quality-ci.yml index fd54b694a1..3680da8778 100644 --- a/.github/workflows/agent-review-runtime-quality-ci.yml +++ b/.github/workflows/agent-review-runtime-quality-ci.yml @@ -32,6 +32,9 @@ on: - "tests/test_strix_quality_timeout_fixture_budget.py" - "tests/test_agent_review_runtime_quality_consolidation.py" - ".github/workflows/pr-review-merge-scheduler.yml" + - "scripts/ci/pr_review_merge_scheduler.py" + - "scripts/ci/pr_review_merge_scheduler_core.py" + - "tests/test_pr_review_merge_scheduler.py" - "scripts/ci/current_head_run_coalescer.py" - ".github/workflows/pr-review-fix-scheduler.yml" - "scripts/ci/pr_review_fix_scheduler.py" @@ -203,12 +206,18 @@ jobs: noema_suite=true opencode_suite=true ;; - .github/workflows/pr-review-merge-scheduler.yml|\ + .github/workflows/pr-review-merge-scheduler.yml) + queue_suite=true + review_repair_suite=true + ;; scripts/ci/current_head_run_coalescer.py) queue_suite=true ;; .github/workflows/pr-review-fix-scheduler.yml|\ scripts/ci/pr_review_fix_scheduler.py|\ + scripts/ci/pr_review_merge_scheduler.py|\ + scripts/ci/pr_review_merge_scheduler_core.py|\ + tests/test_pr_review_merge_scheduler.py|\ .github/workflows/pr-review-autofix.yml|\ .github/workflows/hourly-review-repair.yml|\ scripts/ci/pr_review_conflict_scope.py|\ diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a1552edf4..06b3dba425 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,16 @@ - Raised `hourly-review-repair.yml`'s discovery ceiling from 50 to 200 while rotating deterministic 50-PR deep-inspection windows by hourly run number. The scheduler hydrates only the selected window and stops immediately after its single dispatch, preserving access to newer PRs without quadrupling expensive review/check/comment work. See `docs/doctoring/hourly-review-repair-single-file-consolidation.md`'s 2026-09-03 follow-up. ## [Unreleased] +- Include merge-scheduler entrypoint, core, and regression-test changes in + the existing runtime-quality workflow's trigger and suite selector. Scheduler + workflow edits retain queue checks and also select the full review-repair + suite. Selector-only test edits use the existing unconditional contract step; + changelog-only edits still do not start this runner. No job is added. +- Complete the scheduler test isolation introduced by #1896 for the two + remaining fixtures that invoke `inspect_pr(..., dry_run=False)` or + `main(...)`. Both now stub the environment-gated startup-failure recovery + owner, so `GITHUB_ACTIONS=true` exercises the production guard without + issuing real GitHub calls or rejecting synthetic fixture SHAs. - **Fix current-main contract drift that blocked the unscoped `agent-review-runtime-quality-ci.yml` "Verify scheduler and contextual-orchestrator review-repair contracts" step (which discovers and diff --git a/tests/test_agent_review_runtime_quality_consolidation.py b/tests/test_agent_review_runtime_quality_consolidation.py index dfa6342c0d..b0c90eb707 100644 --- a/tests/test_agent_review_runtime_quality_consolidation.py +++ b/tests/test_agent_review_runtime_quality_consolidation.py @@ -3,8 +3,11 @@ from __future__ import annotations import re +import subprocess from pathlib import Path +import pytest + REPOSITORY_ROOT = Path(__file__).resolve().parents[1] WORKFLOW_PATH = ( @@ -70,6 +73,9 @@ def test_consolidated_workflow_materializes_one_runner_job() -> None: assert "workflow_dispatch:" not in workflow assert "gh api" not in workflow assert re.search(r"(?m)^[ \t]*sleep[ \t]+", workflow) is None + self_test_step = workflow.split("- name: Verify consolidated workflow contract", 1)[1] + assert "if:" not in self_test_step + assert "python -m pytest -q tests/test_agent_review_runtime_quality_consolidation.py" in self_test_step def test_changelog_only_edits_do_not_boot_the_consolidated_runner() -> None: @@ -143,6 +149,45 @@ def test_review_repair_suite_is_selected_and_conditionally_executed() -> None: assert workflow.count("runs-on:") == 1 +@pytest.mark.parametrize( + ("changed_path", "starts_runner", "review_repair", "queue"), + ( + ("scripts/ci/pr_review_merge_scheduler.py", True, True, False), + ("scripts/ci/pr_review_merge_scheduler_core.py", True, True, False), + ("tests/test_pr_review_merge_scheduler.py", True, True, False), + ("tests/test_agent_review_runtime_quality_consolidation.py", True, False, False), + (".github/workflows/pr-review-merge-scheduler.yml", True, True, True), + ("scripts/ci/current_head_run_coalescer.py", True, False, True), + ("CHANGELOG.md", False, False, False), + ), +) +def test_merge_scheduler_changes_start_and_select_contracts( + changed_path: str, starts_runner: bool, review_repair: bool, queue: bool +) -> None: + """Bind scheduler changes to both runner admission and the real selector.""" + workflow = _workflow_text() + trigger = workflow.split("on:\n", 1)[1].split("\nconcurrency:\n", 1)[0] + assert (f' - "{changed_path}"' in trigger) is starts_runner + + selector = workflow.split(' case "$changed_path" in\n', 1)[1].split( + " esac", 1 + )[0] + result = subprocess.run( + [ + "bash", "--noprofile", "--norc", "-e", "-o", "pipefail", "-c", + 'IFS= read -r changed_path\nreview_repair_suite=false\nqueue_suite=false\n' + 'case "$changed_path" in\n' + selector + + 'esac\nprintf "%s,%s" "$review_repair_suite" "$queue_suite"\n', + ], + input=changed_path + "\n", + text=True, + capture_output=True, + check=True, + ) + assert result.stdout == f"{str(review_repair).lower()},{str(queue).lower()}" + assert result.stderr == "" + + def test_commercial_readiness_suite_is_selected_and_conditionally_executed() -> None: """Preserve the retired caller's coverage contract in the shared job.""" diff --git a/tests/test_pr_review_merge_scheduler.py b/tests/test_pr_review_merge_scheduler.py index b821fe1ee3..1e5848aac3 100644 --- a/tests/test_pr_review_merge_scheduler.py +++ b/tests/test_pr_review_merge_scheduler.py @@ -252,6 +252,11 @@ def fake_run(args): }, ) monkeypatch.setattr(sched, "run", fake_run) + monkeypatch.setattr( + sched, + "recover_current_head_startup_failures", + lambda repo, pr, *, dry_run: [], + ) decision = inspect(candidate, dry_run=False) @@ -9010,6 +9015,11 @@ def test_main_reconciles_the_durable_admission_gate_when_a_state_path_is_given( lambda repo, workflow, pr, dry_run: dispatched.append(pr["number"]), ) monkeypatch.setattr(sched, "cancel_stale_pr_runs", lambda repo, pr, dry_run: []) + monkeypatch.setattr( + sched, + "recover_current_head_startup_failures", + lambda repo, pr, *, dry_run: [], + ) state_path = tmp_path / "admission.json" assert (