Skip to content

ci(release): sync uv.lock from release-please and flip CI to --locked#21

Merged
igorlg merged 6 commits into
mainfrom
ci/release-please-sync-uv-lock
May 22, 2026
Merged

ci(release): sync uv.lock from release-please and flip CI to --locked#21
igorlg merged 6 commits into
mainfrom
ci/release-please-sync-uv-lock

Conversation

@igorlg

@igorlg igorlg commented May 22, 2026

Copy link
Copy Markdown
Owner

Issue

No tracked issue — first surfaced as the recurring annoyance of uv.lock
showing up dirty after every git pull post-release-please-merge.
Documented historically in
openspec/specs/ci-infrastructure/spec.md (the "Lockfile drift policy"
requirement), .github/CONTRIBUTING.md, .envrc, ci.yml, and
docs/CI.md "Root cause #2: --locked vs release-please version bump".

Summary

Configures release-please to update uv.lock's cfn-handler
self-version entry alongside pyproject.toml on every release, and
flips CI + .envrc back from uv sync --frozen to uv sync --locked.
Closes both the release-time drift and the contributor-relock-omission
foot-gun in one go.

The trick is the extra-files block in release-please-config.json
with the jsonpath 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
(strings exposed as {value, kind}). Tracked upstream:

Changes

Substantive:

  • release-please-config.json: add extra-files block targeting uv.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 paragraph
  • docs/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-reference
  • uv.lock: catch-up bump (1.1.1 → 1.2.0) in a separate first commit so the very PR introducing --locked doesn't fail its own CI

OpenSpec change:

  • openspec/changes/release-please-sync-uv-lock/: proposal, design, tasks, and a MODIFIED requirement under ci-infrastructure (replaces the Lockfile drift policy: --frozen requirement with the new --locked posture)

Validator tooling (added scope):

  • tests/release-please/validate-uv-lock-updater.js: Node script that loads release-please's GenericToml updater locally and exercises it against the real uv.lock. Asserts that the configured jsonpath matches exactly one line, that the file is not re-serialised (the GenericToml docstring is misleading — see PR description in the openspec design), and that the bare jsonpath without .value still 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] in gha-pre-release runs the validator (npm ci --silent && node validate-uv-lock-updater.js); add _check-npm helper
  • .gitignore: ignore node_modules/

Tests

  • openspec validate release-please-sync-uv-lock --strict passes
  • just openspec-validate passes for all changes
  • just ci-check passes (103 tests, 99.48% coverage)
  • just lint and just typecheck clean
  • uv sync --locked succeeds locally (proves the catch-up commit cleared all drift)
  • cd tests/release-please && npm ci && node validate-uv-lock-updater.js passes (positive + negative cases) — proves release-please will surgically update uv.lock without re-serialising the rest of the file

Breaking changes?

No. Library API unchanged. release-please-action behaviour is purely
additive (it now updates one extra line in uv.lock); CI install
semantics tighten from --frozen to --locked, which is a strictness
upgrade (a contributor PR that previously slipped through with a
stale lockfile will now be caught).

Checklist

  • Conventional Commits prefix in the PR title (ci(release):)
  • CHANGELOG entry will be generated automatically by release-please (no manual edit needed)
  • Documentation updated (docs/CI.md, .github/CONTRIBUTING.md, tests/release-please/README.md)
  • OpenSpec change opened — openspec/changes/release-please-sync-uv-lock/ modifies the ci-infrastructure capability

Verification roadmap post-merge

  1. Squash-merge produces a ci(release): ... commit on main with no version bump
  2. The next feat:/fix: merge triggers release-please; the resulting release PR's diff MUST include the uv.lock self-version line. That's the production validation
  3. openspec archive release-please-sync-uv-lock after step 2 confirms in production

igorlg added 4 commits May 22, 2026 11:37
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.
@github-actions

This comment was marked as outdated.

igorlg added 2 commits May 22, 2026 12:15
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.
@igorlg igorlg merged commit ed80319 into main May 22, 2026
17 checks passed
@igorlg igorlg deleted the ci/release-please-sync-uv-lock branch May 22, 2026 02:23
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.
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.

1 participant