Skip to content

chore(release): v3.31.0 (#1755) #231

chore(release): v3.31.0 (#1755)

chore(release): v3.31.0 (#1755) #231

Workflow file for this run

# .github/workflows/release.yml
# Cross-platform binary releases for Assay
#
# Triggers on:
# - Push to tags matching v*
# - Manual dispatch
#
# Builds for:
# - Linux x86_64 (GNU)
# - Linux aarch64 (GNU)
# - macOS x86_64 (Intel, cross from ARM)
# - macOS aarch64 (Apple Silicon)
# - Windows x86_64
name: Release
on:
push:
tags:
- 'v*'
workflow_dispatch:
inputs:
version:
description: 'Version tag (e.g., v1.1.0)'
required: true
type: string
verify_lsm:
description: 'Run self-hosted LSM enforcement gate'
required: false
default: false
type: boolean
# Default deny; each job declares minimal permissions.
permissions: {}
env:
CARGO_TERM_COLOR: always
CARGO_NET_RETRY: 5
RUST_BACKTRACE: 1
jobs:
# ============================================================
# Build matrix for all platforms
# ============================================================
build:
name: Build ${{ matrix.target }}
runs-on: ${{ matrix.os }}
permissions:
contents: read
strategy:
fail-fast: false
matrix:
include:
# Linux x86_64
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
artifact: assay
archive: tar.gz
# Linux aarch64 (cross-compile)
- os: ubuntu-latest
target: aarch64-unknown-linux-gnu
artifact: assay
archive: tar.gz
cross: true
# macOS x86_64 (cross-compile on ARM runner)
- os: macos-15
target: x86_64-apple-darwin
artifact: assay
archive: tar.gz
# macOS aarch64 (Apple Silicon)
- os: macos-15
target: aarch64-apple-darwin
artifact: assay
archive: tar.gz
# Windows x86_64
- os: windows-2025
target: x86_64-pc-windows-msvc
artifact: assay.exe
archive: zip
steps:
- name: Checkout
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
with:
persist-credentials: false
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
with:
targets: ${{ matrix.target }}
- name: Ensure Rust target is installed
shell: bash
run: rustup target add "${{ matrix.target }}"
- name: Install cross-compilation tools
if: matrix.cross
shell: bash
run: |
set -euo pipefail
# Ensure python3 is available (for Deb822 rewrite) and update strictly for current arch first
command -v python3 >/dev/null 2>&1 || {
sudo DEBIAN_FRONTEND=noninteractive apt-get update -y
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends python3
}
sudo dpkg --add-architecture arm64
. /etc/os-release
CODENAME="${VERSION_CODENAME:-noble}"
# 1) Force amd64-only for default Ubuntu sources so apt doesn't try arm64 on archive/security
if [ -f /etc/apt/sources.list.d/ubuntu.sources ]; then
sudo python3 scripts/ci/fix_apt_sources.py
fi
if [ -f /etc/apt/sources.list ]; then
# If a line already has an arch bracket, leave it alone.
# Otherwise, force [arch=amd64] for any ubuntu.com/ubuntu repo (incl azure.archive.ubuntu.com).
sudo sed -i -E '
/^deb(-src)?[[:space:]]+\[.*\][[:space:]]+/b
s|^(deb(-src)?[[:space:]]+)(http(s)?://[^[:space:]]*ubuntu\.com/ubuntu)|\1[arch=amd64] \3|
' /etc/apt/sources.list
fi
# 2) Add arm64-only ubuntu-ports repo (arm64 packages live here)
sudo tee /etc/apt/sources.list.d/ubuntu-ports-arm64.list >/dev/null <<EOF
deb [arch=arm64] https://mirror.gofoss.xyz/ubuntu-ports ${CODENAME} main universe restricted multiverse
deb [arch=arm64] https://mirror.gofoss.xyz/ubuntu-ports ${CODENAME}-updates main universe restricted multiverse
deb [arch=arm64] https://mirror.gofoss.xyz/ubuntu-ports ${CODENAME}-security main universe restricted multiverse
deb [arch=arm64] https://mirror.gofoss.xyz/ubuntu-ports ${CODENAME}-backports main universe restricted multiverse
EOF
# 3) Rewrite any */ubuntu-ports to the chosen mirror + robust update
sudo bash scripts/ci/apt_ports_failover.sh
# Toolchain + sysroot (no arm64 libs needed - using bundled sqlite)
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
gcc-aarch64-linux-gnu \
libc6-dev-arm64-cross \
linux-libc-dev-arm64-cross \
pkg-config
{
echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc"
echo "CC_aarch64_unknown_linux_gnu=aarch64-linux-gnu-gcc"
echo "AR_aarch64_unknown_linux_gnu=aarch64-linux-gnu-ar"
} >> "$GITHUB_ENV"
- name: Build release binary
run: cargo build --release --target ${{ matrix.target }} --package assay-cli
- name: Get version
id: version
shell: bash
env:
EVENT_NAME: ${{ github.event_name }}
RELEASE_VERSION_INPUT: ${{ github.event.inputs.version }}
run: |
if [ "${EVENT_NAME}" = "workflow_dispatch" ]; then
V="${RELEASE_VERSION_INPUT}"
else
V="${GITHUB_REF#refs/tags/}"
fi
if [[ "$V" == *$'\n'* || "$V" == *$'\r'* ]]; then
echo "::error::Release version must be a single-line value."
exit 1
fi
printf 'version=%s\n' "$V" >> "$GITHUB_OUTPUT"
- name: Package (Unix)
if: matrix.archive == 'tar.gz'
shell: bash
env:
VERSION: ${{ steps.version.outputs.version }}
run: |
ARCHIVE_NAME="assay-${VERSION}-${{ matrix.target }}"
mkdir -p "dist/${ARCHIVE_NAME}"
cp "target/${{ matrix.target }}/release/${{ matrix.artifact }}" "dist/${ARCHIVE_NAME}/"
cp README.md LICENSE "dist/${ARCHIVE_NAME}/" 2>/dev/null || true
cd dist
tar -czvf "${ARCHIVE_NAME}.tar.gz" "${ARCHIVE_NAME}"
shasum -a 256 "${ARCHIVE_NAME}.tar.gz" > "${ARCHIVE_NAME}.tar.gz.sha256"
- name: Package (Windows)
if: matrix.archive == 'zip'
shell: pwsh
env:
VERSION: ${{ steps.version.outputs.version }}
run: |
$VERSION = $env:VERSION
$ARCHIVE_NAME = "assay-${VERSION}-${{ matrix.target }}"
New-Item -ItemType Directory -Force -Path "dist\${ARCHIVE_NAME}"
Copy-Item "target\${{ matrix.target }}\release\${{ matrix.artifact }}" "dist\${ARCHIVE_NAME}\"
Copy-Item README.md, LICENSE "dist\${ARCHIVE_NAME}\" -ErrorAction SilentlyContinue
Compress-Archive -Path "dist\${ARCHIVE_NAME}" -DestinationPath "dist\${ARCHIVE_NAME}.zip"
$hash = (Get-FileHash "dist\${ARCHIVE_NAME}.zip" -Algorithm SHA256).Hash.ToLower()
"${hash} ${ARCHIVE_NAME}.zip" | Out-File -Encoding ASCII "dist\${ARCHIVE_NAME}.zip.sha256"
- name: Upload artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: assay-${{ matrix.target }}
path: dist/assay-*
retention-days: 7
build-mcp-server-linux:
name: Build assay-mcp-server ${{ matrix.target }}
runs-on: ubuntu-latest
permissions:
contents: read
strategy:
fail-fast: false
matrix:
include:
- target: x86_64-unknown-linux-gnu
- target: aarch64-unknown-linux-gnu
cross: true
steps:
- name: Checkout
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
with:
persist-credentials: false
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
with:
targets: ${{ matrix.target }}
- name: Ensure Rust target is installed
shell: bash
run: rustup target add "${{ matrix.target }}"
- name: Install cross-compilation tools
if: matrix.cross
shell: bash
run: |
set -euo pipefail
command -v python3 >/dev/null 2>&1 || {
sudo DEBIAN_FRONTEND=noninteractive apt-get update -y
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends python3
}
sudo dpkg --add-architecture arm64
. /etc/os-release
CODENAME="${VERSION_CODENAME:-noble}"
if [ -f /etc/apt/sources.list.d/ubuntu.sources ]; then
sudo python3 scripts/ci/fix_apt_sources.py
fi
if [ -f /etc/apt/sources.list ]; then
sudo sed -i -E '
/^deb(-src)?[[:space:]]+\[.*\][[:space:]]+/b
s|^(deb(-src)?[[:space:]]+)(http(s)?://[^[:space:]]*ubuntu\.com/ubuntu)|\1[arch=amd64] \3|
' /etc/apt/sources.list
fi
sudo tee /etc/apt/sources.list.d/ubuntu-ports-arm64.list >/dev/null <<EOF
deb [arch=arm64] https://mirror.gofoss.xyz/ubuntu-ports ${CODENAME} main universe restricted multiverse
deb [arch=arm64] https://mirror.gofoss.xyz/ubuntu-ports ${CODENAME}-updates main universe restricted multiverse
deb [arch=arm64] https://mirror.gofoss.xyz/ubuntu-ports ${CODENAME}-security main universe restricted multiverse
deb [arch=arm64] https://mirror.gofoss.xyz/ubuntu-ports ${CODENAME}-backports main universe restricted multiverse
EOF
sudo bash scripts/ci/apt_ports_failover.sh
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
gcc-aarch64-linux-gnu \
libc6-dev-arm64-cross \
linux-libc-dev-arm64-cross \
pkg-config
{
echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc"
echo "CC_aarch64_unknown_linux_gnu=aarch64-linux-gnu-gcc"
echo "AR_aarch64_unknown_linux_gnu=aarch64-linux-gnu-ar"
} >> "$GITHUB_ENV"
- name: Build assay-mcp-server binary
run: cargo build --release --target ${{ matrix.target }} --package assay-mcp-server
- name: Get version
id: version
shell: bash
env:
EVENT_NAME: ${{ github.event_name }}
RELEASE_VERSION_INPUT: ${{ github.event.inputs.version }}
run: |
if [ "${EVENT_NAME}" = "workflow_dispatch" ]; then
V="${RELEASE_VERSION_INPUT}"
else
V="${GITHUB_REF#refs/tags/}"
fi
if [[ "$V" == *$'\n'* || "$V" == *$'\r'* ]]; then
echo "::error::Release version must be a single-line value."
exit 1
fi
printf 'version=%s\n' "$V" >> "$GITHUB_OUTPUT"
- name: Package assay-mcp-server
shell: bash
env:
VERSION: ${{ steps.version.outputs.version }}
run: |
ARCHIVE_NAME="assay-mcp-server-${VERSION}-${{ matrix.target }}"
mkdir -p "dist/${ARCHIVE_NAME}"
cp "target/${{ matrix.target }}/release/assay-mcp-server" "dist/${ARCHIVE_NAME}/"
cp README.md LICENSE "dist/${ARCHIVE_NAME}/" 2>/dev/null || true
cd dist
tar -czvf "${ARCHIVE_NAME}.tar.gz" "${ARCHIVE_NAME}"
shasum -a 256 "${ARCHIVE_NAME}.tar.gz" > "${ARCHIVE_NAME}.tar.gz.sha256"
- name: Upload assay-mcp-server artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: assay-mcp-server-${{ matrix.target }}
path: dist/assay-mcp-server-*
retention-days: 7
# ============================================================
# Create GitHub Release
# ============================================================
release:
name: Create Release
needs: [build, build-mcp-server-linux]
runs-on: ubuntu-latest
environment: release
permissions:
contents: write
actions: read
attestations: write
id-token: write
steps:
- name: Checkout
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
with:
persist-credentials: false
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
- name: Get version
id: version
shell: bash
env:
EVENT_NAME: ${{ github.event_name }}
RELEASE_VERSION_INPUT: ${{ github.event.inputs.version }}
run: |
if [ "${EVENT_NAME}" = "workflow_dispatch" ]; then
V="${RELEASE_VERSION_INPUT}"
else
V="${GITHUB_REF#refs/tags/}"
fi
if [[ "$V" == *$'\n'* || "$V" == *$'\r'* ]]; then
echo "::error::Release version must be a single-line value."
exit 1
fi
printf 'version=%s\n' "$V" >> "$GITHUB_OUTPUT"
- name: Install CycloneDX generator
shell: bash
run: cargo install cargo-cyclonedx --version 0.5.9 --locked
- name: Generate CycloneDX SBOM bundle
shell: bash
env:
VERSION: ${{ steps.version.outputs.version }}
run: |
set -euo pipefail
cargo cyclonedx \
--format json \
--spec-version 1.5 \
--target all \
--override-filename assay-sbom \
--manifest-path Cargo.toml
mkdir -p release/sbom
while IFS= read -r file; do
cp --parents "$file" release/sbom/
done < <(find . -name 'assay-sbom.json' -print)
tar -czf "release/assay-${VERSION}-sbom-cyclonedx.tar.gz" -C release/sbom .
shasum -a 256 "release/assay-${VERSION}-sbom-cyclonedx.tar.gz" > "release/assay-${VERSION}-sbom-cyclonedx.tar.gz.sha256"
rm -rf release/sbom
- name: Download all artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
path: artifacts
- name: Prepare release assets
run: |
mkdir -p release
find artifacts -type f \( -name "*.tar.gz" -o -name "*.zip" -o -name "*.sha256" \) -exec cp {} release/ \;
ls -la release/
- name: Build assay-mcp-server MCPB
shell: bash
env:
VERSION: ${{ steps.version.outputs.version }}
run: |
set -euo pipefail
bash scripts/ci/build_mcpb_bundle.sh \
--version-tag "${VERSION}" \
--linux-x86-archive "release/assay-mcp-server-${VERSION}-x86_64-unknown-linux-gnu.tar.gz" \
--linux-arm64-archive "release/assay-mcp-server-${VERSION}-aarch64-unknown-linux-gnu.tar.gz" \
--output "release/assay-mcp-server-${VERSION}-linux.mcpb"
- name: Render generated registry metadata
shell: bash
env:
VERSION: ${{ steps.version.outputs.version }}
REPO: ${{ github.repository }}
run: |
set -euo pipefail
MCPB_FILE="release/assay-mcp-server-${VERSION}-linux.mcpb"
MCPB_SHA="$(cut -d' ' -f1 "${MCPB_FILE}.sha256")"
MCPB_URL="https://github.com/${REPO}/releases/download/${VERSION}/$(basename "${MCPB_FILE}")"
bash scripts/ci/render_registry_server_json.sh \
--version-tag "${VERSION}" \
--mcpb-url "${MCPB_URL}" \
--file-sha256 "${MCPB_SHA}" \
--output "release/server.json"
- name: ADR-025 enforce readiness (fail-closed)
env:
GH_TOKEN: ${{ github.token }}
shell: bash
run: |
set -euo pipefail
mkdir -p input/readiness
rid="$(gh run list --workflow "adr025-nightly-readiness.yml" --branch main --status success --limit 1 --json databaseId --jq '.[0].databaseId')"
if [ -z "${rid}" ] || [ "${rid}" = "null" ]; then
echo "No successful adr025-nightly-readiness run found"
exit 2
fi
echo "Using readiness run id: ${rid}"
gh run download "${rid}" -n "adr025-nightly-readiness" -D input/readiness
test -f input/readiness/nightly_readiness.json
bash scripts/ci/adr025-soak-enforce.sh \
--policy schemas/soak_readiness_policy_v1.json \
--readiness input/readiness/nightly_readiness.json
- name: ADR-025 closure release integration
env:
GH_TOKEN: ${{ github.token }}
MODE: ${{ vars.ASSAY_CLOSURE_GATE || 'attach' }}
POLICY: schemas/closure_release_policy_v1.json
OUT_DIR: artifacts/adr025-closure
shell: bash
run: |
set -euo pipefail
bash scripts/ci/adr025-closure-release.sh
- name: Upload ADR-025 closure release evidence
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: adr025-closure-release-evidence
path: artifacts/adr025-closure/*
if-no-files-found: warn
retention-days: 14
- name: ADR-025 OTel release integration
env:
GH_TOKEN: ${{ github.token }}
MODE: ${{ vars.ASSAY_OTEL_GATE || 'attach' }}
POLICY: schemas/otel_release_policy_v1.json
OUT_DIR: artifacts/adr025-otel
shell: bash
run: |
set -euo pipefail
bash scripts/ci/adr025-otel-release.sh
- name: Upload ADR-025 OTel release evidence
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: adr025-otel-bridge-release-evidence
path: artifacts/adr025-otel/*
if-no-files-found: warn
retention-days: 14
- name: Generate build provenance attestations
uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0
with:
subject-path: release/*
- name: Enforce release attestation policy
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ASSETS_DIR: release
OUT_SUMMARY: release/assay-${{ steps.version.outputs.version }}-release-provenance.json
OUT_RAW_DIR: artifacts/release-provenance/raw
REPO: ${{ github.repository }}
SIGNER_WORKFLOW: ${{ github.repository }}/.github/workflows/release.yml
SOURCE_REF: ${{ github.ref }}
SOURCE_DIGEST: ${{ github.sha }}
shell: bash
run: |
set -euo pipefail
bash scripts/ci/release_attestation_enforce.sh
shasum -a 256 "${OUT_SUMMARY}" > "${OUT_SUMMARY}.sha256"
- name: Build release proof kit
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ASSETS_DIR: release
PROVENANCE_SUMMARY: release/assay-${{ steps.version.outputs.version }}-release-provenance.json
PROVENANCE_SUMMARY_SHA256: release/assay-${{ steps.version.outputs.version }}-release-provenance.json.sha256
OUT_ARCHIVE: release/assay-${{ steps.version.outputs.version }}-release-proof-kit.tar.gz
VERSION: ${{ steps.version.outputs.version }}
shell: bash
run: |
set -euo pipefail
bash scripts/ci/release_proof_kit_build.sh
shasum -a 256 "${OUT_ARCHIVE}" > "${OUT_ARCHIVE}.sha256"
- name: Check release asset preflight
env:
VERSION: ${{ steps.version.outputs.version }}
ASSETS_DIR: release
REPO: ${{ github.repository }}
shell: bash
run: |
set -euo pipefail
bash scripts/ci/check-release-assets.sh
- name: Upload release provenance evidence
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: release-provenance-evidence
path: artifacts/release-provenance/
if-no-files-found: warn
retention-days: 14
- name: Generate release notes
id: notes
env:
REPO: ${{ github.repository }}
VERSION: ${{ steps.version.outputs.version }}
shell: bash
run: |
set -euo pipefail
cat > release_notes.md << 'EOF'
## Assay __VERSION__
Deterministic policy enforcement, canonical evidence, and reviewable trust artifacts for AI agent systems.
### Installation
**One-liner (Linux/macOS):**
```bash
curl -fsSL https://getassay.dev/install.sh | sh
```
**GitHub Action:**
```yaml
- uses: Rul1an/assay-action@v3
with:
bundles: ".assay/evidence/*.tar.gz"
fail_on: error
```
**Manual download:**
Download the appropriate archive for your platform below.
### Checksums
Verify your download with the `.sha256` files.
### Release Notes
See [CHANGELOG.md](__CHANGELOG_URL__) for line-specific shipped features and trust-compiler scope.
### Changelog
See [CHANGELOG.md](__CHANGELOG_URL__) for details.
EOF
python3 - <<'PY'
import os
from pathlib import Path
notes = Path("release_notes.md")
text = notes.read_text()
text = text.replace("__VERSION__", os.environ["VERSION"])
text = text.replace(
"__CHANGELOG_URL__",
f"https://github.com/{os.environ['REPO']}/blob/main/CHANGELOG.md",
)
notes.write_text(text)
PY
- name: Create GitHub Release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VERSION: ${{ steps.version.outputs.version }}
shell: bash
run: |
set -euo pipefail
assets=()
while IFS= read -r -d '' asset; do
assets+=("$asset")
done < <(find release -maxdepth 1 -type f -print0 | sort -z)
if [[ ${#assets[@]} -eq 0 ]]; then
echo "::error::No release assets found in release/."
exit 1
fi
release_args=(
"$VERSION"
"${assets[@]}"
--title "Assay $VERSION"
--target "$GITHUB_SHA"
--notes-file release_notes.md
)
if [[ "$VERSION" == *-rc* || "$VERSION" == *-beta* ]]; then
release_args+=(--prerelease)
fi
gh release create "${release_args[@]}"
# ============================================================
# Verify LSM Enforcement (Self-Hosted Gate)
# ============================================================
verify-lsm-blocking:
name: Verify LSM Enforcement
needs: [build, build-mcp-server-linux]
# Manual check only, to prevent release blocking on runner availability
if: github.event_name == 'workflow_dispatch' && inputs.verify_lsm
runs-on: [self-hosted]
timeout-minutes: 10
permissions:
contents: read
steps:
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
with:
persist-credentials: false
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
- name: Verify LSM blocking (CI gate)
run: |
chmod +x scripts/verify_lsm_docker.sh
# Standard Mode + Enforcement: Will FAIL if BPF is missing.
# Pass PATH so root can find cargo
sudo -E env "PATH=$PATH" ./scripts/verify_lsm_docker.sh --enforce-lsm
- name: Fix Runner Permissions (Cleanup)
if: always()
run: |
echo "🧹 Cleaning up root-owned files..."
sudo chown -R "$(whoami)":"$(id -gn)" . || true
- name: Upload verification logs
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: lsm-verification-logs
path: /tmp/assay-lsm-verify/
# ============================================================
# Publish to crates.io (optional)
# ============================================================
publish-crates:
name: Publish to crates.io
needs: release
runs-on: ubuntu-latest
if: >-
(startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '-rc') && !contains(github.ref, '-beta')) ||
(github.event_name == 'workflow_dispatch' && startsWith(github.event.inputs.version, 'v') && !contains(github.event.inputs.version, '-rc') && !contains(github.event.inputs.version, '-beta'))
environment: crates
permissions:
id-token: write
contents: read
steps:
- name: Checkout
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
with:
persist-credentials: false
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
- name: Debug Cargo Version
run: cargo --version
- name: Check public crate policy
run: bash scripts/ci/check-public-crate-policy.sh
- name: Authenticate with crates.io
id: auth
uses: rust-lang/crates-io-auth-action@c6f97d42243bad5fab37ca0427f495c86d5b1a18 # v1.0.5
- name: Publish Crates (Idempotent)
run: |
chmod +x scripts/ci/publish_idempotent.sh
./scripts/ci/publish_idempotent.sh
env:
CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }}
# ============================================================
# Build and Publish Python Wheels (PyPI)
# ============================================================
wheels:
name: Build Wheels
runs-on: ${{ matrix.os }}
permissions:
contents: read
strategy:
matrix:
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
- os: macos-15
target: x86_64-apple-darwin
- os: macos-15
target: aarch64-apple-darwin
steps:
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
with:
persist-credentials: false
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: '3.12'
- name: Build wheels
uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1
with:
working-directory: assay-python-sdk
target: ${{ matrix.target }}
# Keep the interpreter explicit for all wheel targets. This is
# required inside the manylinux container and harmless on macOS,
# where setup-python provides the same Python version.
args: --release --out dist --locked -i python3.12 --compatibility pypi
sccache: 'false'
manylinux: auto
- name: Upload wheels
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: wheels-${{ matrix.os }}-${{ matrix.target }}
path: assay-python-sdk/dist/*.whl
publish-pypi:
name: Publish to PyPI
needs: [release, wheels]
runs-on: ubuntu-latest
if: >-
(startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '-rc') && !contains(github.ref, '-beta')) ||
(github.event_name == 'workflow_dispatch' && startsWith(github.event.inputs.version, 'v') && !contains(github.event.inputs.version, '-rc') && !contains(github.event.inputs.version, '-beta'))
environment: pypi
permissions:
id-token: write # Required for PyPI trusted publishing
steps:
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: wheels-*
merge-multiple: true
path: dist
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
with:
packages-dir: dist
skip-existing: true # Idempotent: skip already-published versions