Release engineering

This document captures how the release pipeline works, what each artifact contains, and how to cut a new release.

Overview

The pipeline has two halves:

  1. release-plz (.github/workflows/release-plz.yml, config release-plz.toml) maintains a rolling release PR on every push to main: it bumps [workspace.package].version (all crates inherit it — never hand-bump), rewrites the internal =X.Y.Z dependency pins, and updates CHANGELOG.md via cliff.toml (Common Changelog style). Merging that PR cuts the release: the same workflow then pushes the bare vX.Y.Z tag.
  2. dist / cargo-dist (.github/workflows/release.yml, config dist-workspace.toml) fires on that tag: builds the three target tarballs plus the shell installer, uploads checksums and GitHub build-provenance attestations, creates the GitHub Release (body from the version’s CHANGELOG.md section), and runs our custom jobs.
push to main ─→ release-plz-pr  (maintains rolling release PR)
merge release PR ─→ release-plz-release  (pushes tag vX.Y.Z)
tag vX.Y.Z ─→ release.yml (dist):
    plan ─→ build-local-artifacts (3 runners) ─→ build-global-artifacts
         ─→ custom-release-checks   (release-checks.yml — artifact gate)
         ─→ host                    (uploads artifacts, creates GitHub Release)
         ─→ custom-homebrew-tap-publish  (needs host + checks — pushes tap formula)
         ─→ announce ─→ custom-smoke-test (smoke-test.yml — post-announce, informational)

release.yml is generated by dist generate from dist-workspace.toml — never hand-edit it; edit the config and regenerate (dist generate --check guards drift). The custom jobs live in hand-maintained workflow_call workflows:

Version and changelog policy

Release targets

Platform Target triple Runner Notes
macOS Apple Silicon aarch64-apple-darwin macos-14 CoreML built in
Linux x86_64 x86_64-unknown-linux-gnu ubuntu-22.04 glibc-2.35 floor
Linux arm64 aarch64-unknown-linux-gnu ubuntu-22.04-arm native build, no cross

Runner pinning lives in [dist.github-custom-runners] in dist-workspace.toml. The ubuntu-22.04 pins are the glibc-floor mechanism (issue #133): our own Rust code inherits the build machine’s glibc floor. Pinning also keeps dist off its default zigbuild cross path for aarch64.

Artifacts are built with the dist cargo profile ([profile.dist]: release + strip = true).

Embedding backends per artifact

Artifact Backend
aarch64-apple-darwin CoreML (ANE/GPU) built in — auto-selected at runtime; falls back to ONNX
x86_64-unknown-linux-gnu ONNX CPU only
aarch64-unknown-linux-gnu ONNX CPU only

How CoreML gets into the macOS binarycli/Cargo.toml declares a [target.'cfg(target_os = "macos")'.dependencies] block that depends on embed with features = ["local-coreml"]. Cargo unions this with the base local-onnx feature, so on macOS embed builds with both. No --features flag is needed anywhere.

Models are downloaded from HuggingFace at runtime on first use (~706 MB for the default model) and cached under paths.models. Nothing is bundled in the binary.

Native deps and static-linking guarantees

ONNX Runtime is embedded, never system-provided (issue #133): embed/build.rs downloads and sha256-verifies Microsoft’s official build at compile time and embeds it; the binary extracts it to the user cache dir on first use (embed::ort_runtime). The Homebrew formula deliberately has no depends_on "onnxruntime" — brew’s onnxruntime bumps every few weeks while ort v2 pins 1.24.x, and Ubuntu 24.04 has no apt package, so a system-lib approach would fork the build per channel and accept untested version skew. Embedded keeps one artifact and one tested combo everywhere.

release-checks.yml enforces the guarantees on the built tarballs:

Install channels

MSRV

The workspace MSRV is Rust 1.88 on every platform, declared as rust-version in the root Cargo.toml (floor set by image 0.25 via the pdf_oxide PDF parser). CI and the release pipeline use current stable.

How to cut a release

  1. Merge the rolling release PR that release-plz maintains (curate its changelog section and, if the default patch bump is wrong, edit the version in the PR first).
  2. That’s it. The merge triggers the tag push; the tag triggers dist. Monitor the Release run in the Actions tab: 3 tarballs + shell installer + checksums + attestations on the GitHub Release, tap formula updated, smoke test green.

Do not hand-bump the version or hand-push vX.Y.Z tags outside this flow (a hand-pushed tag does work — dist only needs the tag — but the changelog and version pins won’t have been updated).

Operational notes

Footguns (each of these has bitten once — the behaviors are permanent)

Known gaps / future work