From 97d9ab3bc3baac2275ae2d8436bae912f1a5639e Mon Sep 17 00:00:00 2001 From: bartzbeielstein <32470350+bartzbeielstein@users.noreply.github.com> Date: Thu, 4 Jun 2026 10:34:11 +0200 Subject: [PATCH 1/3] ci: re-lock uv.lock during semantic-release prepare semantic-release bumped the version in pyproject.toml but never re-locked, so after every release uv.lock still recorded the previous self-version. Each subsequent `uv run` then re-locked the file, dirtying the working tree and failing the pre-push pytest hook with "files were modified by this hook" despite green tests (one manual one-line sync per release, see 321226c). Run `uv lock` in the exec prepareCmd right after the sed version bump and add uv.lock to the @semantic-release/git assets so the refreshed lockfile ships inside the chore(release) commit. Also syncs uv.lock for the 0.12.6 release that predates this fix. Co-Authored-By: Claude Opus 4.8 (1M context) --- .releaserc.json | 5 +++-- uv.lock | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.releaserc.json b/.releaserc.json index 84a86e42..a1a188c1 100644 --- a/.releaserc.json +++ b/.releaserc.json @@ -128,7 +128,7 @@ [ "@semantic-release/exec", { - "prepareCmd": "sed -i 's/^version = .*/version = \"${nextRelease.version}\"/' pyproject.toml" + "prepareCmd": "sed -i 's/^version = .*/version = \"${nextRelease.version}\"/' pyproject.toml && uv lock" } ], [ @@ -159,7 +159,8 @@ { "assets": [ "CHANGELOG.md", - "pyproject.toml" + "pyproject.toml", + "uv.lock" ], "message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}" } diff --git a/uv.lock b/uv.lock index 61052f15..504941ba 100644 --- a/uv.lock +++ b/uv.lock @@ -2926,7 +2926,7 @@ wheels = [ [[package]] name = "spotoptim" -version = "0.12.5" +version = "0.12.6" source = { editable = "." } dependencies = [ { name = "black" }, From 740014c7d1c9ab8b7871bbbea7ebff219c705ef9 Mon Sep 17 00:00:00 2001 From: bartzbeielstein <32470350+bartzbeielstein@users.noreply.github.com> Date: Thu, 4 Jun 2026 10:45:17 +0200 Subject: [PATCH 2/3] ci: declare read-only top-level permissions in quarto-build.yml MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit OpenSSF Scorecard's TokenPermissions check flags workflows with no top-level permissions block (alert #118). `read-all` is off the table — a called workflow may not request more than its caller's job grants — but `contents: read` is a subset of the callers' `contents: write` grant, so it passes reusable-workflow validation while giving Scorecard the explicit default it wants. The build-docs job still elevates to `contents: write` for the gh-pages push. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/quarto-build.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/quarto-build.yml b/.github/workflows/quarto-build.yml index d2763017..92e6d37e 100644 --- a/.github/workflows/quarto-build.yml +++ b/.github/workflows/quarto-build.yml @@ -23,10 +23,13 @@ on: type: string default: "" -# NOTE: no workflow-level `permissions:` here. A called workflow may not +# NOTE: keep workflow-level permissions minimal. A called workflow may not # request more than its caller's job grants; `read-all` would demand read on # every scope while callers grant only `contents: write` (startup_failure). -# The job below declares exactly what it needs. +# `contents: read` is a subset of that grant and satisfies the OpenSSF +# Scorecard TokenPermissions check; the job below elevates to what it needs. +permissions: + contents: read env: QUARTO_VERSION: "1.8.27" From 41ee2240872ea1e8d0bd5d883cd16fe058dbd6de Mon Sep 17 00:00:00 2001 From: bartzbeielstein <32470350+bartzbeielstein@users.noreply.github.com> Date: Thu, 4 Jun 2026 10:59:44 +0200 Subject: [PATCH 3/3] ci: push semantic-release commits over SSH via deploy key MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The new protect-main ruleset blocks direct pushes to main. The GitHub Actions app cannot be added as a bypass actor on this org (422 from the API, no suggestion in the UI), so the release pipeline switches to the deploy-key route instead: - release.yml checks out with the DEPLOY_KEY secret; actions/checkout then configures origin over SSH with that key. - .releaserc.json pins repositoryUrl to the SSH form so semantic-release pushes the chore(release) commit and tag as the deploy key, which the ruleset's DeployKey bypass waves through. Without the explicit repositoryUrl, semantic-release would construct an authenticated HTTPS URL from GITHUB_TOKEN and push as github-actions[bot] — blocked. - GITHUB_TOKEN continues to serve the GitHub API (release creation). Requires the DEPLOY_KEY secret (write deploy key) and the DeployKey bypass on ruleset protect-main to be configured. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/release.yml | 6 ++++++ .releaserc.json | 1 + 2 files changed, 7 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 433863d5..d9db2265 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -37,9 +37,15 @@ jobs: steps: # ── Checkout ────────────────────────────────────────────────────── + # ssh-key: the protect-main ruleset blocks direct pushes to main, with + # a bypass for deploy keys. Checking out with the DEPLOY_KEY secret + # makes git operate over SSH as that deploy key, so semantic-release's + # chore(release) push (see repositoryUrl in .releaserc.json) bypasses + # the ruleset. GITHUB_TOKEN stays for the GitHub API (release creation). - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 + ssh-key: ${{ secrets.DEPLOY_KEY }} # ── Python + uv ────────────────────────────────────────────────── - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 diff --git a/.releaserc.json b/.releaserc.json index a1a188c1..80aa67dc 100644 --- a/.releaserc.json +++ b/.releaserc.json @@ -1,4 +1,5 @@ { + "repositoryUrl": "git@github.com:sequential-parameter-optimization/spotoptim.git", "branches": [ "main" ],