Release Process¶
tradedesk-miner ships prebuilt binaries via GitHub Releases so consumers
(notably the RadiusRed Quant agent) don't need a Rust toolchain on the
target host. This document describes how a release moves from main to a
published Release page.
At a glance¶
maintainer triggers prepare-release.yml (workflow_dispatch)
│
▼
analyze ───── inspects conventional commits since last tag,
computes next semver (major/minor/patch)
│
▼
gate ───── pauses until a reviewer approves the
`release-approval` GitHub Environment
│
▼
execute ───── opens a `release/vX.Y.Z` branch with one
"chore: release vX.Y.Z" commit bumping
[workspace.package].version + Cargo.lock,
opens a PR to main, then waits up to
`merge_timeout_minutes` (default 60) for a
reviewer to rebase-merge.
Once merged: tags the merged SHA, generates
release notes via git-cliff + cliff.toml,
creates the GitHub Release as a DRAFT.
│
▼ (workflow_run completed event)
publish.yml — cross-compiles `miner` for each target triple,
uploads tarballs + checksums + SHA256SUMS manifest
to the draft Release, then flips the draft to
published as the final step.
Why draft → upload → publish? The repo has Settings → General → Immutable releases enabled, which forbids any asset modification once a release is published. Creating the release as a draft, attaching the matrix-built binaries while it's still a draft, then publishing as the last step satisfies immutability while still allowing the parallel-build pattern. After publish, no further asset modifications are possible — which is exactly the supply-chain guarantee binary consumers depend on.
This is the Cargo analog of the sibling-repo pattern
(tradedesk and tradedesk-dukascopy use the radiusred reusable
release workflow + PyPI publishing). The job structure, GitHub App
authentication, release-approval environment-gating, and
branch+PR+wait-for-merge flow are deliberately identical; only the
version-bump and packaging steps differ.
Why branch+PR (not direct push): the org-wide protect-main
ruleset requires PRs, ≥1 reviewer, linear history, and rebase-only
merges. Direct pushes to main are blocked even for the release App's
installation token. The workflow opens a PR and pauses until a
reviewer merges it, then tags and releases the merge SHA.
Versioning¶
The workspace uses a single version declared at
[workspace.package].version in the root Cargo.toml. Every crate
inherits via version.workspace = true. The prepare-release.yml
execute step rewrites that one line; cargo metadata refreshes
Cargo.lock, and the commit lands on main as
chore: release vX.Y.Z [skip ci].
Bumps are inferred from Conventional Commits:
| Commit prefix on any commit since the last tag | Resulting bump |
|---|---|
<type>(scope)?!: header (e.g. feat!:) |
major |
BREAKING CHANGE: / BREAKING-CHANGE: footer |
major |
feat(scope)?: |
minor |
anything else (fix, chore, docs, …) |
patch |
What you need (one-time setup)¶
In Settings → Secrets and variables → Actions:
- Variable:
RELEASE_APP_CLIENT_ID— radiusred-release GitHub App client id - Secret:
RELEASE_APP_PRIVATE_KEY— the App's private key (PEM)
In Settings → Environments:
- Environment:
release-approvalwith required reviewers set to the designated release maintainers
These three settings are identical to tradedesk and tradedesk-dukascopy;
re-use the same App / reviewer list.
Cutting a release¶
- Land all feature work on
main. Confirm CI is green. - Go to Actions → Prepare Release → Run workflow (branch:
main). Optionally setmerge_timeout_minutes(default 60); set it higher if the reviewer might be slow. - The
analyzejob prints the planned bump and tag in the run summary. - The
gatejob pauses; a reviewer clicks Review deployments → Approve and deploy in therelease-approvalenvironment. - The
executejob opens arelease/vX.Y.Zbranch with the version-bump commit, opens a PR tomain, then waits for it to be merged. - A reviewer rebase-merges the release PR. CI runs on the PR; the
PR template stays minimal (one-line description). The reviewer can be
the same person who approved the
release-approvalenvironment. - Once merged, the
executejob resumes: it tags the merged SHA, generates release notes from commit history (viacliff.toml), and creates the GitHub Release. publish.ymlfires automatically onrelease.published, builds the binary for each target triple, and attaches the tarballs + aSHA256SUMSmanifest to the Release page.- Users install via the README snippet — no toolchain required.
If the release-PR wait times out¶
The execute job polls the PR every 30s up to merge_timeout_minutes.
If it times out the PR is left open. To finish the release after the
PR is merged:
- Manually rebase-merge the release PR (don't squash — the workflow
expects one
chore: release vX.Y.Zcommit on main). - Re-run Actions → Prepare Release → Run workflow with the same
inputs. The
detect existing bumpstep finds the bump commit already on main and skips straight to tagging + creating the Release. This makes the workflow safely re-runnable.
Target triples¶
Currently published per release:
x86_64-unknown-linux-gnu— primary (Quant agent host class)aarch64-unknown-linux-gnu— Graviton / Ampereaarch64-apple-darwin— Apple Silicon developer workstationsx86_64-apple-darwin— Intel Mac developer workstations
To add a target, append to the matrix in .github/workflows/publish.yml.
Common candidates: x86_64-unknown-linux-musl (fully static for old
distros), x86_64-pc-windows-msvc (Windows).
Verifying a release¶
Each Release page includes a SHA256SUMS manifest covering all artifacts.
Users verify before unpacking:
The release-tarball internal SHA256SUMS file (committed under
tests/fixtures/cache/, written by scripts/generate-fixture-cache.sh)
is unrelated — that one pins the synthetic-cache regression-test data
per Plan 07-02.
Rolling back a release¶
prepare-release.yml produces an annotated tag and a "chore: release vX.Y.Z"
commit on main (via merged PR). If a release needs to be withdrawn:
gh release delete vX.Y.Z— removes the GitHub Release and its assetsgit push --delete origin vX.Y.Z— removes the remote tag- Revert the version-bump commit on
mainvia a follow-up PR - If the release branch
release/vX.Y.Zstill exists, delete it:git push --delete origin release/vX.Y.Z
(publish.yml artifacts are tied to the Release, so deleting the Release
also removes them.)