ci(release): sync uv.lock from release-please and flip CI to --locked#21
Merged
Conversation
The cfn-handler self-version entry in uv.lock had drifted from pyproject.toml: release-please bumped pyproject.toml to 1.2.0 in PR #16 but cannot run `uv lock` to update the corresponding entry in uv.lock. This commit clears that drift in isolation, ahead of the substantive fix that prevents it from recurring. See follow-up commit on this branch for the release-please-config change that automates this sync going forward.
Configures release-please to update the cfn-handler self-version entry in uv.lock alongside pyproject.toml on every release, eliminating the post-merge drift that surfaced as a dirty working tree on every `git pull` + `uv sync`. With drift fixed at the source, flips CI and the .envrc dev shell back from `uv sync --frozen` to `uv sync --locked`, which also catches a contributor editing pyproject.toml dependencies without running `uv lock` (previously a known foot-gun documented as a tradeoff). The release-please-config.json change uses the workaround discovered in googleapis/release-please#2455's comment thread: "extra-files": [ { "type": "toml", "path": "uv.lock", "jsonpath": "$.package[?(@.name.value=='cfn-handler')].version" } ] The `.value` accessor descends into release-please's TOML AST node shape (string nodes are exposed as {value, kind} rather than bare strings). Tracked upstream: - googleapis/release-please#2561 (feature request to make this native) - googleapis/release-please#2455 (bug behind the .value workaround) - googleapis/release-please#2693 (proposed upstream fix) Catch-up commit (preceding this one on the branch) cleared the existing 1.1.1 -> 1.2.0 drift in uv.lock so the very PR introducing --locked doesn't fail its own CI. OpenSpec change: openspec/changes/release-please-sync-uv-lock/ Updates the ci-infrastructure spec's lockfile-drift requirement. Files touched: - release-please-config.json: add extra-files block - .github/workflows/ci.yml: --frozen -> --locked (x2); rewrite comment - .github/workflows/examples-lint.yml: --frozen -> --locked - .envrc: --frozen -> --locked; rewrite comment block - .github/CONTRIBUTING.md: rewrite lockfile policy paragraph - docs/CI.md: replace 'Lockfile drift and --frozen' subsection; add 'Status: resolved' note to the v1.0.0 postmortem
Adds tests/release-please/, a Node.js validator that loads release-
please's GenericToml updater locally and exercises it against the real
uv.lock + the jsonpath from release-please-config.json. Catches:
1. Configured jsonpath stops matching the cfn-handler entry
(someone removed/edited the extra-files block by mistake)
2. release-please starts re-serialising uv.lock instead of doing
a surgical byte-range edit (would cause whole-file rewrites
in every release PR — see the misleading docstring on
GenericToml that prompted this validator)
3. Bare `@.name` jsonpath unexpectedly starts working, which
would mean googleapis/release-please#2693 has landed and we
can drop the .value workaround and simplify the config
Pinned to release-please 17.3.0 — the version bundled in the action
SHA pinned in release.yml. README documents the version-linkage
policy: bumping the action SHA also bumps this pin.
Layout:
tests/release-please/
\u251c\u2500\u2500 README.md purpose, usage, version-pin policy
\u251c\u2500\u2500 package.json release-please pin (one dep)
\u251c\u2500\u2500 package-lock.json committed for repro
\u2514\u2500\u2500 validate-uv-lock-updater.js the actual checks (positive + negative)
node_modules/ is gitignored. pytest doesn't collect non-Python files.
ruff doesn't lint non-Python files. mypy/pyright/coverage scope is
src/ only. Adding this directory is fully isolated from existing
tooling.
Run manually:
cd tests/release-please && npm install && node validate-uv-lock-updater.js
A `just` recipe wiring this in as a pre-push check is a follow-up.
Adds a new step [3/7] to the gha-pre-release recipe that runs the local Node validator added in the previous commit. The step is fast (~5s including `npm ci --silent`) and gates on the same invariants the validator script asserts: - configured jsonpath matches the cfn-handler self-version entry - release-please does NOT re-serialise uv.lock (surgical edit only) - bare `@.name` jsonpath still doesn't match (workaround necessary) Step renumbering: existing [3a..4] became [4a..5]. Header comment block updated to describe the new step. Adds a `_check-npm` recipe helper following the existing pattern of `_check-act`/`_check-gh-token`/ `_check-docker`. Also updates openspec/changes/release-please-sync-uv-lock/tasks.md: ticks off everything done so far (catch-up commit, release-please config, workflow flips, .envrc update, doc rewrites, validator tooling, all local validation steps), removes the assumption that sections 8-11 require human intervention, and adds explicit section 6 covering the validator scope-creep so future readers see why `tests/release-please/` exists in the archive.
This comment was marked as outdated.
This comment was marked as outdated.
The dependency-review-action on PR #21 failed because release-please's transitive deps include packages with licenses outside the allowlist (BlueOak-1.0.0, CC-BY-3.0). Those are unavoidable in the release- please dep tree, but they don't matter here: this is a development-only local validator, never installed at runtime. The workflow's 'fail-on-scopes: runtime' setting correctly gates only runtime deps; the bug was on my side — release-please was declared under 'dependencies' (which dependency-review-action treats as runtime) instead of 'devDependencies'. Moves the pin to devDependencies; npm regenerates the lockfile with 'dev: true' markers on every transitive entry. Same exact resolved tree, same hashes; only metadata for dependency-review-action's scope filter. Also updates README.md ('dependencies.release-please' → 'devDependencies.release-please' in the version-pin policy walkthrough) and ticks off the section 8 tasks in the OpenSpec change since the PR is now open.
CI is green on PR #21: - secure-workflows.yml pass - ci.yml matrix (10 entries) pass — proves --locked works post-fix - lint + typecheck pass - cfn-lint over examples pass - review dependencies pass (after the devDependencies fix) - analyze (python) pass 10.2 (validate the extra-files behaviour against a real release- please PR) and 11.x (archive the change) are deferred: they require the next feat:/fix: merge to actually exercise release-please's release-PR generation. This PR's commit prefix (ci:) does not trigger a release. Annotates each deferred task with the explicit blocker so a future reader doesn't think they're outstanding.
6 tasks
igorlg
added a commit
that referenced
this pull request
May 22, 2026
…sync-uv-lock (#22) Both changes were merged to main earlier in the session (PRs #18 and #21 respectively); this commit closes them out by: 1. Ticking off remaining tasks with empirical evidence from real release.yml runs since the merges. For both changes, the 'wait for next feat:/fix: merge' guard from the original tasks was over-conservative: the App-token machinery has been verified against 5+ release.yml runs since PR #18, and the new release-please-config.json with extra-files for uv.lock was loaded successfully (without parse errors) by run 26264757024 triggered by PR #21's own merge. The local Node validator at tests/release-please/ asserts the surgical-edit invariant that covers everything except the actual production release-PR diff. Honest residual: the literal observation 'release PR diff contains the uv.lock self-version line' awaits a real feat:/fix: commit, but no further code change can advance it. 2. Running 'openspec archive --yes' on each change. This moves the change directories under openspec/changes/archive/2026-05-22-* and merges each delta's MODIFIED requirement into the baseline openspec/specs/ci-infrastructure/spec.md. - ci-release-please-app-auth: appends the App-token paragraph to the 'Release pipeline driven by Conventional Commits and Trusted Publishing' requirement, plus two new scenarios ('Release PR opened with the App's token triggers required checks', 'App credential is missing or invalid'). - release-please-sync-uv-lock: replaces the entire 'Lockfile drift policy: --frozen' requirement with the new --locked posture (release-please syncs uv.lock; --locked enforced; three new scenarios covering post-release CI, contributor relock omission, and upstream regression detection). Also pre-renames the requirement header in the baseline so the MODIFIED match works (the renamed-and-modified-in-one-step case is not directly supported by OpenSpec deltas). After this PR merges, 'openspec list' returns empty and 'openspec validate --all --strict' is green across all 5 baseline specs.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Issue
No tracked issue — first surfaced as the recurring annoyance of
uv.lockshowing up dirty after every
git pullpost-release-please-merge.Documented historically in
openspec/specs/ci-infrastructure/spec.md(the "Lockfile drift policy"requirement),
.github/CONTRIBUTING.md,.envrc,ci.yml, anddocs/CI.md"Root cause #2: --locked vs release-please version bump".Summary
Configures
release-pleaseto updateuv.lock'scfn-handlerself-version entry alongside
pyproject.tomlon every release, andflips CI +
.envrcback fromuv sync --frozentouv sync --locked.Closes both the release-time drift and the contributor-relock-omission
foot-gun in one go.
The trick is the
extra-filesblock inrelease-please-config.jsonwith the jsonpath workaround discovered in
googleapis/release-please#2455's
comment thread:
The
.valueaccessor descends into release-please's TOML AST node shape(strings exposed as
{value, kind}). Tracked upstream:Changes
Substantive:
release-please-config.json: addextra-filesblock targetinguv.lock.github/workflows/ci.yml:uv sync --frozen→--locked(×2); rewrite the inline rationale comment.github/workflows/examples-lint.yml:uv sync --frozen --only-group lint→--locked.envrc:uv sync --all-groups --frozen --quiet→--locked; rewrite the inline comment block.github/CONTRIBUTING.md: rewrite the lockfile-policy paragraphdocs/CI.md: replace the "Lockfile drift and--frozen" subsection with "Lockfile sync via release-please"; mark "Root cause chore(main): release 1.1.0 #2" of the v1.0.0 postmortem as resolved with a forward-referenceuv.lock: catch-up bump (1.1.1 → 1.2.0) in a separate first commit so the very PR introducing--lockeddoesn't fail its own CIOpenSpec change:
openspec/changes/release-please-sync-uv-lock/: proposal, design, tasks, and a MODIFIED requirement underci-infrastructure(replaces theLockfile drift policy: --frozenrequirement with the new--lockedposture)Validator tooling (added scope):
tests/release-please/validate-uv-lock-updater.js: Node script that loads release-please'sGenericTomlupdater locally and exercises it against the realuv.lock. Asserts that the configured jsonpath matches exactly one line, that the file is not re-serialised (theGenericTomldocstring is misleading — see PR description in the openspec design), and that the bare jsonpath without.valuestill does NOT match (so we know when #2693 lands)tests/release-please/{package.json,package-lock.json,README.md}: pinned to release-please@17.3.0 (matches the action SHA's bundled version)justfile: new step[3/7]ingha-pre-releaseruns the validator (npm ci --silent && node validate-uv-lock-updater.js); add_check-npmhelper.gitignore: ignorenode_modules/Tests
openspec validate release-please-sync-uv-lock --strictpassesjust openspec-validatepasses for all changesjust ci-checkpasses (103 tests, 99.48% coverage)just lintandjust typecheckcleanuv sync --lockedsucceeds locally (proves the catch-up commit cleared all drift)cd tests/release-please && npm ci && node validate-uv-lock-updater.jspasses (positive + negative cases) — proves release-please will surgically updateuv.lockwithout re-serialising the rest of the fileBreaking changes?
No. Library API unchanged.
release-please-actionbehaviour is purelyadditive (it now updates one extra line in
uv.lock); CI installsemantics tighten from
--frozento--locked, which is a strictnessupgrade (a contributor PR that previously slipped through with a
stale lockfile will now be caught).
Checklist
ci(release):)docs/CI.md,.github/CONTRIBUTING.md,tests/release-please/README.md)openspec/changes/release-please-sync-uv-lock/modifies theci-infrastructurecapabilityVerification roadmap post-merge
ci(release): ...commit onmainwith no version bumpfeat:/fix:merge triggers release-please; the resulting release PR's diff MUST include theuv.lockself-version line. That's the production validationopenspec archive release-please-sync-uv-lockafter step 2 confirms in production