diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 42277ec85d..ef7e885af9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -71,8 +71,17 @@ jobs: name: lint runs-on: ubuntu-latest needs: dependency-locks - if: github.event_name == 'push' || github.event_name == 'merge_group' || github.event.pull_request.head.repo.fork + if: >- + !cancelled() + && (github.event_name == 'push' || github.event_name == 'merge_group' || github.event.pull_request.head.repo.fork) steps: + # A skipped required check counts as satisfied, so refuse here instead of skipping. + - name: Require fresh dependency locks + if: needs.dependency-locks.result != 'success' + run: | + echo 'Refuse to run without verified dependency locks.' >&2 + exit 1 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false @@ -93,13 +102,21 @@ jobs: build: needs: dependency-locks - if: github.event_name == 'push' || github.event_name == 'merge_group' || github.event.pull_request.head.repo.fork + if: >- + !cancelled() + && (github.event_name == 'push' || github.event_name == 'merge_group' || github.event.pull_request.head.repo.fork) timeout-minutes: 10 name: build permissions: contents: read runs-on: ubuntu-latest steps: + - name: Require fresh dependency locks + if: needs.dependency-locks.result != 'success' + run: | + echo 'Refuse to run without verified dependency locks.' >&2 + exit 1 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false @@ -148,13 +165,21 @@ jobs: name: test (Python ${{ matrix.python-version }}) runs-on: ubuntu-latest needs: dependency-locks - if: github.event_name == 'push' || github.event_name == 'merge_group' || github.event.pull_request.head.repo.fork + if: >- + !cancelled() + && (github.event_name == 'push' || github.event_name == 'merge_group' || github.event.pull_request.head.repo.fork) strategy: fail-fast: false matrix: # Per-PR coverage protects both ends of the support window. python-version: ["3.10", "3.14"] steps: + - name: Require fresh dependency locks + if: needs.dependency-locks.result != 'success' + run: | + echo 'Refuse to run without verified dependency locks.' >&2 + exit 1 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false @@ -185,8 +210,16 @@ jobs: name: test (HTTPX2) runs-on: ubuntu-latest needs: dependency-locks - if: github.event_name == 'push' || github.event_name == 'merge_group' || github.event.pull_request.head.repo.fork + if: >- + !cancelled() + && (github.event_name == 'push' || github.event_name == 'merge_group' || github.event.pull_request.head.repo.fork) steps: + - name: Require fresh dependency locks + if: needs.dependency-locks.result != 'success' + run: | + echo 'Refuse to run without verified dependency locks.' >&2 + exit 1 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false diff --git a/tests/test_uv_workflows.py b/tests/test_uv_workflows.py index 779777dabd..18a7bceb9d 100644 --- a/tests/test_uv_workflows.py +++ b/tests/test_uv_workflows.py @@ -1005,6 +1005,19 @@ def test_untrusted_provenance_leaves_no_dependency_install_reachable(tmp_path: P } == installers +def test_required_checks_fail_when_dependency_provenance_fails() -> None: + jobs = dependency_workflow_jobs() + for name in ("lint", "build", "test", "test-httpx2"): + job = jobs[name] + assert re.search(r"^ needs:\s*dependency-locks\s*$", job, re.MULTILINE) + assert re.search(r"^ if: >-\n !cancelled\(\)\n", job, re.MULTILINE) + + guard = job.split(" steps:\n", 1)[1].split("\n\n", 1)[0] + assert "if: needs.dependency-locks.result != 'success'" in guard + assert "exit 1" in guard + assert "uses:" not in guard + + def test_scheduled_compatibility_keeps_dependency_provenance_gate() -> None: jobs = dependency_workflow_jobs() assert not re.search(r"^ if:.*schedule", jobs["dependency-locks"], re.MULTILINE)