The branch model is dev → qa → main, and each arrow means one thing:
| Push to | What runs | Cost |
|---|---|---|
dev |
ci.yml — fmt, clippy, cargo test |
cheap; push often |
qa |
qa-gate.yml — release-check.sh, the real all-plugins end-to-end gate |
~2h; the pre-release soak |
main |
tag-on-main.yml — auto-tags crates/busbar/Cargo.toml's version → the release |
the BOOM |
dev is always release-ready because every push is fully CI'd. Promoting to qa proves the exact
commit works against every plugin together. Promoting to main is the release.
Write your notes under ## [Unreleased] in CHANGELOG.md (Keep-a-Changelog
headings: Added / Changed / Fixed / Security). If you leave it empty, the release notes fall back
to "Maintenance and dependency updates."
- Prepare the bump on
dev. Run the Prepare release (on dev) workflow (Actions → Prepare release (on dev) → Run workflow fromdev→ enter e.g.1.5.1). It bumpscrates/busbar/Cargo.toml+Cargo.lock, regenerates the committed OpenAPI schema (UPDATE_OPENAPI=1— the CI drift gate that fails the build if stale), promotesCHANGELOG [Unreleased]→[version]with today's date, and commits + pushes todev. It does not tag. - Promote
dev→qa. The ~2hqa-gate.ymlfull-plugin gate runs. A green run means the bumped commit is release-ready. - Promote
qa→main. Landing onmainrunstag-on-main.yml, which tagsvX.Y.Z(the version now inCargo.toml) — and that tag is the sign-off. It is idempotent: if the tag already exists (e.g. a docs hotfix landing on main without a version bump), it is a safe no-op, so only a new version cuts a release.
release.yml— cross-compiles the 5 target binaries, SBOM, the OpenAPI asset, and the build-provenance attestation (verify with--repo GetBusbar/busbar).docker.yml— builds + pushesgetbusbar/busbar:X.Y.Z+latestto Docker Hub andghcr.io/getbusbar/busbar, cosign-signed.
- Homebrew — the tap's
bump-formulaworkflow runs daily and updates bothbusbarandbusbar-adminformulae (version + checksums) when it sees a newer release. A missed run just catches up the next day. (Optional: add a PAT + firerepository_dispatch{type: upstream-release}at the end ofrelease.ymlfor an instant bump instead of ≤24 h.) - Website — the download page shows the new version automatically (
src/release.jsonis regenerated from Cargo at build). For the version-pin examples (docker/compose/helm/attestation), runnode scripts/bump-site-version.mjs X.Y.Zin the marketing repo and push — or wire a Cloudflare Pages deploy hook to rebuild on release. (This never touchesfacts.ts BUSBAR_VERSION, which stamps measured benchmark data and only changes on a re-benchmark.) - SDKs (
busbar-python/-js/-go,busbar-admin) — these carry their own semver and regenerate fromopenapi.json; tag them (vX.Y.Z) only when you want to publish a new SDK cut. Publishing is tokenless (OIDC / git tag).
Every performance number the site publishes is stamped with version + hardware + source, enforced
by a build-time self-check in facts.ts that fails the build if a stamp is missing. Re-benchmark →
update the measured value and its BUSBAR_VERSION/hardware stamp together; never bump the stamp
without a real run behind it.