From 01afa706470a862da50be9726ff3ee5b5d8b671f Mon Sep 17 00:00:00 2001 From: Babissimo Date: Wed, 23 Sep 2026 15:34:50 +0100 Subject: [PATCH] Pin an app's CI to the uv its image builds with retina-server's CI ran uv 0.12.18 while its images pinned 0.12.5, and tower-finder-service#42 had already moved its CI onto the Dockerfile's UV_VERSION. The ADR said nothing either way, so each app would settle it again. Lock compatibility across uv versions is good in practice, so the pin is about reproducibility: a uv release cannot change CI without a commit, and one UV_VERSION edit moves CI, image and relock together. Where CI never builds the image it is also the only pre-merge check that the image's uv accepts the lock. The python-app scaffold has no Dockerfile, so its CI template keeps latest uv and says where to pin once an image exists. Decided in ClickUp 123zgec4m5q; retina-server#572 is the multi-job form. Co-Authored-By: Claude Opus 5.5 --- .../decisions/2026-09-23-python-apps-lock-with-uv.md | 12 ++++++++++-- .../skills/setup-repo/assets/ci/ci-python-app.yml | 3 +++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/docs/decisions/2026-09-23-python-apps-lock-with-uv.md b/docs/decisions/2026-09-23-python-apps-lock-with-uv.md index e617f8e..ebccf03 100644 --- a/docs/decisions/2026-09-23-python-apps-lock-with-uv.md +++ b/docs/decisions/2026-09-23-python-apps-lock-with-uv.md @@ -28,6 +28,9 @@ as a dependency. Apps are uv projects: - `uv.lock` is committed and is the only record of what gets installed. There is no `requirements.txt`. - CI runs `uv sync --locked`, which fails when the lock no longer matches `pyproject.toml`. +- Where the image pins uv as `ARG UV_VERSION`, CI installs that version, read from the + Dockerfile, and fails unless it reads exactly one `x.y.z`: setup-uv takes an empty version as + its latest. Locks are written with the same uv (`uv tool run uv@ lock`). - Images run `uv sync --locked --no-dev` into a virtualenv put first on `PATH`, not into the system site-packages: `uv sync` removes whatever the lock does not name, the base image's own `pip` included. @@ -48,11 +51,16 @@ as a dependency. Apps are uv projects: and lock again: uv keeps a locked version until told to upgrade it, so the lock ends at the deployed versions and the first image built from it matches the running one. - The uv that writes a lock and the uv that reads it need not match. A lock written by 0.12.5 - syncs under 0.9.22. + syncs under 0.9.22. Pinning CI to the image's uv therefore buys reproducibility more than + compatibility: a uv release cannot change CI without a commit, and one edit to `UV_VERSION` + moves CI, image and relock together. Where CI does not build the image, it is also the only + check before merge that the image's uv accepts the lock. ## 4. Adoption `setup-repo` gains a `python-app` stack that scaffolds §2. Its `python` stack stays as it is and remains the scaffold for libraries. retina-server moves first. tower-finder-service, retina-telemetry, retina-gui and node-infra's `mender-auto-accept` install with plain pip today -and move under `123zgec4jn0`. +and move under `123zgec4jn0`. The scaffold has no Dockerfile, so the CI pin is added with the +image: tower-finder-service#42's `id: uv` step is the pattern for one job, and retina-server#572 +wraps it in a composite action for several. diff --git a/plugins/core/skills/setup-repo/assets/ci/ci-python-app.yml b/plugins/core/skills/setup-repo/assets/ci/ci-python-app.yml index 02148e2..ed0b709 100644 --- a/plugins/core/skills/setup-repo/assets/ci/ci-python-app.yml +++ b/plugins/core/skills/setup-repo/assets/ci/ci-python-app.yml @@ -17,6 +17,9 @@ jobs: with: python-version: "3.12" + # Latest uv until the app has an image. Once its Dockerfile pins + # ARG UV_VERSION, install that version here instead: claude-shared ADR + # 2026-09-23-python-apps-lock-with-uv, §2. - name: Install uv uses: astral-sh/setup-uv@v5