From 57213a85b29d581e98b17f1cddac8810f04c943d Mon Sep 17 00:00:00 2001 From: Jay Rodgers Date: Fri, 28 Aug 2026 14:46:13 +0100 Subject: [PATCH 1/3] chore: pin Rust 1.98.0 --- .github/workflows/ci.yml | 4 ++-- .github/workflows/publish.yml | 2 +- CONTRIBUTING.md | 2 +- Cargo.toml | 1 + rust-toolchain.toml | 4 ++++ 5 files changed, 9 insertions(+), 4 deletions(-) create mode 100644 rust-toolchain.toml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6cde152..a295724 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,7 +31,7 @@ jobs: needs: version-bump steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable + - uses: dtolnay/rust-toolchain@1.98.0 with: components: clippy, rustfmt - name: Configure shared Cargo cache @@ -123,7 +123,7 @@ jobs: contents: write steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable + - uses: dtolnay/rust-toolchain@1.98.0 - name: Configure shared Cargo cache run: | # The runner image pre-sets CARGO_HOME=$HOME/.cargo; drop the diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index f42a5bb..bec32a3 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable + - uses: dtolnay/rust-toolchain@1.98.0 - name: Verify version and tag match upstream run: scripts/verify_upstream_version.sh "$GITHUB_REF_NAME" - name: Configure shared Cargo cache diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a9aca9c..86a3c20 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,7 +10,7 @@ workflow. ## Development setup -- A recent stable Rust toolchain (`rustup default stable`). +- Rust 1.98.0, selected automatically by the checked-in `rust-toolchain.toml`. - Go (for the upstream parity scripts and the pinned `upstream-go/` checkout). - No other system dependencies; there are no C build steps. diff --git a/Cargo.toml b/Cargo.toml index 8547601..2fff9b0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ name = "rusty-bubbles" exclude = ["POLICIES.md"] version = "2.1.0" edition = "2021" +rust-version = "1.98.0" description = "Cleanroom Rust port of charm.land/bubbles/v2: TUI components for Bubble Tea applications" license = "MIT" diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..d1ad785 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "1.98.0" +profile = "minimal" +components = ["clippy", "rustfmt"] From 86e40e18ed815276e62b818344b4c1cf282e99bb Mon Sep 17 00:00:00 2001 From: Jay Rodgers Date: Fri, 28 Aug 2026 15:12:35 +0100 Subject: [PATCH 2/3] fix: pin rust-toolchain action immutably --- .github/workflows/ci.yml | 7 +++++-- .github/workflows/publish.yml | 4 +++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a295724..b526ea8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,8 +31,9 @@ jobs: needs: version-bump steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@1.98.0 + - uses: dtolnay/rust-toolchain@6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772 with: + toolchain: 1.98.0 components: clippy, rustfmt - name: Configure shared Cargo cache run: | @@ -123,7 +124,9 @@ jobs: contents: write steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@1.98.0 + - uses: dtolnay/rust-toolchain@6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772 + with: + toolchain: 1.98.0 - name: Configure shared Cargo cache run: | # The runner image pre-sets CARGO_HOME=$HOME/.cargo; drop the diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index bec32a3..2c7594f 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -15,7 +15,9 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@1.98.0 + - uses: dtolnay/rust-toolchain@6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772 + with: + toolchain: 1.98.0 - name: Verify version and tag match upstream run: scripts/verify_upstream_version.sh "$GITHUB_REF_NAME" - name: Configure shared Cargo cache From c2d2e43287b543b90d15641330f2d8d9d2f1f368 Mon Sep 17 00:00:00 2001 From: Jay Rodgers Date: Fri, 28 Aug 2026 23:57:04 +0100 Subject: [PATCH 3/3] security: harden CI and release trust boundaries --- .github/workflows/ci.yml | 133 ++++++++++++++++++++--------- .github/workflows/publish.yml | 39 +++++---- CONTRIBUTING.md | 18 +--- scripts/test-release-guards.sh | 98 +++++++++++++++++++++ scripts/verify_upstream_version.sh | 9 +- 5 files changed, 219 insertions(+), 78 deletions(-) create mode 100755 scripts/test-release-guards.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b526ea8..ba56ab8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,9 +20,10 @@ jobs: name: Version Change Required runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 with: fetch-depth: 0 + persist-credentials: false - name: Verify version matches upstream run: scripts/verify_upstream_version.sh gate: @@ -30,7 +31,9 @@ jobs: runs-on: ubuntu-latest needs: version-bump steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + with: + persist-credentials: false - uses: dtolnay/rust-toolchain@6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772 with: toolchain: 1.98.0 @@ -52,49 +55,56 @@ jobs: } >> "$GITHUB_ENV" - name: Test shared Cargo cache env run: scripts/test-cargo-env.sh - - uses: actions/setup-go@v5 + - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 with: go-version: 'stable' - name: Fetch upstream Go source (pinned) run: | - git clone --quiet https://github.com/charmbracelet/bubbles.git upstream-go - cd upstream-go && git checkout --quiet v2.1.0 + git clone --quiet --no-tags https://github.com/charmbracelet/bubbles.git upstream-go + cd upstream-go + git checkout --quiet f1daacfa0cfee07e31a12498078426d275aa5286 - name: Fetch sibling rusty-bubbletea (pinned) - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 with: repository: coderbants/rusty-bubbletea path: siblings/rusty-bubbletea - ref: dev + ref: df1c3b83ae4fe61b57de8194b487732db342468e + persist-credentials: false - name: Fetch sibling rusty-colorprofile (pinned) - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 with: repository: coderbants/rusty-colorprofile path: siblings/rusty-colorprofile - ref: dev + ref: 2d505e7821f72f133c17d6cc7afd314e03eddb66 + persist-credentials: false - name: Fetch sibling rusty-lipgloss (pinned) - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 with: repository: coderbants/rusty-lipgloss path: siblings/rusty-lipgloss - ref: dev + ref: 685b25da2af226b6ab2b9ce70f149eb9e4fb54ee + persist-credentials: false - name: Fetch sibling rusty-testkit (pinned) - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 with: repository: coderbants/rusty-testkit path: siblings/rusty-testkit - ref: dev + ref: 163cf16399662da7bf2b17fb6637b466c6402fb2 + persist-credentials: false - name: Fetch sibling rusty-ultraviolet (pinned) - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 with: repository: coderbants/rusty-ultraviolet path: siblings/rusty-ultraviolet - ref: dev + ref: 934257fbfc39cb4e42eb3f88c5e32ab4c32753b0 + persist-credentials: false - name: Fetch sibling rusty-x-ansi (pinned) - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 with: repository: coderbants/rusty-x-ansi path: siblings/rusty-x-ansi - ref: dev + ref: 971128e8566ad37991cd9cc50ff4f4c8a4b09c29 + persist-credentials: false - name: Place sibling crates run: | mkdir -p ../siblings && true @@ -116,14 +126,16 @@ jobs: run: cargo test --all-targets - name: verify_mapping run: ./scripts/verify_mapping.sh + - name: test_release_guards + run: ./scripts/test-release-guards.sh coverage: name: Coverage runs-on: ubuntu-latest needs: gate - permissions: - contents: write steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + with: + persist-credentials: false - uses: dtolnay/rust-toolchain@6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772 with: toolchain: 1.98.0 @@ -142,41 +154,47 @@ jobs: echo "CARGO_INSTALL_ROOT=$CARGO_INSTALL_ROOT" } >> "$GITHUB_ENV" - name: Fetch sibling rusty-bubbletea (pinned) - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 with: repository: coderbants/rusty-bubbletea path: siblings/rusty-bubbletea - ref: dev + ref: df1c3b83ae4fe61b57de8194b487732db342468e + persist-credentials: false - name: Fetch sibling rusty-colorprofile (pinned) - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 with: repository: coderbants/rusty-colorprofile path: siblings/rusty-colorprofile - ref: dev + ref: 2d505e7821f72f133c17d6cc7afd314e03eddb66 + persist-credentials: false - name: Fetch sibling rusty-lipgloss (pinned) - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 with: repository: coderbants/rusty-lipgloss path: siblings/rusty-lipgloss - ref: dev + ref: 685b25da2af226b6ab2b9ce70f149eb9e4fb54ee + persist-credentials: false - name: Fetch sibling rusty-testkit (pinned) - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 with: repository: coderbants/rusty-testkit path: siblings/rusty-testkit - ref: dev + ref: 163cf16399662da7bf2b17fb6637b466c6402fb2 + persist-credentials: false - name: Fetch sibling rusty-ultraviolet (pinned) - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 with: repository: coderbants/rusty-ultraviolet path: siblings/rusty-ultraviolet - ref: dev + ref: 934257fbfc39cb4e42eb3f88c5e32ab4c32753b0 + persist-credentials: false - name: Fetch sibling rusty-x-ansi (pinned) - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 with: repository: coderbants/rusty-x-ansi path: siblings/rusty-x-ansi - ref: dev + ref: 971128e8566ad37991cd9cc50ff4f4c8a4b09c29 + persist-credentials: false - name: Place sibling crates run: | mkdir -p ../siblings && true @@ -187,12 +205,39 @@ jobs: mv siblings/rusty-ultraviolet ../rusty-ultraviolet mv siblings/rusty-x-ansi ../rusty-x-ansi - name: Install cargo-tarpaulin - uses: taiki-e/install-action@cargo-tarpaulin + uses: taiki-e/install-action@94f54106ddd448427843411da73d6b8a35ae69b3 - name: Generate code coverage run: | + set -o pipefail cargo tarpaulin --all-features --out xml --output-dir target/tarpaulin -- --test-threads=1 2>&1 | tee /tmp/tarpaulin.log + - name: Upload coverage report + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 + with: + name: coverage-report + path: target/tarpaulin/cobertura.xml + if-no-files-found: error + retention-days: 1 + coverage-badge: + name: Update coverage badge + runs-on: ubuntu-latest + needs: coverage + if: github.event_name == 'push' && github.ref == 'refs/heads/dev' + permissions: + contents: write + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + with: + ref: dev + persist-credentials: false + - name: Download coverage report + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 + with: + name: coverage-report + path: target/tarpaulin - name: Write coverage badge in place + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | raw="$(grep -oE 'line-rate="[0-9.]+' target/tarpaulin/cobertura.xml | head -1 | grep -oE '[0-9.]+')" pct="$(awk -v r="$raw" 'BEGIN { printf "%.1f", r * 100 }')" @@ -207,13 +252,17 @@ jobs: *) color="red" ;; esac curl -sS "https://img.shields.io/badge/coverage-${pct}%25-${color}.svg" -o coverage.svg - # Commit the badge in place only when it changed (git diff ignores - # untracked files, so use status), so the push cannot re-trigger CI - # indefinitely. - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - if [ -n "$(git status --porcelain -- coverage.svg)" ]; then - git add coverage.svg - git commit -q -m "docs: update coverage badge ($pct%)" - git push -q origin dev + content="$(base64 -w 0 coverage.svg)" + existing_sha="$(gh api "repos/${GITHUB_REPOSITORY}/contents/coverage.svg?ref=dev" --jq .sha 2>/dev/null || true)" + if [ -n "$existing_sha" ]; then + gh api --method PUT "repos/${GITHUB_REPOSITORY}/contents/coverage.svg" \ + --field message="docs: update coverage badge ($pct%)" \ + --field content="$content" \ + --field branch=dev \ + --field sha="$existing_sha" + else + gh api --method PUT "repos/${GITHUB_REPOSITORY}/contents/coverage.svg" \ + --field message="docs: update coverage badge ($pct%)" \ + --field content="$content" \ + --field branch=dev fi diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 2c7594f..6e3bd97 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -4,7 +4,6 @@ on: push: tags: - 'v*' - workflow_dispatch: permissions: contents: write @@ -14,10 +13,13 @@ jobs: name: Publish Crate runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + with: + persist-credentials: false - uses: dtolnay/rust-toolchain@6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772 with: toolchain: 1.98.0 + components: clippy, rustfmt - name: Verify version and tag match upstream run: scripts/verify_upstream_version.sh "$GITHUB_REF_NAME" - name: Configure shared Cargo cache @@ -38,41 +40,47 @@ jobs: - name: Test shared Cargo cache env run: scripts/test-cargo-env.sh - name: Fetch sibling rusty-bubbletea (pinned) - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 with: repository: coderbants/rusty-bubbletea path: siblings/rusty-bubbletea - ref: dev + ref: df1c3b83ae4fe61b57de8194b487732db342468e + persist-credentials: false - name: Fetch sibling rusty-colorprofile (pinned) - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 with: repository: coderbants/rusty-colorprofile path: siblings/rusty-colorprofile - ref: dev + ref: 2d505e7821f72f133c17d6cc7afd314e03eddb66 + persist-credentials: false - name: Fetch sibling rusty-lipgloss (pinned) - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 with: repository: coderbants/rusty-lipgloss path: siblings/rusty-lipgloss - ref: dev + ref: 685b25da2af226b6ab2b9ce70f149eb9e4fb54ee + persist-credentials: false - name: Fetch sibling rusty-testkit (pinned) - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 with: repository: coderbants/rusty-testkit path: siblings/rusty-testkit - ref: dev + ref: 163cf16399662da7bf2b17fb6637b466c6402fb2 + persist-credentials: false - name: Fetch sibling rusty-ultraviolet (pinned) - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 with: repository: coderbants/rusty-ultraviolet path: siblings/rusty-ultraviolet - ref: dev + ref: 934257fbfc39cb4e42eb3f88c5e32ab4c32753b0 + persist-credentials: false - name: Fetch sibling rusty-x-ansi (pinned) - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 with: repository: coderbants/rusty-x-ansi path: siblings/rusty-x-ansi - ref: dev + ref: 971128e8566ad37991cd9cc50ff4f4c8a4b09c29 + persist-credentials: false - name: Place sibling crates run: | mkdir -p ../siblings && true @@ -118,6 +126,7 @@ jobs: git archive --format=tar.gz -o "$RUNNER_TEMP/$src" HEAD gh release upload "$GITHUB_REF_NAME" "$RUNNER_TEMP/$src" --clobber - name: Publish to crates.io + if: startsWith(github.ref, 'refs/tags/v') env: CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} run: | @@ -133,7 +142,7 @@ jobs: # registry, but ultraviolet's dev-dependency on lipgloss and # lipgloss's dependency on ultraviolet form a cycle; the full # gates (build, clippy, tests) already ran above. - cargo publish --token "${CARGO_REGISTRY_TOKEN}" --no-verify --allow-dirty + cargo publish --no-verify --allow-dirty git checkout -- Cargo.toml else echo "No CARGO_REGISTRY_TOKEN secret; skipping crates.io publish." diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 86a3c20..7bae725 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -56,6 +56,7 @@ cargo test --all-targets ```sh cargo test --all-targets + ./scripts/test-release-guards.sh cargo doc --no-deps # rustdoc coverage ``` @@ -74,20 +75,9 @@ cargo test --all-targets ## Versioning -Every release that matches an upstream version uses the upstream `MAJOR.MINOR.PATCH` plus a -fourth dot-separated iteration number that internally tracks which deployed release of this -port it is for that upstream version: - -- `v2.1.0.0` — first port release of upstream `v2.1.0` -- `v2.1.0.1` — a hotfix iteration for `v2.1.0` (bug fix released without an upstream - version bump) - -The iteration increments whenever we publish a new release of our port without an upstream -version bump (e.g. a bug fix that upstream has not yet released). The git tag and GitHub -release carry the full four-part version (`v2.1.0.1`). `Cargo.toml` keeps the upstream -`X.Y.Z` (`2.1.0`), since crates.io only accepts `MAJOR.MINOR.PATCH`; iteration hotfixes -publish under the same `X.Y.Z` on crates.io, replacing the previous deployment (iterations -are only used for bug fixes, so the contents differ only in fixes). +Every release uses the tracked upstream version exactly. There is no fourth-part iteration or +republishing of an existing crates.io version; wait for an upstream version change before +creating another release tag. ## Contribution guidelines diff --git a/scripts/test-release-guards.sh b/scripts/test-release-guards.sh new file mode 100755 index 0000000..fa50117 --- /dev/null +++ b/scripts/test-release-guards.sh @@ -0,0 +1,98 @@ +#!/usr/bin/env bash + +# Regression checks for the release and CI trust boundaries. These checks are +# intentionally static and fast so every gate can prove that workflow changes +# did not reintroduce mutable dependencies or accidental write access. Keep +# the implementation on GitHub-hosted runner core tools; ripgrep is not +# guaranteed to be installed there. + +set -euo pipefail + +cd "$(dirname "$0")/.." + +fail=0 + +report() { + printf 'ERROR: %s\n' "$1" >&2 + fail=1 +} + +check_pinned_action() { + local action="$1" + local line + local ref + + while IFS= read -r line; do + ref="${line##*@}" + if [[ ! "${ref}" =~ ^[0-9a-f]{40}$ ]]; then + report "${action} must use a full immutable commit SHA: ${line}" + fi + done < <(grep -nE "uses: ${action}@" .github/workflows/*.yml) +} + +check_pinned_action "actions/checkout" +check_pinned_action "actions/setup-go" +check_pinned_action "taiki-e/install-action" + +if grep -n 'workflow_dispatch' .github/workflows/publish.yml >/dev/null; then + report "publish workflow must not expose a manual dispatch path" +fi + +if awk ' + /repository: coderbants\/rusty-/ { sibling=1; next } + sibling && /ref: dev/ { bad=1 } + sibling && /^ - name:/ { sibling=0 } + END { exit bad ? 0 : 1 } +' .github/workflows/ci.yml .github/workflows/publish.yml; then + report "sibling dependency checkouts must use immutable commit refs" +fi + +if ! grep -n 'git clone --quiet --no-tags' .github/workflows/ci.yml >/dev/null; then + report "the upstream checkout must suppress mutable tag discovery" +fi + +if ! grep -nE 'git checkout --quiet [0-9a-f]{40}' .github/workflows/ci.yml >/dev/null; then + report "the upstream checkout must use an immutable commit" +fi + +coverage_job="$(awk ' + /^ coverage:/ { in_coverage=1 } + in_coverage && /^ [A-Za-z0-9_-]+:/ && $0 !~ /^ coverage:/ { exit } + in_coverage { print } +' .github/workflows/ci.yml)" + +case "${coverage_job}" in + *"contents: write"*) + report "coverage must not have repository write permission while running tests" + ;; +esac + +if ! grep -n 'needs: coverage' .github/workflows/ci.yml >/dev/null; then + report "coverage badge publication must depend on the read-only coverage job" +fi + +if ! grep -nE 'uses: actions/(upload|download)-artifact@[0-9a-f]{40}' .github/workflows/ci.yml >/dev/null; then + report "coverage must exchange its report through immutable artifact actions" +fi + +if grep -nE 'x-access-token:|git (remote set-url|push).*(GH_TOKEN|\$\{GH_TOKEN\})|cargo publish.*--token' .github/workflows/ci.yml .github/workflows/publish.yml >/dev/null; then + report "workflow credentials must not be embedded in URLs or command-line arguments" +fi + +if ! grep -n 'gh api --method PUT' .github/workflows/ci.yml >/dev/null; then + report "coverage badge updates must use the GitHub API credential channel" +fi + +if ! scripts/verify_upstream_version.sh >/dev/null; then + report "the tracked upstream version must pass the release-version guard" +fi + +if scripts/verify_upstream_version.sh not-a-release-tag >/dev/null 2>&1; then + report "the release-version guard must reject non-v tags" +fi + +if [ "${fail}" -ne 0 ]; then + exit 1 +fi + +echo "OK: release and CI trust-boundary guards pass" diff --git a/scripts/verify_upstream_version.sh b/scripts/verify_upstream_version.sh index f26dc14..2b2ffae 100755 --- a/scripts/verify_upstream_version.sh +++ b/scripts/verify_upstream_version.sh @@ -23,12 +23,6 @@ crate_version="$(grep -m1 '^version' Cargo.toml | sed 's/.*"\(.*\)".*/\1/')" upstream="$(grep -m1 'Upstream Target Tag / Version:' src/lib.rs | sed -n 's/.*`\([^`]*\)`.*/\1/p' | tr -d ' ')" if [ -z "${upstream}" ]; then - if [ ! -d upstream-go ]; then - # No tracked upstream at all (e.g. the original test harness): the - # upstream-mirror policy does not apply. - echo "OK: no upstream tracked (original crate); version policy not applicable" - exit 0 - fi echo "ERROR: could not read the tracked upstream version from src/lib.rs" >&2 exit 1 fi @@ -55,7 +49,8 @@ if [ "$#" -ge 1 ]; then fi ;; *) - echo "WARN: '${tag}' is not a v* tag; skipping the tag check (crate version was still verified)." >&2 + echo "ERROR: release tag '${tag}' is not a v* tag." >&2 + fail=1 ;; esac fi