diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2e5a177ba..0c4b33923 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -971,6 +971,7 @@ jobs: if: github.event_name == 'pull_request' || github.ref == 'refs/heads/main' permissions: contents: read + pull-requests: read # Read the current title/body when a check is rerun after an edit. steps: - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 @@ -980,6 +981,8 @@ jobs: persist-credentials: false - name: Validate PR source or every integrated main commit and breaking footer run: python3 -m scripts.check_main_policies conventional + env: + GH_TOKEN: ${{ github.token }} downstream-imports: name: Downstream import smoke (representative consumer symbols) diff --git a/scripts/check_main_policies.py b/scripts/check_main_policies.py index 70bfa8e5d..49844838e 100644 --- a/scripts/check_main_policies.py +++ b/scripts/check_main_policies.py @@ -173,12 +173,21 @@ def event_context() -> tuple[str, str, dict[str, Any]]: return kind, target, event -def conventional(kind: str, target: str, event: dict[str, Any]) -> dict[str, Any]: +def conventional( + kind: str, target: str, event: dict[str, Any], *, api: GitHub | None = None +) -> dict[str, Any]: if kind == "push": commits = main_commits(target) else: pr = event["pull_request"] - validate_message(pr["title"] + "\n\n" + (pr["body"] or "")) + # Reruns retain the original webhook payload, including an obsolete PR + # description. Read the editable message again without changing which + # checkout and commits this run is allowed to validate. + if api is None: + api = GitHub(os.environ["GH_TOKEN"]) + current = api.repo(f"pulls/{pr['number']}") + require(current["head"]["sha"] == pr["head"]["sha"], "PR head changed; run current CI") + validate_message(current["title"] + "\n\n" + (current["body"] or "")) head, base = pr["head"]["sha"], pr["base"]["sha"] require(SHA.fullmatch(head) and SHA.fullmatch(base), "invalid PR source identities") fork = git("merge-base", base, head) diff --git a/tests/conformance/signing/test_ip_pinned_transport.py b/tests/conformance/signing/test_ip_pinned_transport.py index b9292b8e5..a5f0bbefb 100644 --- a/tests/conformance/signing/test_ip_pinned_transport.py +++ b/tests/conformance/signing/test_ip_pinned_transport.py @@ -349,7 +349,10 @@ def test_real_tls_handshake_still_validates_hostname() -> None: # machines. The intent is "handshake didn't reject", not speed. with httpx.Client(transport=transport, timeout=60.0) as client: response = client.get("https://example.com/") - assert response.status_code == 200 + # Any HTTP response proves the handshake and hostname check passed (a + # certificate mismatch raises before a response exists). The origin's + # own status is not under test: example.com intermittently answers 503. + assert 100 <= response.status_code < 600 @pytest.mark.skipif(not _internet_ok(), reason="no outbound internet") diff --git a/tests/test_main_release_policies.py b/tests/test_main_release_policies.py index 66d48c269..6d6e97bf1 100644 --- a/tests/test_main_release_policies.py +++ b/tests/test_main_release_policies.py @@ -100,6 +100,61 @@ def test_every_post_release_integration_has_real_agreement(history: list[str]) - assert policies.conventional("push", history[-1], {})["commits"] == history[1:] +def test_conventional_rerun_reads_the_corrected_pr_body(history: list[str]) -> None: + api = PolicyAPI(history) + pr = { + "number": 1174, + "head": {"sha": history[-1]}, + "base": {"sha": history[0]}, + "title": BREAKING_SUBJECT, + "body": "Missing the breaking footer in the original webhook.", + } + api.data["pulls/1174"] = {**pr, "body": BREAKING_FOOTER} + + result = policies.conventional("pull_request", history[-1], {"pull_request": pr}, api=api) + + assert result["commits"] == history[1:] + assert api.reads == ["pulls/1174"] + + +def test_conventional_rerun_rejects_a_newly_invalid_pr_message(history: list[str]) -> None: + api = PolicyAPI(history) + pr = { + "number": 1174, + "head": {"sha": history[-1]}, + "base": {"sha": history[0]}, + "title": BREAKING_SUBJECT, + "body": BREAKING_FOOTER, + } + api.data["pulls/1174"] = {**pr, "body": None} + + with pytest.raises(ReleaseRejectedError, match="BREAKING CHANGE"): + policies.conventional("pull_request", history[-1], {"pull_request": pr}, api=api) + + +def test_conventional_rerun_cannot_validate_a_different_pr_head(history: list[str]) -> None: + api = PolicyAPI(history) + pr = { + "number": 1174, + "head": {"sha": history[-1]}, + "base": {"sha": history[0]}, + "title": "fix: valid contribution", + "body": "", + } + api.data["pulls/1174"] = {**pr, "head": {"sha": "a" * 40}} + + with pytest.raises(ReleaseRejectedError, match="PR head changed"): + policies.conventional("pull_request", history[-1], {"pull_request": pr}, api=api) + + +def test_conventional_workflow_can_read_current_pr_metadata() -> None: + workflow = yaml.safe_load((policies.ROOT / ".github/workflows/ci.yml").read_text()) + job = workflow["jobs"]["conventional-commits"] + assert job["permissions"] == {"contents": "read", "pull-requests": "read"} + step = next(s for s in job["steps"] if "check_main_policies conventional" in s.get("run", "")) + assert step["env"]["GH_TOKEN"] == "${{ github.token }}" + + @pytest.mark.parametrize( "case", [