From 2aa36ec53b9760bee04106a5fd12c221b107a5a5 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Tue, 13 Oct 2026 00:31:53 -0400 Subject: [PATCH 1/2] Install Lint and the documentation build from uv.lock Lint ran the newest ruff (uvx ruff) and the documentation build installed the newest releases into the system Python, so a new upstream release could change what every pull request was checked against. Lint now runs the ruff uv.lock pins, the same way Versioning already runs towncrier, and both docs jobs install through the test-environment action the tests use. Part of #2128. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/code_changes.yaml | 12 +++++++--- .github/workflows/pr_code_changes.yaml | 26 ++++++++++----------- .github/workflows/pr_docs_changes.yaml | 18 ++++---------- .github/workflows/versioning.yaml | 12 +++++++--- changelog.d/ci-lint-docs-from-lock.fixed.md | 1 + 5 files changed, 36 insertions(+), 33 deletions(-) create mode 100644 changelog.d/ci-lint-docs-from-lock.fixed.md diff --git a/.github/workflows/code_changes.yaml b/.github/workflows/code_changes.yaml index 3ffa1b7d9a..4d151a4218 100644 --- a/.github/workflows/code_changes.yaml +++ b/.github/workflows/code_changes.yaml @@ -80,7 +80,13 @@ jobs: - name: Install uv uses: astral-sh/setup-uv@v8.1.0 - name: Check formatting - run: uvx ruff format --check . + # With the ruff that uv.lock pins, so a new ruff release cannot change + # what Lint accepts until the lock is updated. + run: | + ruff=$(uv export --frozen --extra dev --no-hashes --no-header | grep -E '^ruff==' | cut -d' ' -f1) + if [ -z "$ruff" ]; then echo "::error::uv.lock pins no ruff"; exit 1; fi + echo "RUFF=$ruff" >> "$GITHUB_ENV" + uvx --from "$ruff" ruff format --check . - name: Lint # The ruff rules pyproject.toml selects, then two pyflakes rules it # ignores, checked in the package: F811, a definition that a later @@ -89,8 +95,8 @@ jobs: # notice 16), and F821, an undefined name. Neither has a finding # in the package today. run: | - uvx ruff check . - uvx ruff check --select F811,F821 policyengine_uk + uvx --from "$RUFF" ruff check . + uvx --from "$RUFF" ruff check --select F811,F821 policyengine_uk # The same suite as pull requests (test-suite.yaml), on the merged commit. Test: needs: Tested diff --git a/.github/workflows/pr_code_changes.yaml b/.github/workflows/pr_code_changes.yaml index 42d93e9513..7136d476b5 100644 --- a/.github/workflows/pr_code_changes.yaml +++ b/.github/workflows/pr_code_changes.yaml @@ -110,7 +110,13 @@ jobs: - name: Install uv uses: astral-sh/setup-uv@v8.1.0 - name: Check formatting - run: uvx ruff format --check . + # With the ruff that uv.lock pins, so a new ruff release cannot change + # what Lint accepts until the lock is updated. + run: | + ruff=$(uv export --frozen --extra dev --no-hashes --no-header | grep -E '^ruff==' | cut -d' ' -f1) + if [ -z "$ruff" ]; then echo "::error::uv.lock pins no ruff"; exit 1; fi + echo "RUFF=$ruff" >> "$GITHUB_ENV" + uvx --from "$ruff" ruff format --check . - name: Lint # The ruff rules pyproject.toml selects, then two pyflakes rules it # ignores, checked in the package: F811, a definition that a later @@ -119,8 +125,8 @@ jobs: # notice 16), and F821, an undefined name. Neither has a finding # in the package today. run: | - uvx ruff check . - uvx ruff check --select F811,F821 policyengine_uk + uvx --from "$RUFF" ruff check . + uvx --from "$RUFF" ruff check --select F811,F821 policyengine_uk Docs: # The documentation, built against a pull request's code changes. A # pull request that changes only docs/ builds it in pr_docs_changes.yaml. @@ -132,16 +138,10 @@ jobs: steps: - name: Checkout repo uses: actions/checkout@v6 - - name: Install uv - uses: astral-sh/setup-uv@v8.1.0 - - name: Set up Python - uses: actions/setup-python@v6 - with: - python-version: '3.13' - - name: Install package - run: uv pip install -e .[dev] --system - - name: Install policyengine - run: uv pip install policyengine --system + # The environment the tests use: uv.lock with the dev extra (which + # includes jupyter-book), so a new release cannot change the build. + - name: Install the locked environment + uses: ./.github/actions/test-environment - name: Test documentation builds run: make documentation # The YAML tests, the Python tests on four runners, and the check that diff --git a/.github/workflows/pr_docs_changes.yaml b/.github/workflows/pr_docs_changes.yaml index f3468523a3..09f7d5cad6 100644 --- a/.github/workflows/pr_docs_changes.yaml +++ b/.github/workflows/pr_docs_changes.yaml @@ -26,19 +26,9 @@ jobs: steps: - name: Checkout repo uses: actions/checkout@v6 - - name: Install uv - uses: astral-sh/setup-uv@v8.1.0 - - - name: Set up Python - uses: actions/setup-python@v6 - with: - python-version: '3.13' - - - name: Install package - run: uv pip install -e .[dev] --system - - name: Install policyengine - run: uv pip install policyengine --system - - name: Install JB - run: uv pip install "jupyter-book>=2.0.0a0" --system + # The environment the tests use: uv.lock with the dev extra (which + # includes jupyter-book), so a new release cannot change the build. + - name: Install the locked environment + uses: ./.github/actions/test-environment - name: Test documentation builds run: make documentation \ No newline at end of file diff --git a/.github/workflows/versioning.yaml b/.github/workflows/versioning.yaml index 04704f8b60..60efc8911c 100644 --- a/.github/workflows/versioning.yaml +++ b/.github/workflows/versioning.yaml @@ -269,7 +269,13 @@ jobs: - name: Install uv uses: astral-sh/setup-uv@v8.1.0 - name: Check formatting - run: uvx ruff format --check . + # With the ruff that uv.lock pins, so a new ruff release cannot change + # what Lint accepts until the lock is updated. + run: | + ruff=$(uv export --frozen --extra dev --no-hashes --no-header | grep -E '^ruff==' | cut -d' ' -f1) + if [ -z "$ruff" ]; then echo "::error::uv.lock pins no ruff"; exit 1; fi + echo "RUFF=$ruff" >> "$GITHUB_ENV" + uvx --from "$ruff" ruff format --check . - name: Lint # The ruff rules pyproject.toml selects, then two pyflakes rules it # ignores, checked in the package: F811, a definition that a later @@ -278,8 +284,8 @@ jobs: # notice 16), and F821, an undefined name. Neither has a finding # in the package today. run: | - uvx ruff check . - uvx ruff check --select F811,F821 policyengine_uk + uvx --from "$RUFF" ruff check . + uvx --from "$RUFF" ruff check --select F811,F821 policyengine_uk Compat: # What a user gets: the wheel, with the newest dependencies it allows, diff --git a/changelog.d/ci-lint-docs-from-lock.fixed.md b/changelog.d/ci-lint-docs-from-lock.fixed.md new file mode 100644 index 0000000000..f631b550c1 --- /dev/null +++ b/changelog.d/ci-lint-docs-from-lock.fixed.md @@ -0,0 +1 @@ +- Lint and the documentation build now install from uv.lock, as the tests already do, so a new release of ruff, jupyter-book or another dependency cannot change what pull requests are checked against. From 9d55a5c8e53a965f48ebe62b2c8ecc1e1eac098a Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Tue, 13 Oct 2026 06:28:03 -0400 Subject: [PATCH 2/2] Fail Lint clearly unless the lock pins exactly one ruff Review nits on #2394: pipefail so a failing uv export reports its own error, and an explicit check for exactly one ruff line. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/code_changes.yaml | 8 ++++++-- .github/workflows/pr_code_changes.yaml | 8 ++++++-- .github/workflows/versioning.yaml | 8 ++++++-- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/.github/workflows/code_changes.yaml b/.github/workflows/code_changes.yaml index 4d151a4218..2241a3dc59 100644 --- a/.github/workflows/code_changes.yaml +++ b/.github/workflows/code_changes.yaml @@ -83,8 +83,12 @@ jobs: # With the ruff that uv.lock pins, so a new ruff release cannot change # what Lint accepts until the lock is updated. run: | - ruff=$(uv export --frozen --extra dev --no-hashes --no-header | grep -E '^ruff==' | cut -d' ' -f1) - if [ -z "$ruff" ]; then echo "::error::uv.lock pins no ruff"; exit 1; fi + set -euo pipefail + ruff=$(uv export --frozen --extra dev --no-hashes --no-header | { grep -E '^ruff==' || true; } | cut -d' ' -f1) + if [ "$(printf '%s' "$ruff" | grep -c '^ruff==')" != 1 ]; then + echo "::error::uv.lock should pin exactly one ruff; found: ${ruff:-none}" + exit 1 + fi echo "RUFF=$ruff" >> "$GITHUB_ENV" uvx --from "$ruff" ruff format --check . - name: Lint diff --git a/.github/workflows/pr_code_changes.yaml b/.github/workflows/pr_code_changes.yaml index 7136d476b5..c848ce7c81 100644 --- a/.github/workflows/pr_code_changes.yaml +++ b/.github/workflows/pr_code_changes.yaml @@ -113,8 +113,12 @@ jobs: # With the ruff that uv.lock pins, so a new ruff release cannot change # what Lint accepts until the lock is updated. run: | - ruff=$(uv export --frozen --extra dev --no-hashes --no-header | grep -E '^ruff==' | cut -d' ' -f1) - if [ -z "$ruff" ]; then echo "::error::uv.lock pins no ruff"; exit 1; fi + set -euo pipefail + ruff=$(uv export --frozen --extra dev --no-hashes --no-header | { grep -E '^ruff==' || true; } | cut -d' ' -f1) + if [ "$(printf '%s' "$ruff" | grep -c '^ruff==')" != 1 ]; then + echo "::error::uv.lock should pin exactly one ruff; found: ${ruff:-none}" + exit 1 + fi echo "RUFF=$ruff" >> "$GITHUB_ENV" uvx --from "$ruff" ruff format --check . - name: Lint diff --git a/.github/workflows/versioning.yaml b/.github/workflows/versioning.yaml index 60efc8911c..f6756c1182 100644 --- a/.github/workflows/versioning.yaml +++ b/.github/workflows/versioning.yaml @@ -272,8 +272,12 @@ jobs: # With the ruff that uv.lock pins, so a new ruff release cannot change # what Lint accepts until the lock is updated. run: | - ruff=$(uv export --frozen --extra dev --no-hashes --no-header | grep -E '^ruff==' | cut -d' ' -f1) - if [ -z "$ruff" ]; then echo "::error::uv.lock pins no ruff"; exit 1; fi + set -euo pipefail + ruff=$(uv export --frozen --extra dev --no-hashes --no-header | { grep -E '^ruff==' || true; } | cut -d' ' -f1) + if [ "$(printf '%s' "$ruff" | grep -c '^ruff==')" != 1 ]; then + echo "::error::uv.lock should pin exactly one ruff; found: ${ruff:-none}" + exit 1 + fi echo "RUFF=$ruff" >> "$GITHUB_ENV" uvx --from "$ruff" ruff format --check . - name: Lint