temper CLI release. End users looking for install instructions should read install.md instead. developer machine main branch tag push releases
┌──────────────────────┐ ┌────────────────┐ ┌──────────┐ ┌────────────────┐
│ cargo make release- │ │ release/v0.1.0 │ │ v0.1.0 │ │ GitHub Release │
│ prepare --bump X │──▶ │ branch │──▶ │ tag │──▶ │ + 3 archives │
│ │ PR │ (merged) │ │ │ │ + 3 sha256 │
└──────────────────────┘ └────────────────┘ └──────────┘ └────────────────┘
│ ▲ ▲
│ │ │
release-tag.yml release.yml install.sh /
(fires on VERSION (fires on v* install.ps1
change) tag push) (fetches latest)release-tag.yml — fires when main receives a commit that changes VERSION. Reads the new version, creates and pushes an annotated v<X.Y.Z> tag. That's it.release.yml — fires on v* tag push (or via workflow_call from release-tag.yml, or manual workflow_dispatch). Calls the reusable build-cli-binaries.yml to produce 3 platform archives, then creates a GitHub Release with attached artifacts. There's no separate pre-flight validation step — the release PR's normal CI (fmt, clippy, tests) is the gate.build-cli-binaries.yml — a reusable workflow called by release.yml. Builds temper for macOS arm64, Linux x86_64, and Windows x86_64, bundles the matching ONNX Runtime library, and uploads per-platform archives plus SHA256 checksums.cargo make release-preparemain, up-to-date with origin/main, gh CLI present.temper-cli or any of its workspace deps (temper-core, temper-client, temper-ingest) or release/installer tooling changed since the last v* tag. If nothing changed, it exits cleanly — no release needed.VERSION file and the bump level (patch by default). Bump variants:cargo make release-prepare # patch: 0.1.0 → 0.1.1
cargo make release-prepare-minor # minor: 0.1.x → 0.2.0
cargo make release-prepare-major # major: 0.x.y → 1.0.0release/v<X.Y.Z> branch, writes the new version into VERSION and crates/temper-cli/Cargo.toml, runs cargo check as a sanity gate, commits, pushes, and opens a PR via gh.release/v<X.Y.Z> PR into main lands a commit that modifies VERSION. That triggers release-tag.yml, which creates and pushes v<X.Y.Z>. That tag push triggers release.yml, which builds binaries and creates the GitHub Release.| Platform | Archive | Checksum |
|---|---|---|
| macOS (Apple Silicon) | temper-v<X.Y.Z>-aarch64-apple-darwin.tar.gz | ...tar.gz.sha256 |
| Linux (x86_64) | temper-v<X.Y.Z>-x86_64-unknown-linux-gnu.tar.gz | ...tar.gz.sha256 |
| Windows (x86_64) | temper-v<X.Y.Z>-x86_64-pc-windows-msvc.zip | ...zip.sha256 |
temper or temper.exe — the CLI binarylib/libonnxruntime.dylib or lib/libonnxruntime.so (mac/linux) OR onnxruntime.dll (Windows, flat)LICENSEREADME-INSTALL.txt — brief pointer at the installer~/.local/share/temper/ (mac/linux) or %LOCALAPPDATA%\Programs\temper\ (Windows), and symlink or PATH-update as appropriate.| Artifact | Checksum |
|---|---|
temper-skill-v<X.Y.Z>.zip | ...zip.sha256 |
build-skill-bundle job from the committedagent-skills/temper-knowledge-base/ tree, attested like every other published artifact, andcargo make skill-package. Seeinstall.sh can verify each extracted filetemper-skill-v<ver>.zip, not temper-v<ver>-skill.zip. create-github-release.shtemper-v<ver>-*.{tar.gz,zip}. The second spelling matches that glob and would fail the release fortemper-*.zip, temper-*.sha256), so the bundle rides the existing publish loop untouched. Bothtest-create-github-release.sh — the failure direction is safebuild-cli-binaries.yml produced, running in GitHubmain, triggered by the release-tag chain (release-tag.yml):https://github.com/tasker-systems/temper/.github/workflows/build-cli-binaries.yml@refs/heads/mainattest.rs's expected_identity — that string is the property), and whosepredicate.buildDefinition.externalParameters.workflow.path to.github/workflows/release-tag.yml (the chain's entry workflow — this closesworkflow_dispatch door on build-cli-binaries.yml, which wouldskip_tlog() is never called). Both online paths check that--verify --online, the archive's for temper update. That isWhy refs/heads/main, notrefs/tags/{tag}: the release chain is
branch-triggered by construction.release-tag.ymlfires on aVERSION-file
push tomain, creates and pushes the tag withGITHUB_TOKEN, then callsrelease.yml→build-cli-binaries.ymlviaworkflow_call. A tag pushed
withGITHUB_TOKENdoes not trigger workflows, sorelease.yml's ownon: push: tags: v*never fires (release-tag.yml:53-56documents this).
A reusable workflow called viaworkflow_callinherits the caller'sgithub.ref, which isrefs/heads/mainthroughout the chain — so the OIDC
token'srefclaim, and therefore the Fulcio cert SAN, carries@refs/heads/mainfor every release. The tag is carried by the archive
filename and the manifest'sversionfield, not by the cert SAN. The digest
match is what binds to a specific release artifact.
main whosetempermain.refs/heads/main),gh attestation
verify (see install.md)temper's own verification code and on our pinnedcurl -fsSL …/main/scripts/install/install.sh | sh line inraw.githubusercontent.com and is authenticated by TLS and GitHub's controlAttestError::TrustRootUnusable andAttestationVerifyError::TrustRootUnusable render "cut a new release orinstall.sh. So the escape hatch fromattest-build-provenance, checkout,upload-artifact) are pinned by full commit SHA, because a moving tag on aattestations: write is a signing oracle waiting to beuses: pins; only version updates with thegithub-actions ecosystem do, and those run solely from a committed config..github/dependabot.yml, and it is the whole bump path: deletecrates/temper-cli/trust/sigstore-public-good-trusted-root.jsoninclude_str! and nothing else. There is nobuild.rs in temper-cli, no digest pin over it, and no freshness check —gh attestation trusted-root rather than anywhere else.temper verifies release attestations against a Sigstore trust root pinnedcrates/temper-cli/trust/sigstore-public-good-trusted-root.json,include_str! in attest.rs) — deliberately not fetched liveinternal/superpowers/spikes/2026-07-29-sigstore-crate-evaluation.mdEXPECTED_MODEL_SHA256 doctrine (crates/temper-ingest/build.rs) applied totemper built before that rotation will fail--verify --online and temper update's (mandatory, no-bypass) attestationtemper is itself replaced by a build carrying the new root.attest.rs's AttestError::TrustRootUnusable fires specifically for anNotOurs (a bad signature orinstall.sh fetches atemper update's attestation path to catch up. Note whatcrates/temper-cli/trust/sigstore-public-good-trusted-root.json from a freshgh attestation trusted-root, verify it still contains the public-good rootattest.rs's module docscargo make release-prepare promptly..github/workflows/build-cli-binaries.yml:ort in crates/temper-ingest/Cargo.toml — specifically, the api-XX feature. When upgrading ort:ort and its api-XX feature in crates/temper-ingest/Cargo.toml.ONNX_RUNTIME_VERSION in build-cli-binaries.yml.ort_sha256 matrix values — see the standing obligation below. This is not optional; the build fails closed without it..so in crates/temper-ingest/lib/x86_64-unknown-linux-gnu/ (this is used by the Vercel temper-api deploy).github.com/microsoft/onnxruntime/releases per platform. The four per-platform archives differ in packaging (.tgz vs .zip) and library name (libonnxruntime.{dylib,so} vs onnxruntime.dll), all handled in the workflow's matrix..github/workflows/build-cli-binaries.yml carries anort_sha256 beside its ort_archive/ort_archive_ext, and the "Download ONNXEXPECTED_MODEL_SHA256 doctrinecrates/temper-ingest/build.rs) applied one layer out: the native librarydlopen'd by the shipped binary. An unpinned--verify --online, comesverified over it. The model riding in the same archive was alreadyONNX_RUNTIME_VERSION requires recomputing all three digests in theonnxruntime-{osx-arm64,linux-x64,win-x64}-<version> archives, so a versionONNX_RUNTIME_VERSION — Microsoft's release assets areONNX_RUNTIME_VERSION, and paste the real output — never a placeholder. Theseort_archive matches the name printeddetect-changes.sh finds no changes to temper-cli, its deps, installer scripts, release tooling, or release workflows, release-prepare exits cleanly with:[warn] No changes to temper-cli or its deps since <base-ref> — nothing to releasegit checkout main
git pull
cargo make release-preparegit status # see what's uncommitted
git stash # or: git commit -am "wip"
cargo make release-prepare
git stash pop # restore afterrelease.yml fails on a single platformneeds.build-cli-binaries.result — a failure on any single platform marks the whole release as failed and skips GitHub Release creation. To investigate:build-cli-binaries job for the failing platform.main in a normal PR.workflow_dispatch on the release.yml workflow (input: the existing tag), or delete the tag and cargo make release-prepare again.gh release delete v<X.Y.Z> --yes --cleanup-tag
git push --delete origin v<X.Y.Z>
# fix whatever broke it, then
cargo make release-preparebuild-cli-binaries.yml are self-documenting. To add a new target:matrix.target list with name, runner, triple, ort_archive, ort_archive_ext, lib_name, lib_dest_dir, archive_ext.install.sh (or install.ps1 for a Windows variant) with the new OS/arch detection branch.docs/guides/install.md's platform list.docs/guides/install.md — user-facing install instructionsinternal/superpowers/specs/2026-04-17-temper-cli-binary-release-design.md — original design docinternal/superpowers/specs/2026-07-29-binary-attestation-and-manifest-verification-design.md — per-file manifest + attestation designinternal/superpowers/spikes/2026-07-29-sigstore-crate-evaluation.md — why the trust root is pinned, and which crate/roottools/scripts/release/ — the shell scripts driving release-prepare.github/workflows/release.yml — the tag-driven release workflow.github/workflows/build-cli-binaries.yml — the reusable build matrix