Skip to content

Pin one uv version, install and test from uv.lock, and relock weekly with Dependabot - #48

Merged
anth-volk merged 44 commits into
mainfrom
claude/rules-uv
Oct 30, 2026
Merged

anth-volk merged 44 commits into
mainfrom
claude/rules-uv

Conversation

@anth-volk

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

Copy link
Copy Markdown
Contributor

Fixes #47

What this does

Pins one uv release, runs CI from uv.lock, and has Dependabot propose uv.lock updates every week, each as a registered rule. 16.1 · 4 in the plan.

  • One uv release, written once. .github/uv-version.txt holds the line uv==0.13.6, and every astral-sh/setup-uv step reads it with version-file: .github/uv-version.txt (two in pr.yaml, four in ci.yml, one in push.yaml). The uv-version guard fails a step that writes a version: of its own, even the same release, and requires uv images, install scripts, release downloads and package installs, which cannot read a file, to name that release. The root pyproject.toml sets [tool.uv] required-version = ">=0.13", so an older uv refuses the project; the guard requires that to be a lower bound the release meets. It is no longer an exact ==0.13.6: Dependabot runs uv lock with its own uv (0.13.5 in its image today) and stops with ToolVersionNotSupported when required-version excludes it, so an exact pin would stop every Dependabot run. Rule emitter.dependencies.uv-version-pinned.

  • Locked installs and tests, unchanged from the last round. Every job runs uv sync --locked; ci.yml's quality job runs uv lock --check; the tests job, tools/check-quality.sh and tools/check-artifacts.sh run uv run --locked; the guard also reads the commands Python scripts under tools/ start. The sites that cannot are registered exceptions with their reasons (the standard-library-only rules tool and bump-version.py, the pinned shellcheck-py and towncrier, the collector contract test's installs, and tools/check-installed.py, which installs the built wheels as a user's uv tool install would). Rules emitter.dependencies.lock-committed, .install-from-lock and .test-from-lock. uv workspace members share the root uv.lock.

  • Dependabot replaces the custom relock. Review round 2 found that build code run by uv lock --upgrade could reach the PolicyEngine GitHub App's token, and that App can push to main past its ruleset, which starts a PyPI release. So update-dependencies.yaml, its three scripts and their tests are gone, and no workflow but push.yaml's versioning job uses the App token. Dependabot runs on GitHub's side with its own, limited credentials, so no secret of this repository reaches the dependency code that resolving runs. .github/dependabot.yml sets the uv ecosystem at /, a weekly schedule, versioning-strategy: lockfile-only and one group (locked-dependencies, pattern *), so each week brings one pull request that changes only uv.lock. Rule emitter.dependencies.dependabot-relock, checked by a test that reads dependabot.yml; it replaces emitter.dependencies.scheduled-relock, and the lock check now looks for a scheduled relock only when that rule is registered.

  • A Dependabot lock update passes and releases nothing. uv.lock does not change the published packages' requirements, so:

    • tools/check-changelog.sh lets a pull request whose author is dependabot[bot] and which changes nothing but uv.lock (compared with origin/<base>; the changelog job now fetches history and passes PR_AUTHOR and BASE_REF) through without a fragment. A Dependabot change to pyproject.toml still needs one;
    • tools/update-version.sh releases nothing for a merge without fragments: it changes no file and writes released=false, and push.yaml makes the "Update microcosm-emitter" commit only when released is true. Without that commit the verify, tag and publish jobs, which run on it, never start. Before this, such a merge failed the versioning job (bump-version.py refuses a release without fragments).

    Rule emitter.distributions.lock-only-merges-release-nothing. The PR description check, which Check that every pull request description into main starts with Fixes #N #52 adds, passes Dependabot's pull requests too.

Test-first record

New and changed tests were run on their own, by file or test ID; no command ran a test directory or suite. Earlier rounds' records are in the commit messages; this round:

Commit Result
d11a22d Tests Red: tests/test_dependabot.py 6 failed, 2 passed (the two that expect a fragment check already pass); scripts.rules.tests.test_uv_version fails to import VERSION_FILE; the new TestRepositoryLayout relock test fails
800926c The guards Green: the 28 test_uv_version and two relock tests pass
07501bc Dependabot, the scripts and workflows Green: tests/test_dependabot.py 8 passed; shellcheck and actionlint pass; all four guards pass; uv lock --check passes

GitHub rebased this branch onto main when #45 merged and force-pushed it; a765abd merges that rewritten branch with the ours strategy (its tree was identical to this branch's), so no history was rewritten here and every push was a fast-forward. The commit list therefore shows those commits twice.

CI

No trigger changes. update-dependencies.yaml is removed; pr.yaml's changelog job fetches full history and passes two values to its script; push.yaml's versioning job names its update step and makes the commit step conditional.

Environment variables and secrets in the changed workflows

Name Read by Provider Wiring
PR_AUTHOR tools/check-changelog.sh github.event.pull_request.user.login, from the pull_request event The changelog step's env:
BASE_REF tools/check-changelog.sh github.base_ref, set by the runner on pull_request events The changelog step's env:; fetch-depth: 0 makes origin/<base> readable
GITHUB_OUTPUT tools/update-version.sh Set by the runner Present in every step; read back as steps.version.outputs.released
secrets.APP_ID, secrets.APP_PRIVATE_KEY push.yaml's versioning job only (unchanged) GitHub Actions organization secrets, visible to all repositories The token step's with:, as before
GH_TOKEN (github.token), RESULTS, TEST_DATABASE_URL unchanged jobs Unchanged Unchanged

Dependabot's pull requests run CI with a read-only GITHUB_TOKEN and without this repository's secrets; none of the pull-request jobs needs a secret.

Unverified:

  • Dependabot has not run here. That its uv honours versioning-strategy: lockfile-only is from GitHub's current options reference, which lists uv among the ecosystems that support it; a July 2026 report said the uv ecosystem then ignored it and raised pyproject.toml lower bounds. If it does, the changelog check asks that pull request for a fragment, as it should.

Release

The fragment is changelog.d/uv-locked-installs.changed.md, a patch bump: merging releases the next patch version of both distributions. The wheels are unchanged.

🤖 Generated with Claude Code

anth-volk and others added 9 commits October 30, 2026 19:06
A member of a uv workspace is locked in the workspace root's uv.lock and
relocked with it, as microcosm-emitter-interface is in the emitter's
workspace. The lock check asks each member for a uv.lock of its own, and
the relock check for a separate relock.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The lock and relock checks now leave out the members of a uv workspace:
the projects a [tool.uv.workspace] members glob matches and no exclude
glob does, relative to the workspace root. Their dependencies are in the
root's uv.lock, so they need no uv.lock of their own and are relocked
with it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add emitter.dependencies.lock-committed, install-from-lock,
test-from-lock, scheduled-relock and uv-version-pinned, and run the
locked-installs and uv-version guards in the Rules registry job. They
fail on this tree: no .uv-version, eight setup-uv steps without a
version, no uv lock --check, uv run without --locked in ci.yml and four
scripts, and no scheduled relock.

The rendered views follow once the registry names only files that exist.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings review round 1 of #43.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
update-dependencies.yaml should run weekly and by hand, rebuild the
update-dependencies branch from main, run uv lock --upgrade, commit the
result with a changelog fragment and push it with a lease on the branch
it replaced, then open or update a pull request whose description starts
with Fixes # and the oldest open "Update locked dependencies" issue,
opening one when none is open. The scripts are run with fake git, gh and
date first on PATH, using the new tests/support/fake_commands.py.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings review round 1 of #45.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
update-dependencies.yaml runs on Mondays at 06:00 UTC, and by hand, with
the PolicyEngine GitHub App's token. Three scripts under tools/ rebuild
the update-dependencies branch from main, commit what uv lock --upgrade
changes with a changelog fragment and push it with a lease on the commit
the branch held, and open or update the pull request, whose description
starts with Fixes # and its open "Update locked dependencies" tracking
issue. They follow the simulation API's dependency-update scripts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- .uv-version holds uv 0.13.6, and every setup-uv step, in pr.yaml,
  ci.yml, push.yaml and update-dependencies.yaml, sets version: to it.
- ci.yml's quality job runs uv lock --check.
- The tests job, tools/check-quality.sh and tools/check-artifacts.sh run
  uv run --locked in place of uv run --no-sync, so each command checks the
  lock and runs in the environment it locks.
- The sites that cannot are registered exceptions, each with its reason
  and when it can go: the standard-library-only rules tool and
  bump-version.py, the pinned shellcheck-py and towncrier releases, and
  tools/check-collector.sh, which installs the pinned collector and the
  built wheels and must not sync them away.

All four quality guards pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@anth-volk
anth-volk added this pull request to stack #46 October 30, 2026 15:23
@anth-volk anth-volk changed the title claude/rules uv Pin one uv version, install and test from uv.lock, and relock every week Oct 30, 2026
anth-volk and others added 9 commits October 30, 2026 19:42
Review round 1 of #48: tools/check-installed.py runs uv pip install
through subprocess, which the check cannot see, since it reads Python
only for Modal image calls. Test that a Python script under the script
directories is read for subprocess calls whose leading arguments are
string literals, such as run("uv", "pip", "install", ...) or
subprocess.run(["pytest", ...]), and that the check's fixes name
--locked, the flag the rules ask for, rather than --frozen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The locked-installs check now reads each Python module under the
registry's script directories for subprocess calls, and treats the
command their leading string-literal arguments spell, such as
run("uv", "pip", "install", "--python", ...), like a shell command. Its
fixes name uv sync --locked and uv run --locked, and its docstring says
it accepts --frozen too.

It now finds tools/check-installed.py's uv pip install, which the next
commit registers as an exception.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review round 1 of #48: only CI's setup-uv steps pin uv, so a developer's
uv can be any release. Test that, when the repository root has a
pyproject.toml, the uv-version guard requires its [tool.uv]
required-version to be ==<the release in .uv-version>, which makes uv
itself refuse another release.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The uv-version guard now also requires the root pyproject.toml's
[tool.uv] required-version to be ==<the release in .uv-version>, and the
emitter's sets ==0.13.6, so uv refuses to run the project with another
release on a developer's machine, as setup-uv's pin does in CI.
.uv-version stays the one place the release is written: the guard keeps
the pyproject.toml and workflow copies equal to it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review round 1 of #48: the relock should create its App token only after
uv lock --upgrade, which can run a dependency's build code, has run. A
job's later steps run in the workspace that code could change, so the
token belongs in a later job. The relock check asks one job to relock
and open the pull request; test that a scheduled workflow whose jobs do
so together passes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The relock check reads a scheduled workflow's jobs together: one may run
uv lock --upgrade and another open the pull request, so the token that
opens it never shares a job with a dependency's build code.

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

Review round 1 of #48:

- The relock job runs uv lock --upgrade without any token or stored
  credentials and uploads uv.lock; a later job, which runs no third-party
  code, commits it, then creates the App token, narrowed to contents,
  pull requests and issues.
- Each run commits to a new branch, bot/update-dependencies/<UTC time>,
  pushes it with a plain push, which fails rather than replace a branch
  of that name, and opens a new pull request. It closes earlier relock
  pull requests whose branches hold only the relock's commit, with a
  comment naming the new one, and lists in the new description those
  someone pushed to.

The script tests now run in a real git repository whose origin is a
local bare repository; the fake gh evaluates each --jq expression with
jq.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
update-dependencies.yaml now has two jobs. "Relock uv.lock" checks out
main without stored credentials, runs uv lock --upgrade and uploads
uv.lock; it holds no token. "Propose the relock" checks out main afresh,
takes the relocked uv.lock, and runs only git, gh and these scripts:

- relock-prepare-branch.sh starts bot/update-dependencies/<UTC time>
  from main;
- relock-commit.sh, which replaces relock-commit-and-push.sh, commits
  the change with the changelog fragment, or nothing;
- only then is the App token created, narrowed to contents, pull
  requests and issues write;
- relock-open-pull-request.sh pushes the new branch with a plain push,
  passing the token to git through a credential helper that reads it
  from the environment, opens the pull request naming the tracking
  issue, and closes each earlier relock pull request whose branch holds
  only the relock's commit, with a comment naming the new one. Those
  someone pushed to stay open, and the new description lists them.

tests/support/fake_commands.py separates recorded calls with a record
separator, so an argument may hold a line break.

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

- tools/check-installed.py's uv pip install is a registered exception:
  it installs the built wheels as uv tool install does for a user, so it
  resolves their dependencies from PyPI on purpose.
- The install-from-lock and test-from-lock rules cover the commands the
  Python scripts under tools/ start, and their scopes include tools/*.py.
- The scheduled-relock rule says it accepts --upgrade-package and jobs
  that share the work, and its scope includes the relock scripts.
- The uv-version rule covers the root pyproject.toml's required-version.
- The README says which uv release to use, and the changelog fragment
  describes the relock and the exceptions as they are now.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
anth-volk added a commit that referenced this pull request Oct 30, 2026
Brings review round 1 of #48.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
anth-volk added a commit that referenced this pull request Oct 30, 2026
Brings review round 1 of #48.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings main after #43's merge and the 0.11.3 version commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
anth-volk and others added 2 commits October 30, 2026 20:22
Review round 2 of #48: the release was written in .uv-version, the root
pyproject.toml and eight setup-uv steps. Test that the uv-version guard
reads it from the root pyproject.toml's [tool.uv] required-version,
which must be one release written ==X.Y.Z, that a setup-uv step pins it
only by reading that file (version-file: pyproject.toml), and that a step
writing a release itself fails, even the same one. The proof fixtures
hold a pyproject.toml in place of .uv-version.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The uv-version guard now takes the release from the root pyproject.toml's
[tool.uv] required-version, which must be one release written ==X.Y.Z,
and requires each setup-uv step to read it with version-file:
pyproject.toml and to write no version: of its own. The eight steps do so,
.uv-version is gone, and images, downloads, package installs and Modal
calls, which cannot read a file, must still name the release. The rule,
the README and the changelog fragment say where the release is written.

The proof's bad step now sits on line 31, one comment line further down.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
anth-volk added a commit that referenced this pull request Oct 30, 2026
Brings review round 2 of #48: the uv release written once, in pyproject.toml.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
anth-volk and others added 15 commits October 30, 2026 20:57
update-dependencies.yaml should run weekly and by hand, rebuild the
update-dependencies branch from main, run uv lock --upgrade, commit the
result with a changelog fragment and push it with a lease on the branch
it replaced, then open or update a pull request whose description starts
with Fixes # and the oldest open "Update locked dependencies" issue,
opening one when none is open. The scripts are run with fake git, gh and
date first on PATH, using the new tests/support/fake_commands.py.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
update-dependencies.yaml runs on Mondays at 06:00 UTC, and by hand, with
the PolicyEngine GitHub App's token. Three scripts under tools/ rebuild
the update-dependencies branch from main, commit what uv lock --upgrade
changes with a changelog fragment and push it with a lease on the commit
the branch held, and open or update the pull request, whose description
starts with Fixes # and its open "Update locked dependencies" tracking
issue. They follow the simulation API's dependency-update scripts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- .uv-version holds uv 0.13.6, and every setup-uv step, in pr.yaml,
  ci.yml, push.yaml and update-dependencies.yaml, sets version: to it.
- ci.yml's quality job runs uv lock --check.
- The tests job, tools/check-quality.sh and tools/check-artifacts.sh run
  uv run --locked in place of uv run --no-sync, so each command checks the
  lock and runs in the environment it locks.
- The sites that cannot are registered exceptions, each with its reason
  and when it can go: the standard-library-only rules tool and
  bump-version.py, the pinned shellcheck-py and towncrier releases, and
  tools/check-collector.sh, which installs the pinned collector and the
  built wheels and must not sync them away.

All four quality guards pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review round 1 of #48: tools/check-installed.py runs uv pip install
through subprocess, which the check cannot see, since it reads Python
only for Modal image calls. Test that a Python script under the script
directories is read for subprocess calls whose leading arguments are
string literals, such as run("uv", "pip", "install", ...) or
subprocess.run(["pytest", ...]), and that the check's fixes name
--locked, the flag the rules ask for, rather than --frozen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The locked-installs check now reads each Python module under the
registry's script directories for subprocess calls, and treats the
command their leading string-literal arguments spell, such as
run("uv", "pip", "install", "--python", ...), like a shell command. Its
fixes name uv sync --locked and uv run --locked, and its docstring says
it accepts --frozen too.

It now finds tools/check-installed.py's uv pip install, which the next
commit registers as an exception.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review round 1 of #48: only CI's setup-uv steps pin uv, so a developer's
uv can be any release. Test that, when the repository root has a
pyproject.toml, the uv-version guard requires its [tool.uv]
required-version to be ==<the release in .uv-version>, which makes uv
itself refuse another release.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The uv-version guard now also requires the root pyproject.toml's
[tool.uv] required-version to be ==<the release in .uv-version>, and the
emitter's sets ==0.13.6, so uv refuses to run the project with another
release on a developer's machine, as setup-uv's pin does in CI.
.uv-version stays the one place the release is written: the guard keeps
the pyproject.toml and workflow copies equal to it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review round 1 of #48: the relock should create its App token only after
uv lock --upgrade, which can run a dependency's build code, has run. A
job's later steps run in the workspace that code could change, so the
token belongs in a later job. The relock check asks one job to relock
and open the pull request; test that a scheduled workflow whose jobs do
so together passes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The relock check reads a scheduled workflow's jobs together: one may run
uv lock --upgrade and another open the pull request, so the token that
opens it never shares a job with a dependency's build code.

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

Review round 1 of #48:

- The relock job runs uv lock --upgrade without any token or stored
  credentials and uploads uv.lock; a later job, which runs no third-party
  code, commits it, then creates the App token, narrowed to contents,
  pull requests and issues.
- Each run commits to a new branch, bot/update-dependencies/<UTC time>,
  pushes it with a plain push, which fails rather than replace a branch
  of that name, and opens a new pull request. It closes earlier relock
  pull requests whose branches hold only the relock's commit, with a
  comment naming the new one, and lists in the new description those
  someone pushed to.

The script tests now run in a real git repository whose origin is a
local bare repository; the fake gh evaluates each --jq expression with
jq.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
update-dependencies.yaml now has two jobs. "Relock uv.lock" checks out
main without stored credentials, runs uv lock --upgrade and uploads
uv.lock; it holds no token. "Propose the relock" checks out main afresh,
takes the relocked uv.lock, and runs only git, gh and these scripts:

- relock-prepare-branch.sh starts bot/update-dependencies/<UTC time>
  from main;
- relock-commit.sh, which replaces relock-commit-and-push.sh, commits
  the change with the changelog fragment, or nothing;
- only then is the App token created, narrowed to contents, pull
  requests and issues write;
- relock-open-pull-request.sh pushes the new branch with a plain push,
  passing the token to git through a credential helper that reads it
  from the environment, opens the pull request naming the tracking
  issue, and closes each earlier relock pull request whose branch holds
  only the relock's commit, with a comment naming the new one. Those
  someone pushed to stay open, and the new description lists them.

tests/support/fake_commands.py separates recorded calls with a record
separator, so an argument may hold a line break.

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

- tools/check-installed.py's uv pip install is a registered exception:
  it installs the built wheels as uv tool install does for a user, so it
  resolves their dependencies from PyPI on purpose.
- The install-from-lock and test-from-lock rules cover the commands the
  Python scripts under tools/ start, and their scopes include tools/*.py.
- The scheduled-relock rule says it accepts --upgrade-package and jobs
  that share the work, and its scope includes the relock scripts.
- The uv-version rule covers the root pyproject.toml's required-version.
- The README says which uv release to use, and the changelog fragment
  describes the relock and the exceptions as they are now.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review round 2 of #48: the release was written in .uv-version, the root
pyproject.toml and eight setup-uv steps. Test that the uv-version guard
reads it from the root pyproject.toml's [tool.uv] required-version,
which must be one release written ==X.Y.Z, that a setup-uv step pins it
only by reading that file (version-file: pyproject.toml), and that a step
writing a release itself fails, even the same one. The proof fixtures
hold a pyproject.toml in place of .uv-version.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The uv-version guard now takes the release from the root pyproject.toml's
[tool.uv] required-version, which must be one release written ==X.Y.Z,
and requires each setup-uv step to read it with version-file:
pyproject.toml and to write no version: of its own. The eight steps do so,
.uv-version is gone, and images, downloads, package installs and Modal
calls, which cannot read a file, must still name the release. The rule,
the README and the changelog fragment say where the release is written.

The proof's bad step now sits on line 31, one comment line further down.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
anth-volk and others added 5 commits October 30, 2026 20:57
Anthony replaced the custom relock with Dependabot. Test that:

- .github/dependabot.yml proposes uv.lock updates weekly, for the uv
  ecosystem at the root, as one grouped pull request that changes only
  uv.lock (versioning-strategy lockfile-only);
- tools/check-changelog.sh lets a Dependabot pull request that changes
  only uv.lock through without a fragment, and asks every other one for
  one, and the changelog job passes the author and base branch;
- tools/update-version.sh releases nothing, records released=false and
  runs nothing when a merge has no fragment, and push.yaml makes the
  version commit, which starts the release, only after a release;
- the lock check's scheduled-relock test runs only when the registry has
  that rule;
- the uv release is written once, as uv==X.Y.Z in .github/uv-version.txt,
  which setup-uv steps read, while pyproject.toml's required-version is a
  lower bound that release meets: an exact required-version stops
  Dependabot, whose uv (0.13.5 today) is another release.

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

- The uv-version guard takes the release from the line uv==X.Y.Z in
  .github/uv-version.txt, which setup-uv steps read with version-file,
  and requires the root pyproject.toml's required-version to be a lower
  bound written >=X.Y that the release meets. An exact required-version
  makes Dependabot stop: it runs uv lock with its own uv release and
  raises ToolVersionNotSupported when required-version excludes it.
- The locked-installs check looks for a scheduled relock only when the
  registry has the scheduled-relock rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review round 2 of #48 found that build code run by uv lock --upgrade
could reach the GitHub App token, which can push to main past its
ruleset. Anthony chose Dependabot instead, which runs on GitHub's side
with its own, limited credentials.

- update-dependencies.yaml, the three relock scripts and their tests are
  gone, and no workflow but push.yaml uses the App token.
- .github/dependabot.yml proposes the week's uv.lock updates as one
  grouped, lock-only pull request (rule
  emitter.dependencies.dependabot-relock, which replaces
  emitter.dependencies.scheduled-relock).
- tools/check-changelog.sh lets such a pull request through without a
  fragment, given its author and base branch by the changelog job, and
  tools/update-version.sh releases nothing for a merge without fragments;
  push.yaml then makes no version commit, so nothing is tagged or
  published (rule emitter.distributions.lock-only-merges-release-nothing).
- .github/uv-version.txt holds uv==0.13.6, which every setup-uv step
  reads, and pyproject.toml's required-version is >=0.13, a floor
  Dependabot's uv meets.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings #45, merged as 18b9c00, and the 0.11.4 version commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When #45 merged, GitHub rebased this branch onto main and force-pushed it
(head d28acdb). Its tree is the same as d110082 here, so this merge keeps
the local tree, which builds on d110082, and makes the push a fast-forward.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@anth-volk anth-volk changed the title Pin one uv version, install and test from uv.lock, and relock every week Pin one uv version, install and test from uv.lock, and relock weekly with Dependabot Oct 30, 2026
@anth-volk
anth-volk marked this pull request as ready for review October 30, 2026 17:11
@anth-volk
anth-volk merged commit 88b09a7 into main Oct 30, 2026
12 checks passed
@anth-volk
anth-volk deleted the claude/rules-uv branch October 30, 2026 17:11
anth-volk added a commit that referenced this pull request Oct 30, 2026
Brings #48, merged as 88b09a7, and the version commit after it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
anth-volk added a commit that referenced this pull request Oct 30, 2026
Brings main after #48's merge.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
anth-volk added a commit that referenced this pull request Oct 30, 2026
Brings main after #48's merge.

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.

Pin one uv version, install and test from uv.lock, and relock on a schedule

1 participant