name: Release
on:
workflow_dispatch:
inputs:
commit_sha:
description: Exact main-branch commit to publish
required: true
type: string
version:
description: Exact crate version to publish
required: true
type: string
permissions:
actions: read
contents: write
concurrency:
group: release-stable
cancel-in-progress: false
jobs:
standalone-candidate:
uses: ./.github/workflows/standalone-candidate.yml
with:
commit_sha: ${{ inputs.commit_sha }}
version: ${{ inputs.version }}
secrets: inherit
publish:
needs: standalone-candidate
runs-on: macos-latest
timeout-minutes: 90
env:
EXPECTED_SHA: ${{ inputs.commit_sha }}
EXPECTED_VERSION: ${{ inputs.version }}
EXPECTED_STANDALONE_CANDIDATE_RUN_ID: ${{ github.run_id }}
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 with:
ref: ${{ inputs.commit_sha }}
fetch-depth: 0
persist-credentials: false
- name: Verify immutable release identity
shell: bash
run: |
set -euo pipefail
actual_sha=$(git rev-parse HEAD)
test "$actual_sha" = "$EXPECTED_SHA"
git fetch origin main
git merge-base --is-ancestor "$actual_sha" origin/main
actual_version=$(sed -n 's/^version = "\([^"]*\)"/\1/p' Cargo.toml | head -1)
test "$actual_version" = "$EXPECTED_VERSION"
git diff --exit-code
- name: Require successful exact-SHA CI
shell: bash
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
successes=$(gh run list \
--commit "$EXPECTED_SHA" \
--workflow CI \
--json conclusion,event \
--jq '[.[] | select(.event == "push" and .conclusion == "success")] | length')
test "$successes" -ge 1
- name: Require exact signed standalone candidate
shell: bash
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
proof_root="$RUNNER_TEMP/standalone-candidate-proof"
scripts/verify_standalone_candidate.sh \
"$EXPECTED_STANDALONE_CANDIDATE_RUN_ID" \
"$EXPECTED_SHA" "$EXPECTED_VERSION" \
"$proof_root" "$GITHUB_ENV" Release
- name: Toolchain
uses: dtolnay/rust-toolchain@6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772 with:
toolchain: "1.88.0"
- name: cargo audit
shell: bash
run: |
cargo install cargo-audit --locked --version 0.22.2 --no-default-features
cargo audit
- name: Package exact source
shell: bash
run: |
set -euo pipefail
if [[ ${MLX_NATIVE_SKIP_METALLIB+x} ]]; then
echo "MLX_NATIVE_SKIP_METALLIB is forbidden for release builds" >&2
exit 1
fi
cargo package --locked --no-verify
crate="target/package/hf2q-${EXPECTED_VERSION}.crate"
test -s "$crate"
actual_crate_sha=$(shasum -a 256 "$crate" | awk '{print $1}')
test "$actual_crate_sha" = "$EXPECTED_CRATE_SHA256"
release_package_stage=$(mktemp -d \
"$RUNNER_TEMP/hf2q-release-package.XXXXXX")
/usr/bin/tar -xzf "$crate" -C "$release_package_stage"
package_root=$(cd \
"$release_package_stage/hf2q-${EXPECTED_VERSION}" && pwd -P)
packed_lock="$package_root/Cargo.lock"
bash scripts/verify_release_dependency_provenance.sh \
check-package-root "$package_root"
bash scripts/verify_release_dependency_provenance.sh verify \
"$DEPENDENCY_PROVENANCE_DIR" "$packed_lock"
echo "RELEASE_PACKAGE_ROOT=$package_root" >> "$GITHUB_ENV"
printf '%s %s\n' "$actual_crate_sha" "$crate" \
| tee "$RUNNER_TEMP/release-crate.sha256"
- name: Install and smoke-test packed artifact
shell: bash
run: |
set -euo pipefail
package_root=${RELEASE_PACKAGE_ROOT:?RELEASE_PACKAGE_ROOT is required}
(
cd "$package_root"
cargo check --locked --all-targets --all-features
cargo build --locked --bin hf2q --all-features
cargo test --locked --test completions --all-features
cargo test --locked --bin hf2q --all-features \
distribution::standalone:: -- --test-threads=1
cargo test --locked --bin hf2q --all-features \
setup:: -- --test-threads=1
bash -n scripts/verify_release_dependency_provenance.sh \
scripts/test_release_dependency_provenance_contract.sh \
scripts/verify_standalone_candidate.sh \
scripts/sign_notarize_standalone_release.sh \
scripts/test_standalone_release_signing_contract.sh \
scripts/test_standalone_installer.sh
bash scripts/test_release_dependency_provenance_contract.sh
HF2Q_BIN="$package_root/target/debug/hf2q" \
scripts/test_standalone_installer.sh
scripts/test_standalone_release_signing_contract.sh
)
install_root="$RUNNER_TEMP/hf2q-install"
cargo install \
--path "$package_root" \
--locked \
--root "$install_root"
completion_home="$RUNNER_TEMP/hf2q-cargo-completion-home"
mkdir -p "$completion_home"
test "$(HOME="$completion_home" SHELL=/bin/zsh \
"$install_root/bin/hf2q" --version)" = \
"hf2q $EXPECTED_VERSION"
HOME="$completion_home" SHELL=/bin/zsh \
"$install_root/bin/hf2q" --help >/dev/null
test -f "$completion_home/.local/share/bash-completion/completions/hf2q"
test -f "$completion_home/.local/share/zsh/site-functions/_hf2q"
test -f "$completion_home/.config/fish/completions/hf2q.fish"
test -f "$completion_home/.zshrc"
test -f "$completion_home/.local/state/hf2q/completion-ownership-v1.json"
- name: Prepare and locally prove standalone release assets
shell: bash
run: |
set -euo pipefail
standalone_stage=$(mktemp -d "$RUNNER_TEMP/hf2q-standalone-release.XXXXXX")
source_dir=${STANDALONE_RELEASE_DIR:?STANDALONE_RELEASE_DIR is required}
binary="$standalone_stage/hf2q-aarch64-apple-darwin"
cp "$source_dir/hf2q-aarch64-apple-darwin" "$binary"
chmod 0555 "$binary"
cp "$source_dir/hf2q-aarch64-apple-darwin.sha256" \
"$standalone_stage/hf2q-aarch64-apple-darwin.sha256"
cp "$source_dir/proof.json" \
"$standalone_stage/hf2q-aarch64-apple-darwin.proof.json"
cp "$source_dir/notary-log.json" \
"$standalone_stage/hf2q-aarch64-apple-darwin.notary-log.json"
test "$(shasum -a 256 "$binary" | awk '{print $1}')" = \
"$STANDALONE_BINARY_SHA256"
test "$(stat -f '%z' "$binary")" = "$STANDALONE_BINARY_SIZE"
test "$(/usr/bin/lipo -archs "$binary")" = arm64
test "$(/usr/bin/vtool -show-build "$binary" | awk '$1 == "minos" {print $2}')" = 14.0
/usr/bin/codesign --verify --strict --all-architectures "$binary"
/usr/bin/codesign --verify --strict --all-architectures \
--check-notarization --test-requirement '=notarized' "$binary"
test "$("$binary" --version)" = \
"hf2q $EXPECTED_VERSION"
scripts/render_standalone_release_record.sh \
"$standalone_stage/stable-aarch64-apple-darwin.json" \
"$EXPECTED_VERSION" "$STANDALONE_BINARY_SIZE" \
"$STANDALONE_BINARY_SHA256"
scripts/render_standalone_installer.sh \
scripts/install.sh.in "$standalone_stage/install.sh" \
"$EXPECTED_VERSION" "$STANDALONE_BINARY_SIZE" \
"$STANDALONE_BINARY_SHA256" "$STANDALONE_TEAM_ID" \
"$STANDALONE_IDENTIFIER"
proof_home=$(cd "$(mktemp -d "$RUNNER_TEMP/hf2q-standalone-install-home.XXXXXX")" && pwd -P)
proof_install="$proof_home/.local/bin"
proof_state="$proof_home/.hf2q"
curl -fsSL "file://$standalone_stage/install.sh" | \
HOME="$proof_home" \
SHELL=/bin/bash \
HF2Q_INSTALL_DIR="$proof_install" \
HF2Q_RELEASE_BASE_URL="file://$standalone_stage" \
HF2Q_INSTALL_TEST_MODE=1 \
PATH=/usr/bin:/bin:/usr/sbin:/sbin \
sh >/dev/null
test "$(shasum -a 256 "$proof_install/hf2q" | awk '{print $1}')" = \
"$STANDALONE_BINARY_SHA256"
test "$(HOME="$proof_home" SHELL=/bin/bash \
"$proof_install/hf2q" --version)" = \
"hf2q $EXPECTED_VERSION"
test -f "$proof_home/.local/share/bash-completion/completions/hf2q"
test -f "$proof_home/.local/share/zsh/site-functions/_hf2q"
test -f "$proof_home/.config/fish/completions/hf2q.fish"
test -f "$proof_home/.bashrc"
test -f "$proof_home/.profile"
test -f "$proof_home/.local/state/hf2q/completion-ownership-v1.json"
HOME="$proof_home" SHELL=/bin/bash \
"$proof_install/hf2q" --state-root "$proof_state" \
setup --accept-defaults >/dev/null
test -s "$proof_state/config.toml"
test "$(stat -f '%Lp' "$proof_state/config.toml")" = 600
grep -Fx 'kind = "hf2q.config"' "$proof_state/config.toml"
grep -Fx 'schema_version = 2' "$proof_state/config.toml"
grep -Fx 'package = "hf2q"' "$proof_state/config.toml"
setup_golden=${RELEASE_PACKAGE_ROOT:?RELEASE_PACKAGE_ROOT is required}/src/setup/testdata/config_v2.toml
cmp -s "$proof_state/config.toml" "$setup_golden"
setup_config_sha=$(shasum -a 256 "$proof_state/config.toml" | awk '{print $1}')
HOME="$proof_home" SHELL=/bin/bash \
"$proof_install/hf2q" --state-root "$proof_state" \
setup --accept-defaults >/dev/null
test "$(shasum -a 256 "$proof_state/config.toml" | awk '{print $1}')" = \
"$setup_config_sha"
mkdir -p "$proof_state/models"
printf 'converted model\n' > "$proof_state/models/model.gguf"
model_sha=$(shasum -a 256 "$proof_state/models/model.gguf" | awk '{print $1}')
HOME="$proof_home" SHELL=/bin/bash \
"$proof_install/hf2q" uninstall --yes >/dev/null
test ! -e "$proof_install/hf2q"
test ! -e "$proof_install/.hf2q-standalone.json"
test ! -e "$proof_install/.hf2q-standalone.lock"
test ! -e "$proof_install/.hf2q-previous"
test ! -e "$proof_home/.local/share/bash-completion/completions/hf2q"
test ! -e "$proof_home/.local/share/zsh/site-functions/_hf2q"
test ! -e "$proof_home/.config/fish/completions/hf2q.fish"
test ! -e "$proof_home/.bashrc"
test ! -e "$proof_home/.profile"
test ! -e "$proof_home/.local/state/hf2q/completion-ownership-v1.json"
test -f "$proof_state/config.toml"
test -f "$proof_state/models/model.gguf"
test "$(shasum -a 256 "$proof_state/config.toml" | awk '{print $1}')" = \
"$setup_config_sha"
test "$(shasum -a 256 "$proof_state/models/model.gguf" | awk '{print $1}')" = \
"$model_sha"
echo "STANDALONE_STAGE=$standalone_stage" >> "$GITHUB_ENV"
- name: Create draft release and attach exact assets once
shell: bash
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
tag="v${EXPECTED_VERSION}"
bash scripts/ensure_github_release_tag.sh \
"$GITHUB_REPOSITORY" "$tag" "$EXPECTED_SHA"
if ! gh release view "$tag" >/dev/null 2>&1; then
gh release create "$tag" \
--target "$EXPECTED_SHA" \
--title "hf2q ${EXPECTED_VERSION}" \
--generate-notes \
--draft \
--verify-tag
fi
release_json=$(gh release view "$tag" --json isDraft,targetCommitish)
test "$(jq -r .targetCommitish <<<"$release_json")" = "$EXPECTED_SHA"
is_draft=$(jq -r .isDraft <<<"$release_json")
ensure_exact_asset() {
local asset=$1
local name directory
name=$(basename "$asset")
directory=$(mktemp -d "$RUNNER_TEMP/hf2q-existing-asset.XXXXXX")
if gh release download "$tag" --pattern "$name" --dir "$directory" \
>/dev/null 2>&1; then
cmp -s "$asset" "$directory/$name" || {
echo "published release asset differs from candidate: $name" >&2
return 1
}
else
test "$is_draft" = true || {
echo "published release is missing immutable asset: $name" >&2
return 1
}
gh release upload "$tag" "$asset"
fi
}
for asset in \
"$STANDALONE_STAGE/hf2q-aarch64-apple-darwin" \
"$STANDALONE_STAGE/hf2q-aarch64-apple-darwin.sha256" \
"$STANDALONE_STAGE/hf2q-aarch64-apple-darwin.proof.json" \
"$STANDALONE_STAGE/hf2q-aarch64-apple-darwin.notary-log.json" \
"$STANDALONE_STAGE/stable-aarch64-apple-darwin.json" \
"$STANDALONE_STAGE/install.sh" \
"target/package/hf2q-${EXPECTED_VERSION}.crate" \
"$RUNNER_TEMP/release-crate.sha256"; do
ensure_exact_asset "$asset"
done
- name: Verify every draft release asset from a fresh download
shell: bash
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
tag="v${EXPECTED_VERSION}"
release_dir=$(mktemp -d "$RUNNER_TEMP/hf2q-draft-release.XXXXXX")
gh release download "$tag" --dir "$release_dir"
for asset in \
"$STANDALONE_STAGE/hf2q-aarch64-apple-darwin" \
"$STANDALONE_STAGE/hf2q-aarch64-apple-darwin.sha256" \
"$STANDALONE_STAGE/hf2q-aarch64-apple-darwin.proof.json" \
"$STANDALONE_STAGE/hf2q-aarch64-apple-darwin.notary-log.json" \
"$STANDALONE_STAGE/stable-aarch64-apple-darwin.json" \
"$STANDALONE_STAGE/install.sh" \
"target/package/hf2q-${EXPECTED_VERSION}.crate" \
"$RUNNER_TEMP/release-crate.sha256"; do
cmp -s "$asset" "$release_dir/$(basename "$asset")"
done
- name: Publish exact package
shell: bash
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
run: |
set -euo pipefail
test -n "$CARGO_REGISTRY_TOKEN"
published="$RUNNER_TEMP/hf2q-${EXPECTED_VERSION}-published.crate"
if curl --fail --location --silent --show-error \
"https://static.crates.io/crates/hf2q/hf2q-${EXPECTED_VERSION}.crate" \
--output "$published"; then
echo "hf2q ${EXPECTED_VERSION} is already published; verifying exact bytes"
else
# The exact package was already compiled and tested above. Avoid
# re-running dependency build scripts while the registry token is
# present in this narrowly scoped step.
cargo publish --locked --no-verify --token "$CARGO_REGISTRY_TOKEN"
fi
- name: Verify crates.io bytes
shell: bash
run: |
set -euo pipefail
expected=$(awk '{print $1}' "$RUNNER_TEMP/release-crate.sha256")
downloaded="$RUNNER_TEMP/hf2q-${EXPECTED_VERSION}.crate"
for _ in $(seq 1 24); do
if curl --fail --location --silent --show-error \
"https://static.crates.io/crates/hf2q/hf2q-${EXPECTED_VERSION}.crate" \
--output "$downloaded"; then
actual=$(shasum -a 256 "$downloaded" | awk '{print $1}')
test "$actual" = "$expected"
exit 0
fi
sleep 5
done
exit 1
- name: Install and smoke-test crates.io artifact
shell: bash
run: |
set -euo pipefail
downloaded="$RUNNER_TEMP/hf2q-${EXPECTED_VERSION}.crate"
registry_root="$RUNNER_TEMP/hf2q-registry"
install_root="$RUNNER_TEMP/hf2q-registry-install"
mkdir -p "$registry_root"
tar -xzf "$downloaded" -C "$registry_root"
cargo install \
--path "$registry_root/hf2q-${EXPECTED_VERSION}" \
--locked \
--root "$install_root"
completion_home="$RUNNER_TEMP/hf2q-registry-completion-home"
mkdir -p "$completion_home"
HOME="$completion_home" SHELL=/bin/zsh \
"$install_root/bin/hf2q" --help >/dev/null
test -f "$completion_home/.local/share/bash-completion/completions/hf2q"
test -f "$completion_home/.local/share/zsh/site-functions/_hf2q"
test -f "$completion_home/.config/fish/completions/hf2q.fish"
- name: Publish the complete GitHub release
shell: bash
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
tag="v${EXPECTED_VERSION}"
release_json=$(gh release view "$tag" --json isDraft,targetCommitish)
test "$(jq -r .targetCommitish <<<"$release_json")" = "$EXPECTED_SHA"
if [[ $(jq -r .isDraft <<<"$release_json") == true ]]; then
gh release edit "$tag" --draft=false
fi
release_json=$(gh release view "$tag" --json isDraft,targetCommitish)
test "$(jq -r .targetCommitish <<<"$release_json")" = "$EXPECTED_SHA"
test "$(jq -r .isDraft <<<"$release_json")" = false
- name: Verify public GitHub release bytes and clean-prefix install
shell: bash
run: |
set -euo pipefail
tag="v${EXPECTED_VERSION}"
public_base="https://github.com/$GITHUB_REPOSITORY/releases/download/$tag"
release_dir=$(mktemp -d "$RUNNER_TEMP/hf2q-public-release.XXXXXX")
for name in \
hf2q-aarch64-apple-darwin \
hf2q-aarch64-apple-darwin.sha256 \
hf2q-aarch64-apple-darwin.proof.json \
hf2q-aarch64-apple-darwin.notary-log.json \
stable-aarch64-apple-darwin.json \
install.sh \
"hf2q-${EXPECTED_VERSION}.crate" \
release-crate.sha256; do
downloaded=0
for _ in $(seq 1 24); do
if curl --fail --location --silent --show-error \
"$public_base/$name" --output "$release_dir/$name"; then
downloaded=1
break
fi
sleep 5
done
test "$downloaded" = 1
done
for asset in \
"$STANDALONE_STAGE/hf2q-aarch64-apple-darwin" \
"$STANDALONE_STAGE/hf2q-aarch64-apple-darwin.sha256" \
"$STANDALONE_STAGE/hf2q-aarch64-apple-darwin.proof.json" \
"$STANDALONE_STAGE/hf2q-aarch64-apple-darwin.notary-log.json" \
"$STANDALONE_STAGE/stable-aarch64-apple-darwin.json" \
"$STANDALONE_STAGE/install.sh" \
"target/package/hf2q-${EXPECTED_VERSION}.crate" \
"$RUNNER_TEMP/release-crate.sha256"; do
cmp -s "$asset" "$release_dir/$(basename "$asset")"
done
public_binary="$release_dir/hf2q-aarch64-apple-darwin"
chmod 0555 "$public_binary"
test "$(shasum -a 256 "$public_binary" | awk '{print $1}')" = \
"$STANDALONE_BINARY_SHA256"
test "$(/usr/bin/lipo -archs "$public_binary")" = arm64
/usr/bin/codesign --verify --strict --all-architectures "$public_binary"
/usr/bin/codesign --verify --strict --all-architectures \
--check-notarization --test-requirement '=notarized' "$public_binary"
test "$("$public_binary" --version)" = \
"hf2q $EXPECTED_VERSION"
proof_home=$(cd "$(mktemp -d "$RUNNER_TEMP/hf2q-public-install-home.XXXXXX")" && pwd -P)
proof_install="$proof_home/.local/bin"
proof_state="$proof_home/.hf2q"
curl -fsSL "$public_base/install.sh" | \
HOME="$proof_home" SHELL=/bin/bash HF2Q_INSTALL_DIR="$proof_install" \
PATH=/usr/bin:/bin:/usr/sbin:/sbin sh >/dev/null
test "$(shasum -a 256 "$proof_install/hf2q" | awk '{print $1}')" = \
"$STANDALONE_BINARY_SHA256"
test "$(HOME="$proof_home" SHELL=/bin/bash \
"$proof_install/hf2q" --version)" = \
"hf2q $EXPECTED_VERSION"
test -f "$proof_home/.local/share/bash-completion/completions/hf2q"
test -f "$proof_home/.local/share/zsh/site-functions/_hf2q"
test -f "$proof_home/.config/fish/completions/hf2q.fish"
test -f "$proof_home/.bashrc"
test -f "$proof_home/.profile"
test -f "$proof_home/.local/state/hf2q/completion-ownership-v1.json"
HOME="$proof_home" SHELL=/bin/bash \
"$proof_install/hf2q" --state-root "$proof_state" \
setup --accept-defaults >/dev/null
test -s "$proof_state/config.toml"
test "$(stat -f '%Lp' "$proof_state/config.toml")" = 600
grep -Fx 'kind = "hf2q.config"' "$proof_state/config.toml"
grep -Fx 'schema_version = 2' "$proof_state/config.toml"
grep -Fx 'package = "hf2q"' "$proof_state/config.toml"
setup_golden=${RELEASE_PACKAGE_ROOT:?RELEASE_PACKAGE_ROOT is required}/src/setup/testdata/config_v2.toml
cmp -s "$proof_state/config.toml" "$setup_golden"
setup_config_sha=$(shasum -a 256 "$proof_state/config.toml" | awk '{print $1}')
HOME="$proof_home" SHELL=/bin/bash \
"$proof_install/hf2q" --state-root "$proof_state" \
setup --accept-defaults >/dev/null
test "$(shasum -a 256 "$proof_state/config.toml" | awk '{print $1}')" = \
"$setup_config_sha"
mkdir -p "$proof_state/models"
printf 'converted model\n' > "$proof_state/models/model.gguf"
model_sha=$(shasum -a 256 "$proof_state/models/model.gguf" | awk '{print $1}')
HOME="$proof_home" SHELL=/bin/bash \
"$proof_install/hf2q" uninstall --yes >/dev/null
test ! -e "$proof_install/hf2q"
test ! -e "$proof_install/.hf2q-standalone.json"
test ! -e "$proof_install/.hf2q-standalone.lock"
test ! -e "$proof_install/.hf2q-previous"
test ! -e "$proof_home/.local/share/bash-completion/completions/hf2q"
test ! -e "$proof_home/.local/share/zsh/site-functions/_hf2q"
test ! -e "$proof_home/.config/fish/completions/hf2q.fish"
test ! -e "$proof_home/.bashrc"
test ! -e "$proof_home/.profile"
test ! -e "$proof_home/.local/state/hf2q/completion-ownership-v1.json"
test -f "$proof_state/config.toml"
test -f "$proof_state/models/model.gguf"
test "$(shasum -a 256 "$proof_state/config.toml" | awk '{print $1}')" = \
"$setup_config_sha"
test "$(shasum -a 256 "$proof_state/models/model.gguf" | awk '{print $1}')" = \
"$model_sha"