Skip to content

Register and check rules for locking, installing and testing from uv.lock - #887

Merged
anth-volk merged 10 commits into
mainfrom
uv-lock-rules
Oct 21, 2026
Merged

anth-volk merged 10 commits into
mainfrom
uv-lock-rules

Conversation

@anth-volk

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

Copy link
Copy Markdown
Contributor

Fixes #886

What this changes

Four registered rules, all enforced by the new locked-installs quality guard (scripts/rules/locked_installs.py, standard library only), each with bad and good proof fixtures under scripts/rules/tests/fixtures/locked_installs/ and a test_rule_proof_* test:

Rule Checks
sim.dependencies.lock-committed Every tracked pyproject.toml directory commits a uv.lock; a pr.yml job runs uv lock --check. New .github/scripts/check-uv-locks.sh checks every tracked project (found from git ls-files), in test-integration after the API clients are generated, because policyengine-apis-integ depends on the generated client by path.
sim.dependencies.install-from-lock Every install in workflows, composite actions, shell scripts, Makefiles, Dockerfiles, Compose files and Modal image modules is uv sync --frozen, uv run --frozen or uv_sync(frozen=True), or a registered exception for that site.
sim.dependencies.test-from-lock Every pytest, coverage run and python -m unittest command runs through uv run --frozen, or is a registered exception.
sim.dependencies.scheduled-relock A schedule: workflow relocks with upgrades, covers every project directory, and opens a PR (static check of update-dependencies.yml → make update → uv lock --upgrade over libs/*/pyproject.toml projects/*/pyproject.toml, and dependency-update-create-pr.sh → gh pr create).

[[rule.exception]] gains match (one site), category, removal and issue; expires is optional. The guard requires a match, and either a removal condition, a tracking issue, or category = "pinned-tool" with an exact pin; an exception that matches no site fails as stale. TestRepository.test_every_install_capable_file_is_scanned fails if a tracked workflow, shell script, Makefile, Dockerfile, Compose file or Modal image module is not scanned.

Every uv sync and uv run in the workflows, the scripts they call, the Makefile and scripts/ now passes --frozen. Each already ran against a committed lock that matches pyproject.toml (in the bundle updater, one it has just relocked and checked with uv lock --check), so the installed versions do not change; --frozen only stops uv from relocking. The script tests expect the new arguments.

Doc: docs/engineering/skills/dependencies.md (rules and how to add an exception).

Install sites

Site Classification
Dockerfiles (simulation-entry, simulation-executor): uv sync --frozen --no-dev locked (unchanged)
deployment/docker-compose.yml: uv run --frozen --no-dev locked (unchanged)
Modal images: executor app.py, stage12_precompute_app.py, v2_app.py, gateway app.py: uv_sync(frozen=True) locked (unchanged)
Workflow and script uv sync / uv run (pr.yml, pr-image-smoke.yml, simulation-deploy.reusable.yml, simulation-rollback.yml, 13 .github/scripts/*.sh, Makefile, scripts/generate-clients.sh, scripts/test-integration-local.sh) locked (--frozen added)
pr.yml: uv tool install ruff==0.15.22 pinned tool
pr.yml: pip install "shellcheck-py==0.11.0.1" pinned tool
Makefile: uv tool run ruff@$(RUFF_VERSION) (version read from pr.yml's pin) pinned tool
update-policyengine-package.sh: uv run --isolated --no-project --with "${PACKAGE}==${LATEST}" pinned tool: reads the bundle versions of the new release before it is in any lock
pr.yml: python -m unittest discover -s scripts/rules/tests exception (test rule): the rules tool is standard-library only and the root has no project or lock. Remove when scripts/rules gains a pyproject.toml and uv.lock, or a third-party dependency.

Modal images

All four Modal image definitions already install packages with uv_sync(uv_project_dir=..., frozen=True) (the executor images with --only-group modal-simulation-image, the gateway with --no-default-groups). The executor images' run_commands(policyengine bundle install ... --no-packages ...) downloads certified datasets and installs no packages. add_local_python_source and add_local_dir copy repository source and static files, not packages. No Modal image definition is changed, and no Modal exception is needed. #611 is still open, but its first two steps appear done in code (--no-packages, model packages from the lock).

The scheduled relock

update-dependencies.yml runs make update (uv lock --upgrade in all 8 project directories). Its last 10 scheduled runs (2026-10-12 to 2026-10-21) all failed at "Generate API clients", most recently with error: Failed to parse uv.lock (a cryptography 50.0.1 entry with 49.0.0 wheels) on the update branch the run checks out, with uv 0.13.4. This PR does not change that failure: generate-clients.sh only gains --frozen, and the lock still has to parse.

Red and green

  • Exception fields: test_load red on the new allowed-key list, green after updating it; 115/115 rule-tool tests green.

  • Scanner: test_locked_installs red (module missing), then green for the proof, classification and exception tests.

  • check-uv-locks.sh: tests/test_check_uv_locks_script.py 2 failed (script missing), then 2 passed.

  • --frozen edits: the guard listed 72 unlocked install sites and 18 unlocked test commands before, 4 after (the registered exceptions). Executor script tests: 9 failed on the old arguments, then 129 passed. Entry workflow tests: 21 failed after the rebase (the rollback test's uv run python substitution, the authenticated-tests command, the routing prefix check), then 348 passed (commit Expect --frozen in the deployment and rollback workflow tests).

  • Registration: the guard on the previous registry reported the 4 unregistered sites; green after registering.

  • Makefile shell functions (after rebasing onto Run Pyright in CI for every package, in its own locked environment #864): 2 new tests red (a $(shell sed ...) reading the ruff pin was reported as an install; ruff@$(RUFF_VERSION) not accepted as a pin), green after the fix.

  • Self-review fixes (commit Report workflow sites on their own line and require exact pins and review dates): 4 tests red first. A second workflow site was reported on line 7 instead of 13; a ruff@latest pinned-tool exception was accepted; an exception with issue and no expires was accepted (and the existing message test expected the old text). After the fix, 21/21 guard tests pass, and deployment-workflow sites are reported on their real lines (131, 140, 260, 296).

Self-review

Finding Resolution
Workflow sites reported on the wrong line Fixed: run blocks are located in file order
name@latest accepted as an exact pin Fixed: the version must be a release number or a variable
An issue without expires accepted Fixed: removal condition, or both expires and issue
The doc did not say that quotes are removed or which commands are skipped Fixed in dependencies.md
A very short match could cover several sites in one file Not changed; recorded here
The scanned-files test uses classification logic close to the scanner's Not changed; recorded here

Checks

  • python -m unittest discover -s scripts/rules/tests -t .: OK
  • python scripts/run_quality_guards.py: rules-registry, ci-scripts, pyright-coverage, locked-installs passed
  • python -m scripts.rules guard --base-ref origin/main: passed; rendered views current
  • ruff 0.15.22 format --check and check .: clean. # noqa count: 211 before and after.
  • Locally, after ./scripts/generate-clients.sh, check-uv-locks.sh passes for all 8 projects (uv 0.12.23).

Environment variables

No changed line reads a new environment variable: the changes add --frozen to existing commands and one step running check-uv-locks.sh, which reads none (it uses git and uv). simulation-deploy.reusable.yml, simulation-rollback.yml and the deployment scripts run only on pushes to main or manual dispatch, so PR CI does not exercise their --frozen changes; their tests under projects/*/tests do.

Not verified

  • CI has not run yet.
  • The deployment and rollback workflows' --frozen changes have not run in GitHub Actions.
  • Installs started from Python through subprocess are not scanned (for example scripts/rules/pyright.py runs uv sync --frozen --all-extras, which is locked).

🤖 Generated with Claude Code

anth-volk and others added 10 commits October 22, 2026 00:41
A [[rule.exception]] can now carry match (a substring of the one command or
call it allows), category, removal (the condition for removing it) and issue
(the tracking issue of a dated exception). expires becomes optional. The
rendered rules list shows each of them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
scripts/rules/locked_installs.py reads every workflow, composite action,
shell script, Makefile, Dockerfile, Compose file and Modal image module. It
reports installs that do not install the locked versions, test commands that
do not run through uv run --frozen, project directories without a committed
uv.lock or a PR CI uv lock --check, and the absence of a scheduled relock that
covers every project and opens a pull request. Each check has bad and good
proof fixtures.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
.github/scripts/check-uv-locks.sh runs uv lock --check for every tracked
pyproject.toml directory and reports each stale lock before failing. The PR
test-integration job runs it after generating the API clients, because
policyengine-apis-integ depends on the generated client by path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every uv sync and uv run in the workflows, the scripts they call, the
Makefile and scripts/ now passes --frozen. Each one already ran against a
committed lock that matches pyproject.toml (or, in the bundle updater, one
it has just relocked and checked), so the installed versions do not change;
--frozen stops uv from relocking instead of installing what the lock says.
The PR test-integration job also runs check-uv-locks.sh after generating the
API clients. The script tests expect the new arguments.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
rules/registry.toml registers sim.dependencies.lock-committed,
install-from-lock, test-from-lock and scheduled-relock, enforced by the new
locked-installs quality guard, with proof fixtures. Four sites are registered
exceptions: the pinned ruff and shellcheck installs, the bundle updater's
pinned isolated read of a new release, and the rules tool's unittest run,
which has no project or lock. docs/engineering/skills/dependencies.md
explains the rules and how to add an exception. A test fails if any
install-capable file is not scanned.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Main's Makefile reads the pinned ruff version with $(shell sed ...) and runs
it with uv tool run ruff@$(RUFF_VERSION). The guard no longer splits
commands at parentheses, skips commands whose first program only prints or
edits text (echo, printf, sed, grep, awk), and accepts a name@version pin as
a pinned tool. The Makefile's pinned ruff is a registered pinned-tool
exception.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The rollback test replaced uv run python with printf, so it ran the frozen
command for real; the permission test looked for the old authenticated-tests
command; the routing invariants accepted only the frozen prefix. The routing
parser now accepts uv run with and without --frozen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…view dates

Three self-review findings in the locked-installs guard:

- A workflow site was reported on the first line holding its first three
  words, so different sites shared one line number. Run blocks are now
  located in file order and each command is reported on its own line.
- A pinned-tool exception accepted any name@version, including @latest. A
  pin is now name==version or name@version where the version is a release
  number or a variable that holds one.
- An exception with an issue but no expires was accepted. It now needs a
  removal condition, or both expires and issue.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tching

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@anth-volk
anth-volk marked this pull request as ready for review October 21, 2026 22:31
@anth-volk
anth-volk requested a review from a team as a code owner October 21, 2026 22:31
@anth-volk
anth-volk merged commit 1953bdc into main Oct 21, 2026
22 checks passed
@anth-volk
anth-volk deleted the uv-lock-rules branch October 21, 2026 22:31
anth-volk added a commit that referenced this pull request Oct 22, 2026
#887 changed the test job to run uv run --frozen pytest tests/, so the
proof's recorded invocation no longer matched and the rules registry check
failed after the rebase.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
anth-volk added a commit that referenced this pull request Oct 22, 2026
#887 changed the test job to run uv run --frozen pytest tests/, so the
proof's recorded invocation no longer matched and the rules registry check
failed after the rebase.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
anth-volk added a commit that referenced this pull request Oct 26, 2026
#887 changed the test job to run uv run --frozen pytest tests/, so the
proof's recorded invocation no longer matched and the rules registry check
failed after the rebase.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

Register and check rules for locking, installing and testing from uv.lock

1 participant