Skip to content

Pin one uv version in every workflow, Dockerfile and Modal image - #967

Merged
anth-volk merged 2 commits into
mainfrom
pin-uv-version
Oct 27, 2026
Merged

anth-volk merged 2 commits into
mainfrom
pin-uv-version

Conversation

@anth-volk

@anth-volk anth-volk commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #966

Summary

Every place this repository installs uv now installs uv 0.13.6, the one release written in the new .uv-version file, and a new registered rule fails CI when an install pins no release or another one.

Before this change, the 23 astral-sh/setup-uv@v8.1.0 steps had no version: input, so each run installed the newest uv release, and the two Dockerfiles and the four Modal images copied uv from ghcr.io/astral-sh/uv:latest. Lock checks, installs and the daily relock could run different uv releases from one day to the next.

Why 0.13.6

The current uv.lock files were relocked with uv 0.13.6 in #959. .github/scripts/check-uv-locks.sh passes for all eight projects with both 0.13.4 (the release policyengine-api pins) and 0.13.6. With both passing, the newer release is chosen: it is the one that wrote the current locks, and the one CI has been installing since 2026-10-26.

The rule

sim.dependencies.uv-version-pinned (mechanical, enforced), checked by the new uv-version quality guard (scripts/rules/uv_version.py) in the quality-guards job through python scripts/run_quality_guards.py. The guard reads every tracked workflow, composite action, shell script, Makefile, Dockerfile and Compose file, and the Modal image calls in Python modules outside tests and fixtures, and fails when:

  • an astral-sh/setup-uv step sets no with: version:, or another release;
  • a uv container image reference (ghcr.io/astral-sh/uv or astral/uv) has no tag, a tag that is not a release (such as latest), or another release; a variant tag such as 0.13.6-python3.13-bookworm is accepted;
  • a Modal uv_sync or uv_pip_install call passes no uv_version=, or another release;
  • a uv install script or release download URL, a pip, pipx or uv tool install of uv, or uv self update names no release, or another one.

A workflow's with: input can't be read from a file, and Modal imports its image definitions inside containers that don't hold the repository root, so each install writes the release itself and the guard keeps every copy equal to .uv-version. An upgrade changes .uv-version, and the guard then lists every install still on the old release.

Proof: rules/fixtures/sim.dependencies.uv-version-pinned/bad holds the unpinned setup-uv step from pr.yml, the ghcr.io/astral-sh/uv:latest copy from the Dockerfiles and an unpinned Modal uv_sync call, as on main; good holds them pinned. scripts/rules/tests/test_uv_version.py::TestUvVersionProof::test_rule_proof_uv_version_pinned runs in the same job's python -m unittest discover -s scripts/rules/tests -t . step. docs/engineering/skills/dependencies.md describes the rule and how to upgrade uv, ci-workflows.md points to it, and docs/generated/rules.md and the AGENTS.md Rules block are regenerated.

The pins

  • Workflows (23 steps): pr.yml (5), pr-image-smoke.yml (1), simulation-deploy.reusable.yml (13), simulation-rollback.yml (1), publish-clients.yml (1), check-policyengine-updates.yml (1), update-dependencies.yml (1) each set version: "0.13.6".
  • Dockerfiles: projects/policyengine-simulation-entry/Dockerfile and projects/policyengine-simulation-executor/Dockerfile copy uv from ghcr.io/astral-sh/uv:0.13.6.
  • Modal images: the uv_sync calls in projects/policyengine-simulation-executor/src/modal/app.py (runtime image), v2_app.py (Stage 12 worker images), stage12_precompute_app.py (Stage 12 cache precompute image) and projects/policyengine-simulation-gateway/src/policyengine_simulation_gateway/app.py pass uv_version="0.13.6". Both locked Modal releases (1.4.2 in the executor, 1.5.1 in the gateway) accept it.
  • No script or Makefile target installs uv; the Makefile and scripts use the uv on PATH.

Red, then green

The first commit adds the check alone, and the guard fails on main's 29 installs (23 setup-uv steps, 2 Dockerfiles, 4 Modal uv_sync calls), with uv-version: failed and every other guard passing. The second commit pins them, and the guard passes.

Deployment verification

  • Images: both images were built locally from a clean git archive of this branch: the entrypoint for linux/amd64, as Cloud Run runs it, and the executor for the local platform (linux/arm64; its base stage uses $BUILDPLATFORM). Both pulled ghcr.io/astral-sh/uv:0.13.6 (index digest sha256:af22a52f5eafef3d78e551599d405c02df61919d18028f0cea8eda93bc9986d0), ran uv sync --frozen, and report uv 0.13.6 from uv --version inside the image. The PR's Docker build jobs build both for linux/amd64.
  • Modal images: the PR image smoke builds the gateway image, the executor runtime image and the UK Stage 12 worker image with the pinned uv_version. The Stage 12 cache precompute image, the US Stage 12 worker image and the full executor image with precomputed artifacts are built only at deployment, so they are not checked before merge. Because the uv_sync layer's definition changes, Modal rebuilds each image from that layer on its next build (the executor runtime image then downloads its certified datasets again).
  • Inputs and secrets: the only lines added to workflows are with: and version: "0.13.6" on setup-uv steps. No environment variable, secret, env: block or other step input changes, and setup-uv's cache keys do not include the uv release.

Checks run locally

  • uv tool run ruff@0.15.22 format --check projects/*/src libs/*/src and ruff check .: pass.
  • python scripts/run_quality_guards.py: every guard passes, including uv-version (at the first commit, uv-version fails with the 29 installs listed above and every other guard passes).
  • python -m scripts.rules guard --base-ref origin/main: passes; the only note is that the rule is new.
  • python -m scripts.rules render: no diff.
  • python -m unittest discover -s scripts/rules/tests -t .: 263 tests pass (at the first commit, only test_every_uv_install_in_the_repository_pins_the_one_release fails).
  • .github/scripts/check-uv-locks.sh with uv 0.13.6, after ./scripts/generate-clients.sh: every lock passes.
  • shellcheck on the two fixture scripts: clean. No script under .github/scripts/ changed.
  • Unit tests with uv 0.13.6 (uv run --frozen pytest tests/): gateway 268 passed, entrypoint 742 passed, executor 1,876 passed (22 skipped).
  • python -m scripts.rules pyright --sync on the gateway and the executor: no Pyright errors.

🤖 Generated with Claude Code

anth-volk and others added 2 commits October 28, 2026 00:44
Workflows installed uv with astral-sh/setup-uv and no version input, so
each run installed the newest uv release, and the two Dockerfiles and
the four Modal images copied uv from the uv image's latest tag. Lock
checks, installs and the daily relock could therefore run different uv
releases from one day to the next.

The new rule sim.dependencies.uv-version-pinned names one release, in
the new .uv-version file (0.13.6), and the new uv-version quality guard
(scripts/rules/uv_version.py) fails when an install pins no release or
another one: an astral-sh/setup-uv step without with: version:, a uv
container image reference without the release as its tag, a Modal
uv_sync or uv_pip_install call without uv_version=, and a uv install
script, release download, pip, pipx or uv tool install of uv, or uv self
update that does not name the release.

The proof fixtures hold the unpinned setup-uv step from pr.yml, the
ghcr.io/astral-sh/uv:latest copy from the Dockerfiles and an unpinned
Modal uv_sync call. The guard runs in the quality-guards job through
run_quality_guards.py, and its tests run there with the rules tool's
unittest suite. The dependency and CI workflow skill docs describe the
rule and how to upgrade uv, and the rendered rules views are
regenerated.

This commit adds only the check: it fails on the 23 setup-uv steps, the
two Dockerfiles and the four Modal uv_sync calls on main, which the
next commit pins.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every uv install now pins 0.13.6, the release in .uv-version:

- the 23 astral-sh/setup-uv steps in pr.yml (5), pr-image-smoke.yml,
  simulation-deploy.reusable.yml (13), simulation-rollback.yml,
  publish-clients.yml, check-policyengine-updates.yml and
  update-dependencies.yml set with: version: "0.13.6";
- the entrypoint and executor Dockerfiles copy uv from
  ghcr.io/astral-sh/uv:0.13.6 instead of :latest;
- the uv_sync calls of the executor's runtime, v2 and Stage 12
  precompute Modal images and of the gateway's Modal image pass
  uv_version="0.13.6", which Modal 1.4.2 and 1.5.1, the locked
  releases, accept.

0.13.6 is the release the current uv.lock files were relocked with in
#959. check-uv-locks.sh passes for every project with both 0.13.4 (the
release policyengine-api pins) and 0.13.6, so the newer one is used.

No other workflow input, environment variable or secret changes. The
Modal images rebuild once from their uv_sync layer, whose definition
now names the release. The uv-version guard added in the previous
commit now passes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@anth-volk
anth-volk marked this pull request as ready for review October 27, 2026 21:32
@anth-volk
anth-volk requested a review from a team as a code owner October 27, 2026 21:32
@anth-volk
anth-volk merged commit 0bb4e2d into main Oct 27, 2026
24 of 25 checks passed
@anth-volk
anth-volk deleted the pin-uv-version branch October 27, 2026 21:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Pin one uv version in every workflow, Dockerfile and Modal image

1 participant