From 1ee775c45d699b95b85814b3734cbbb2c9d975c5 Mon Sep 17 00:00:00 2001 From: song <22676124+songoow@users.noreply.github.com> Date: Tue, 29 Sep 2026 08:56:57 -0400 Subject: [PATCH 1/2] ci: let a merge queue qualify candidates on both required checks The main ruleset requires an up-to-date branch. Every merge therefore forces each open PR to merge `main` and rerun the full ~45 minute qualification. A GitHub merge queue removes that multiplier, but only if both required checks report on `merge_group` events. - `python-tests.yml` runs on `merge_group`. Queue candidates take the non-PR classification path, which always plans full qualification. Sonar skips temporary queue refs, and the Node forward probe stays on push/dispatch. - `dco.yml` runs on `merge_group`, reads the queue's base ref and head SHA, normalizes the full `refs/heads/` base ref, and fails closed when either coordinate is missing. The trigger stays inert until the ruleset enables a merge queue. Co-Authored-By: Claude Opus 5.5 (1M context) Signed-off-by: song <22676124+songoow@users.noreply.github.com> --- .github/workflows/dco.yml | 9 +++- .github/workflows/python-tests.yml | 6 ++- docs/development/testing-and-quality.md | 13 +++++ tests/test_dco_workflow.py | 63 +++++++++++++++++++++---- tests/test_python_ci_workflow.py | 17 ++++++- 5 files changed, 96 insertions(+), 12 deletions(-) diff --git a/.github/workflows/dco.yml b/.github/workflows/dco.yml index 363be25385..ba2c5af476 100644 --- a/.github/workflows/dco.yml +++ b/.github/workflows/dco.yml @@ -2,6 +2,8 @@ name: DCO on: pull_request: + # A required check must also report on merge-queue candidates. + merge_group: permissions: contents: read @@ -19,12 +21,15 @@ jobs: - name: Require DCO trailers on contribution commits env: - BASE_REF: ${{ github.event.pull_request.base.ref }} - HEAD_SHA: ${{ github.event.pull_request.head.sha }} + BASE_REF: ${{ github.event.pull_request.base.ref || github.event.merge_group.base_ref }} + HEAD_SHA: ${{ github.event.pull_request.head.sha || github.event.merge_group.head_sha }} GH_TOKEN: ${{ github.token }} shell: bash run: | set -euo pipefail + # merge_group reports the base as a full ref (refs/heads/main). + BASE_REF="${BASE_REF#refs/heads/}" + test -n "${BASE_REF}" && test -n "${HEAD_SHA}" # The event's base SHA can predate upstream commits already in the PR. # Refresh the exact target branch before selecting PR-only commits. diff --git a/.github/workflows/python-tests.yml b/.github/workflows/python-tests.yml index f362dd0d81..6bc559220f 100644 --- a/.github/workflows/python-tests.yml +++ b/.github/workflows/python-tests.yml @@ -3,6 +3,8 @@ name: Python Tests on: pull_request: types: [opened, synchronize, reopened, ready_for_review, labeled, unlabeled] + # Inert until the main ruleset enables a merge queue; queue runs are full. + merge_group: workflow_dispatch: push: branches: @@ -363,7 +365,7 @@ jobs: needs: changes # The unsharded full suite outlasts a PR's qualification window, so the # non-blocking probe runs on main and on demand where it can finish. - if: github.event_name != 'pull_request' && needs.changes.outputs.core_tests == 'true' + if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && needs.changes.outputs.core_tests == 'true' continue-on-error: true runs-on: ubuntu-latest # This runs the full core conformance suite. Allow forward-runtime/runner @@ -503,6 +505,8 @@ jobs: sonar: needs: pytest + # Queue refs are temporary; analyse PRs and main, not gh-readonly-queue/*. + if: github.event_name != 'merge_group' uses: ./.github/workflows/sonarcloud.yml secrets: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} diff --git a/docs/development/testing-and-quality.md b/docs/development/testing-and-quality.md index 65f2970339..98d811b35e 100644 --- a/docs/development/testing-and-quality.md +++ b/docs/development/testing-and-quality.md @@ -113,11 +113,24 @@ is authoritative for activation. The lead maintainer alone retains the existing bypass exception; record the exact head, reason, validation and known failures whenever using it. A bypass does not turn failed tests into a pass. +Both required workflows also run on `merge_group`, so a GitHub merge queue can +qualify the exact candidate that would land on `main`. Queue candidates never +receive a job exemption: the classifier plans them as full, exactly like +`main`. `Sign-off` checks the same contribution range and exempts only +verified GitHub-generated two-parent merges, so configure the queue with the +merge method `merge`. The trigger is inert until the live ruleset enables a +merge queue. Enabling the queue, and then relaxing the up-to-date-branch +requirement, is a ruleset decision for the lead maintainer. + 每个 PR 都会收到 `merge-gate` 结果。代码、工作流、治理规则和未知路径必须通过 原有核心测试;失败、取消、缺失或意外跳过均不能通过。仅白名单根目录 Markdown 或 `docs/**/*.md` 的修改可显式跳过昂贵测试;运行时 prompt、可执行文档、代码删除 及代码移入文档均不享受豁免。实际启用状态以在线规则为准,使用 owner bypass 必须留下版本、原因、验证和已知失败的记录。 +两个必需工作流同样响应 `merge_group`,合并队列可在合入 `main` 前验证确切候选; +队列候选一律全量验证、不享受豁免。队列合并方式应设为 `merge`,因为 `Sign-off` +只豁免已验证的 GitHub 双父合并提交。在线规则启用合并队列前该触发不生效;启用 +队列并放宽“分支必须最新”要求由首席维护者决定。 To validate or change the classifier locally: diff --git a/tests/test_dco_workflow.py b/tests/test_dco_workflow.py index 7b0ed898f4..2aefa5db3e 100644 --- a/tests/test_dco_workflow.py +++ b/tests/test_dco_workflow.py @@ -67,20 +67,37 @@ def history(tmp_path: Path, git_env: dict[str, str]): return runner, old_base, upstream +def expression(value: str, context: dict[str, str]) -> str: + """Evaluate the workflow's `${{ a || b }}` env expressions over one event.""" + inner = value.removeprefix("${{").removesuffix("}}") + assert value != inner, value + return next((context[name] for name in (part.strip() for part in inner.split("||")) + if context.get(name)), "") + + def check_dco( repo: Path, env: dict[str, str], old_base: str, head: str, - *, base_ref: str = "release/next", + *, base_ref: str = "release/next", event: str = "pull_request", ) -> subprocess.CompletedProcess[str]: workflow = yaml.safe_load((ROOT / ".github/workflows/dco.yml").read_text(encoding="utf-8")) - event_values = { - "${{ github.event.pull_request.base.sha }}": old_base, - "${{ github.event.pull_request.base.ref }}": base_ref, - "${{ github.event.pull_request.head.sha }}": head, - "${{ github.token }}": "synthetic-read-only-token", - } + assert event in workflow[True] + if event == "pull_request": + context = { + "github.event.pull_request.base.sha": old_base, + "github.event.pull_request.base.ref": base_ref, + "github.event.pull_request.head.sha": head, + } + else: + # A merge-queue event carries a full base ref and no pull_request. + context = { + "github.event.merge_group.base_sha": old_base, + "github.event.merge_group.base_ref": f"refs/heads/{base_ref}", + "github.event.merge_group.head_sha": head, + } + context["github.token"] = "synthetic-read-only-token" steps = [step for step in workflow["jobs"]["signoff"]["steps"] if "run" in step] for step in steps: - step_env = {key: event_values[value] for key, value in step.get("env", {}).items()} + step_env = {key: expression(value, context) for key, value in step.get("env", {}).items()} result = subprocess.run( ["bash", "--noprofile", "--norc", "-e", "-o", "pipefail", "-c", step["run"]], cwd=repo, env={**env, **step_env}, check=False, @@ -270,3 +287,33 @@ def test_signed_pr_with_current_event_base_passes(history, git_env): head = commit(runner, git_env, "Signed contribution") result = check_dco(runner, git_env, upstream, head) assert result.returncode == 0, result.stdout + result.stderr + + +@pytest.mark.parametrize("signed", [False, True]) +def test_merge_queue_candidate_checks_the_same_contribution_range(history, git_env, signed): + runner, old_base, upstream = history + head = commit(runner, git_env, "Queued contribution", signed=signed) + result = check_dco(runner, git_env, old_base, head, event="merge_group") + assert (result.returncode == 0) == signed, result.stdout + result.stderr + assert (f"Commit {head} is missing" in result.stdout) == (not signed) + assert f"Commit {upstream} is missing" not in result.stdout + + +def test_merge_queue_platform_merge_is_exempt_only_with_verified_provenance(history, github_api): + runner, old_base, _ = history + env, calls = github_api + head = github_merge(runner, env) + metadata = verified_merge_record(runner, env, head) + result = check_dco(runner, {**env, "DCO_TEST_METADATA": json.dumps(metadata)}, old_base, head, + event="merge_group") + assert result.returncode == 0, result.stdout + result.stderr + assert f"Verified GitHub-generated merge {head}" in result.stdout + assert calls.read_text().strip() == f"api repos/qualification/dco/commits/{head}" + + +def test_missing_event_coordinates_fail_closed(history, git_env): + runner, old_base, _ = history + head = commit(runner, git_env, "Signed contribution") + for base_ref, head_sha in (("", head), ("release/next", "")): + result = check_dco(runner, git_env, old_base, head_sha, base_ref=base_ref) + assert result.returncode != 0, (base_ref, head_sha) diff --git a/tests/test_python_ci_workflow.py b/tests/test_python_ci_workflow.py index 0e5504a947..5016867dc4 100644 --- a/tests/test_python_ci_workflow.py +++ b/tests/test_python_ci_workflow.py @@ -394,5 +394,20 @@ def test_typescript_core_shards_feed_one_complete_coverage_report() -> None: assert "name: typescript-control-plane-coverage" in report assert "path: coverage/control-plane/lcov.info" in report forward = WORKFLOW.split(" node-forward-compatibility:\n", 1)[1].split(" test-shard:\n", 1)[0] - assert "github.event_name != 'pull_request'" in forward + assert "(github.event_name == 'push' || github.event_name == 'workflow_dispatch')" in forward assert "continue-on-error: true" in forward + + +def test_merge_queue_candidates_run_full_qualification() -> None: + import yaml + + workflow = yaml.safe_load(WORKFLOW) + assert "merge_group" in workflow[True] + classify = WORKFLOW.split("name: Classify the exact pull-request change", 1)[1].split(" - uses:", 1)[0] + # Only pull_request may classify an exemption; queue candidates take the + # non-PR branch, which the classifier always plans as full. + assert 'if [[ "$EVENT_NAME" == pull_request ]]; then' in classify + assert "--non-pr" in classify.split("else", 1)[1] + assert "github.event_name != 'merge_group'" in WORKFLOW.split(" sonar:\n", 1)[1].split(" uses:", 1)[0] + # The required check list is unchanged: merge-gate always reports. + assert "if: always()" in WORKFLOW.split(" merge-gate:\n", 1)[1] From aa5b03da7598dfb777cd1e4e898f09b3acab8640 Mon Sep 17 00:00:00 2001 From: song <22676124+songoow@users.noreply.github.com> Date: Tue, 29 Sep 2026 21:15:46 -0400 Subject: [PATCH 2/2] test(ci): pin the sonar job structurally, allowing the merge-queue skip The sonar caller check matched the literal text 'needs: pytest' followed by 'uses:', so adding the merge_group skip between them failed it. Parse the job instead: it still must need this run's pytest job and call the local reusable workflow, and its only condition may be the merge_group skip, so a status function such as always() cannot run analysis without same-run coverage. Co-Authored-By: Claude Opus 5.5 (1M context) Signed-off-by: song <22676124+songoow@users.noreply.github.com> --- tests/test_sonarcloud_workflow.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/test_sonarcloud_workflow.py b/tests/test_sonarcloud_workflow.py index 52c432a471..bfdca6eeb2 100644 --- a/tests/test_sonarcloud_workflow.py +++ b/tests/test_sonarcloud_workflow.py @@ -4,6 +4,7 @@ from pathlib import Path import pytest +import yaml WORKFLOW = Path(__file__).resolve().parents[1] / ".github" / "workflows" / "sonarcloud.yml" @@ -71,6 +72,12 @@ def test_sonar_reuses_same_run_coverage_without_a_privileged_trigger() -> None: assert "name: python-coverage-xml" in workflow assert "run-id:" not in workflow assert "github-token:" not in workflow - assert "needs: pytest\n uses: ./.github/workflows/sonarcloud.yml" in caller + sonar = yaml.safe_load(caller)["jobs"]["sonar"] + # Coverage comes from this run's pytest job, handed to the local reusable + # workflow. The only condition may skip merge-queue refs; a status function + # such as always() would let analysis run without that coverage. + assert sonar["needs"] == "pytest" + assert sonar["uses"] == "./.github/workflows/sonarcloud.yml" + assert sonar.get("if") == "github.event_name != 'merge_group'" assert '"apps/**"' in caller assert '"sonar-project.properties"' in caller