From 308306e67cdafbb7c179eb55f82b441f06c2e10c Mon Sep 17 00:00:00 2001 From: Maxim Stykow Date: Thu, 21 May 2026 16:58:32 +0200 Subject: [PATCH 1/4] fix(release): move publish flow to GitHub Actions Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus Signed-off-by: Maxim Stykow --- .github/workflows/release.yml | 75 ++++++++++++++++++++++++++++--- Cargo.toml | 6 ++- release.sh | 14 ++++-- scripts/check_release_tag_sync.sh | 40 +++++++++++++++++ 4 files changed, 124 insertions(+), 11 deletions(-) create mode 100755 scripts/check_release_tag_sync.sh diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2dba3f858..7f87ff148 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,16 +12,21 @@ env: CARGO_TERM_COLOR: always permissions: - contents: write + contents: read + +concurrency: + group: release-${{ github.ref }} + cancel-in-progress: false jobs: - license-index: - name: Verify Embedded License Index + verify-release: + name: Verify Release runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 with: + fetch-depth: 0 submodules: recursive lfs: false @@ -34,7 +39,7 @@ jobs: - name: Set up Rust cache uses: Swatinem/rust-cache@42dc69e1aa15d09112580998cf2ef0119e2e91ae with: - key: embedded-license-index + key: release-verify - name: Verify embedded license index uses: ./.github/actions/verify-embedded-license-index @@ -42,9 +47,26 @@ jobs: - name: Verify ScanCode output format version sync uses: ./.github/actions/verify-scancode-output-format-version + - name: Verify release version sync + run: ./scripts/check_release_version_sync.sh + + - name: Verify release tag matches crate version + run: ./scripts/check_release_tag_sync.sh + + - name: Verify tagged commit is reachable from origin/main + run: | + git fetch origin main --depth=1 + git merge-base --is-ancestor "$GITHUB_SHA" origin/main + + - name: Check packaged crate size + run: ./scripts/check_crate_size.sh + + - name: Verify crates.io package publish dry-run + run: cargo publish --locked --dry-run -p provenant-cli + build: name: Build ${{ matrix.target }} - needs: license-index + needs: verify-release strategy: fail-fast: false matrix: @@ -137,10 +159,51 @@ jobs: target/${{ matrix.target }}/release/${{ matrix.asset_name }}.${{ matrix.archive_format }}.sha256 if-no-files-found: error + publish-crate: + name: Publish crate + needs: build + if: github.repository == 'mstykow/provenant' + runs-on: ubuntu-latest + environment: crates-io + permissions: + contents: read + id-token: write + steps: + - name: Checkout code + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 + with: + submodules: false + lfs: false + + - name: Install Rust toolchain + uses: actions-rust-lang/setup-rust-toolchain@150fca883cd4034361b621bd4e6a9d34e5143606 + with: + cache: false + rustflags: "" + + - name: Set up Rust cache + uses: Swatinem/rust-cache@42dc69e1aa15d09112580998cf2ef0119e2e91ae + with: + key: publish-crate + + - name: Authenticate to crates.io via trusted publishing + id: crates-io-auth + uses: rust-lang/crates-io-auth-action@1d2b8f3552d69b407d7790fa3ec7c39041305a61 + + - name: Publish provenant-cli to crates.io + run: cargo publish --locked -p provenant-cli + env: + CARGO_REGISTRY_TOKEN: ${{ steps.crates-io-auth.outputs.token }} + create-release: name: Create Release - needs: build + if: github.repository == 'mstykow/provenant' + needs: + - build + - publish-crate runs-on: ubuntu-latest + permissions: + contents: write steps: - name: Download all artifacts uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8 diff --git a/Cargo.toml b/Cargo.toml index c339c6b5e..278bee4cd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,8 +53,10 @@ pre-release-replacements = [ pre-release-hook = ["cargo", "generate-lockfile"] # Tag message template tag-message = "Release v{{version}}" -# Publish to crates.io automatically -publish = true +# Release tags must use the v-prefixed GitHub workflow contract. +tag-name = "v{{version}}" +# Crates.io publish is handled by the tag-triggered GitHub Actions workflow. +publish = false [workspace] resolver = "3" diff --git a/release.sh b/release.sh index 155aaed2b..3172ef8f1 100755 --- a/release.sh +++ b/release.sh @@ -111,6 +111,9 @@ run_release_step version "$RELEASE_TYPE" run_release_step replace run_release_step hook +echo "🔎 Verifying release version sync..." +./scripts/check_release_version_sync.sh + if [ -n "$EXECUTE_FLAG" ]; then echo "📝 Creating DCO-signed release commit..." git add -u @@ -123,9 +126,14 @@ if [ -n "$EXECUTE_FLAG" ]; then git commit -s -m "chore: release" fi -echo "🚀 Running cargo-release publish, tag, and push steps..." -run_release_step publish +echo "đŸˇī¸ Running cargo-release tag and push steps..." run_release_step tag run_release_step push -echo "✅ Release completed successfully!" +if [ -n "$EXECUTE_FLAG" ]; then + echo "✅ Release prep completed successfully!" + echo "â„šī¸ The pushed release tag now triggers GitHub Actions to publish provenant-cli to crates.io and create GitHub release assets." +else + echo "✅ Dry-run release prep completed successfully!" + echo "â„šī¸ In execute mode, the pushed release tag will trigger GitHub Actions to publish provenant-cli to crates.io and create GitHub release assets." +fi diff --git a/scripts/check_release_tag_sync.sh b/scripts/check_release_tag_sync.sh new file mode 100755 index 000000000..1251d33fe --- /dev/null +++ b/scripts/check_release_tag_sync.sh @@ -0,0 +1,40 @@ +#!/bin/bash +# SPDX-FileCopyrightText: Provenant contributors +# SPDX-License-Identifier: Apache-2.0 + +set -euo pipefail + +ROOT_MANIFEST="Cargo.toml" +TAG_NAME="${1:-${GITHUB_REF_NAME:-}}" + +if [ -z "$TAG_NAME" ]; then + echo "Usage: ./scripts/check_release_tag_sync.sh " + echo "Or set GITHUB_REF_NAME in the environment." + exit 1 +fi + +python3 - "$ROOT_MANIFEST" "$TAG_NAME" <<'PY' +import pathlib +import re +import sys + +root_manifest = pathlib.Path(sys.argv[1]).read_text(encoding="utf-8") +tag_name = sys.argv[2] + +if tag_name.startswith("refs/tags/"): + tag_name = tag_name.removeprefix("refs/tags/") + +root_version_match = re.search(r'^version = "([^"]+)"$', root_manifest, re.MULTILINE) +if root_version_match is None: + raise SystemExit("Could not determine root crate version from Cargo.toml") + +root_version = root_version_match.group(1) +expected_tag = f"v{root_version}" + +if tag_name != expected_tag: + raise SystemExit( + "Release tag is out of sync with Cargo.toml: " + f"expected {expected_tag}, got {tag_name}.\n" + "Create a tag that exactly matches the crate version." + ) +PY From 38657ecb750d867f1cbd003235024cae01eefbf2 Mon Sep 17 00:00:00 2001 From: Maxim Stykow Date: Thu, 21 May 2026 16:58:37 +0200 Subject: [PATCH 2/4] docs(release): document trusted publishing flow Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus Signed-off-by: Maxim Stykow --- docs/RELEASING.md | 51 ++++++++++++++++++++++++++++++++++------------- scripts/README.md | 11 ++++++++++ 2 files changed, 48 insertions(+), 14 deletions(-) diff --git a/docs/RELEASING.md b/docs/RELEASING.md index 0036f182a..724549680 100644 --- a/docs/RELEASING.md +++ b/docs/RELEASING.md @@ -4,7 +4,10 @@ This guide documents the maintainer release flow for `provenant`. ## Overview -Releases are driven locally with `release.sh`, which wraps `cargo release`, refreshes the embedded license data, and checks for ScanCode output-format drift before publishing. +Releases are split into two phases: + +1. local release preparation with `release.sh`, which refreshes the embedded license data, runs the release-time sync checks, writes the release commit, and pushes the release tag +2. tag-triggered GitHub Actions publication, which publishes `provenant-cli` to crates.io via trusted publishing and creates the GitHub Release assets The published crate name is `provenant-cli`, while the installed binary and product name remain `provenant` / Provenant. @@ -15,20 +18,17 @@ Before cutting a release, make sure you have: - A clean working tree - The `reference/scancode-toolkit/` submodule initialized via `./setup.sh` - `cargo-release` installed locally -- A valid crates.io login in your Cargo credentials - GPG signing configured for git tags -- A green `CI` workflow run for the exact commit you plan to release +- A green `CI` workflow run on `main` before you start release prep -Install `cargo-release` if needed: +For the normal release path, you do **not** need `cargo login` on your local machine. crates.io authentication is handled in GitHub Actions through trusted publishing. -```sh -cargo install cargo-release -``` +The tag-triggered publish job now has an explicit approval gate: the tagged commit must be reachable from `origin/main`, and GitHub must approve the `crates-io` environment before the workflow can mint the short-lived crates.io token. -Authenticate with crates.io if needed: +Install `cargo-release` if needed: ```sh -cargo login +cargo install cargo-release ``` ## Preflight Checks @@ -64,10 +64,26 @@ On every release attempt, the script: 1. verifies a clean working tree and initialized ScanCode reference submodule 2. updates the pinned ScanCode checkout from `origin/develop` and regenerates the embedded license index artifact 3. checks ScanCode output-format version sync before continuing -4. in `--execute` mode, commits any license-data refresh with `git commit -s` -5. runs the `cargo release` flow for versioning, publishing, tagging, and pushing +4. runs the release version sync check after `cargo release` updates versioned files +5. in `--execute` mode, commits any license-data refresh with `git commit -s` +6. runs the local `cargo release` flow for versioning, tagging, and pushing + +The exact `cargo release` behavior comes from `[package.metadata.release]` in `Cargo.toml`, including the `CITATION.cff` version replacement, `Cargo.lock` regeneration, signed tag creation, and push behavior. The release commit written by `release.sh` stays versionless (`chore: release`) and DCO-signed. + +## One-Time Trusted Publishing Setup -The exact `cargo release` behavior comes from `[package.metadata.release]` in `Cargo.toml`, including the `CITATION.cff` version replacement, `Cargo.lock` regeneration, signed tag creation, and publish/push behavior. The release commit written by `release.sh` stays versionless (`chore: release`) and DCO-signed. +Before relying on the automated publish step, configure a trusted publisher for the `provenant-cli` crate on crates.io: + +1. Open the `provenant-cli` crate settings on crates.io. +2. Create a GitHub environment named `crates-io` and configure the required reviewers you want for release approval. +3. Add a GitHub Actions trusted publisher for: + - owner: `mstykow` + - repository: `provenant` + - workflow file: `.github/workflows/release.yml` + - environment: `crates-io` +4. Protect release tags so only the small maintainer set can create or update `v*` tags. + +The crate already exists on crates.io, so this is a settings change, not a first-publish migration. ## GitHub Release Automation @@ -75,7 +91,9 @@ Pushing the `vX.Y.Z` tag triggers `.github/workflows/release.yml`. That workflow: +- verifies release invariants on the tagged commit, including version/tag alignment and crates.io dry-run packaging - Builds release binaries for Linux, macOS (Intel and Apple Silicon), and Windows +- waits for approval of the `crates-io` environment and then publishes `provenant-cli` to crates.io via trusted publishing - Re-runs embedded license index verification as a final release-time safeguard before building artifacts - Packages each build under the `provenant--` naming scheme - Generates SHA256 checksum files @@ -89,13 +107,18 @@ Monitor the [GitHub Actions release workflow](https://github.com/mstykow/provena Verify: -- The crates.io publish step succeeded +- The crates.io publish job in the GitHub Actions release workflow succeeded - The tag and release commit are present on the remote - The GitHub Release contains all expected Linux, macOS (Intel and Apple Silicon), and Windows archives and checksum files +If the GitHub Release asset step fails after crates.io publish has already succeeded, rerun only the failed downstream jobs. Do not rerun the successful crates.io publish job for the same version. + ## Common Failure Points - Missing submodule setup: run `./setup.sh` -- Missing crates.io credentials: run `cargo login` - Missing GPG configuration: `cargo release` cannot create the signed tag - Dirty working tree: clean up local changes before retrying +- Missing crates.io trusted publisher configuration for `provenant-cli` +- Missing `crates-io` GitHub environment approval or reviewer configuration +- Release tag is not reachable from `main` +- Release tag does not match the crate version in `Cargo.toml` diff --git a/scripts/README.md b/scripts/README.md index a08939421..b56265dd8 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -77,6 +77,17 @@ Example: ./scripts/check_release_version_sync.sh ``` +## `check_release_tag_sync.sh` + +Verify that a release tag matches the root crate version in `Cargo.toml`. + +Examples: + +```bash +./scripts/check_release_tag_sync.sh v0.1.1 +GITHUB_REF_NAME=v0.1.1 ./scripts/check_release_tag_sync.sh +``` + ## `check_scancode_output_format_sync.sh` Verify that Provenant's `OUTPUT_FORMAT_VERSION` stays aligned with the pinned From a773c80dac0901d0697cfe2b6da8e531b86fa507 Mon Sep 17 00:00:00 2001 From: Maxim Stykow Date: Thu, 21 May 2026 22:30:23 +0200 Subject: [PATCH 3/4] fix(release): remove trusted publishing approval gate Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus Signed-off-by: Maxim Stykow --- .github/workflows/release.yml | 1 - docs/RELEASING.md | 11 ++++------- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7f87ff148..6c5f47c4a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -164,7 +164,6 @@ jobs: needs: build if: github.repository == 'mstykow/provenant' runs-on: ubuntu-latest - environment: crates-io permissions: contents: read id-token: write diff --git a/docs/RELEASING.md b/docs/RELEASING.md index 724549680..139ba51c1 100644 --- a/docs/RELEASING.md +++ b/docs/RELEASING.md @@ -23,7 +23,7 @@ Before cutting a release, make sure you have: For the normal release path, you do **not** need `cargo login` on your local machine. crates.io authentication is handled in GitHub Actions through trusted publishing. -The tag-triggered publish job now has an explicit approval gate: the tagged commit must be reachable from `origin/main`, and GitHub must approve the `crates-io` environment before the workflow can mint the short-lived crates.io token. +The tag-triggered publish job verifies that the tagged commit is reachable from `origin/main` before it can mint the short-lived crates.io token. Install `cargo-release` if needed: @@ -75,13 +75,11 @@ The exact `cargo release` behavior comes from `[package.metadata.release]` in `C Before relying on the automated publish step, configure a trusted publisher for the `provenant-cli` crate on crates.io: 1. Open the `provenant-cli` crate settings on crates.io. -2. Create a GitHub environment named `crates-io` and configure the required reviewers you want for release approval. -3. Add a GitHub Actions trusted publisher for: +2. Add a GitHub Actions trusted publisher for: - owner: `mstykow` - repository: `provenant` - workflow file: `.github/workflows/release.yml` - - environment: `crates-io` -4. Protect release tags so only the small maintainer set can create or update `v*` tags. +3. Protect release tags so only the small maintainer set can create or update `v*` tags. The crate already exists on crates.io, so this is a settings change, not a first-publish migration. @@ -93,7 +91,7 @@ That workflow: - verifies release invariants on the tagged commit, including version/tag alignment and crates.io dry-run packaging - Builds release binaries for Linux, macOS (Intel and Apple Silicon), and Windows -- waits for approval of the `crates-io` environment and then publishes `provenant-cli` to crates.io via trusted publishing +- publishes `provenant-cli` to crates.io via trusted publishing - Re-runs embedded license index verification as a final release-time safeguard before building artifacts - Packages each build under the `provenant--` naming scheme - Generates SHA256 checksum files @@ -119,6 +117,5 @@ If the GitHub Release asset step fails after crates.io publish has already succe - Missing GPG configuration: `cargo release` cannot create the signed tag - Dirty working tree: clean up local changes before retrying - Missing crates.io trusted publisher configuration for `provenant-cli` -- Missing `crates-io` GitHub environment approval or reviewer configuration - Release tag is not reachable from `main` - Release tag does not match the crate version in `Cargo.toml` From a96b325d19c53f51dabdb8c8858bd5bf2941a2c2 Mon Sep 17 00:00:00 2001 From: Maxim Stykow Date: Thu, 21 May 2026 22:56:00 +0200 Subject: [PATCH 4/4] docs(release): tighten maintainer release guide Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus Signed-off-by: Maxim Stykow --- docs/RELEASING.md | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/docs/RELEASING.md b/docs/RELEASING.md index 139ba51c1..25971cf05 100644 --- a/docs/RELEASING.md +++ b/docs/RELEASING.md @@ -6,7 +6,7 @@ This guide documents the maintainer release flow for `provenant`. Releases are split into two phases: -1. local release preparation with `release.sh`, which refreshes the embedded license data, runs the release-time sync checks, writes the release commit, and pushes the release tag +1. local release preparation with `release.sh`, which refreshes the embedded license data, runs the release-time sync checks, prepares the release commit, and pushes the release tag 2. tag-triggered GitHub Actions publication, which publishes `provenant-cli` to crates.io via trusted publishing and creates the GitHub Release assets The published crate name is `provenant-cli`, while the installed binary and product name remain `provenant` / Provenant. @@ -21,9 +21,7 @@ Before cutting a release, make sure you have: - GPG signing configured for git tags - A green `CI` workflow run on `main` before you start release prep -For the normal release path, you do **not** need `cargo login` on your local machine. crates.io authentication is handled in GitHub Actions through trusted publishing. - -The tag-triggered publish job verifies that the tagged commit is reachable from `origin/main` before it can mint the short-lived crates.io token. +For the normal release path, you do **not** need `cargo login` on your local machine. crates.io authentication is handled by the tag-triggered GitHub Actions trusted publishing flow, which verifies that the tagged commit is reachable from `origin/main` before it can mint the short-lived crates.io token. Install `cargo-release` if needed: @@ -70,19 +68,6 @@ On every release attempt, the script: The exact `cargo release` behavior comes from `[package.metadata.release]` in `Cargo.toml`, including the `CITATION.cff` version replacement, `Cargo.lock` regeneration, signed tag creation, and push behavior. The release commit written by `release.sh` stays versionless (`chore: release`) and DCO-signed. -## One-Time Trusted Publishing Setup - -Before relying on the automated publish step, configure a trusted publisher for the `provenant-cli` crate on crates.io: - -1. Open the `provenant-cli` crate settings on crates.io. -2. Add a GitHub Actions trusted publisher for: - - owner: `mstykow` - - repository: `provenant` - - workflow file: `.github/workflows/release.yml` -3. Protect release tags so only the small maintainer set can create or update `v*` tags. - -The crate already exists on crates.io, so this is a settings change, not a first-publish migration. - ## GitHub Release Automation Pushing the `vX.Y.Z` tag triggers `.github/workflows/release.yml`. @@ -116,6 +101,5 @@ If the GitHub Release asset step fails after crates.io publish has already succe - Missing submodule setup: run `./setup.sh` - Missing GPG configuration: `cargo release` cannot create the signed tag - Dirty working tree: clean up local changes before retrying -- Missing crates.io trusted publisher configuration for `provenant-cli` - Release tag is not reachable from `main` - Release tag does not match the crate version in `Cargo.toml`