Skip to content

Release and Versioning

Ivan Seredkin edited this page May 23, 2026 · 2 revisions

Release and Versioning

The release engineering contract for budi-core (siropkin/budi). Tags drive the entire pipeline; Cargo.toml is the source of truth for the workspace version. Stable releases publish to Homebrew; prereleases (rc / beta) do not.

This page documents the release flow as it operates today. Sister repos in the ecosystem (siropkin/budi-cloud, siropkin/budi-cursor, siropkin/budi-jetbrains, siropkin/homebrew-budi) have their own release cadences but consume budi-core via the same tag flow.

Version numbering

X.Y.Z MAJOR.MINOR.PATCH. Cargo.toml is the canonical workspace version; the verify_tag_version step in .github/workflows/release.yml rejects a tag whose version-suffix doesn't match Cargo.toml.

  • PATCH (8.5.7): bug fixes, minor improvements, anything that doesn't change a published contract. Most releases are PATCH.
  • MINOR (8.5.0): new features, new providers, new endpoints. Adds may not break existing consumers.
  • MAJOR (9.0.0): breaking changes to a published contract — wire format, schema version, statusline JSON shape. Reserved for genuine deprecation cliffs.

Prereleases are X.Y.Z-rc.N or X.Y.Z-beta.N. Anything with a - suffix counts. Cargo.toml stays at the target stable across an entire prerelease cycle (verify_tag_version strips the suffix before comparing).

Tag-driven pipeline

.github/workflows/release.yml is triggered by tags. The same workflow ships both stable and prerelease, branching on whether the tag carries a -suffix:

Tag shape GitHub release Homebrew tap
vX.Y.Z (stable) prerelease: false Auto-bumps siropkin/homebrew-budi/Formula/budi.rb
vX.Y.Z-rc.N (prerelease) prerelease: true Skippedbrew upgrade users stay on the last stable

This is what lets the maintainer run vX.Y.Z-rc.1, -rc.2, … from the same branch — swapping the binary in by hand from the prerelease tarball — and only tag the clean vX.Y.Z once the candidate passes the real-DB smoke. Promoting an rc is a tag-only operation: re-tag the same SHA as vX.Y.Z and the workflow republishes with the stable-release gate (prerelease: false, tap bump on).

Inspecting which tag did what:

# The release-workflow run logs is_prerelease=… in verify_tag_version
# Cross-reference with:
gh release view vX.Y.Z[-rc.N] --json prerelease,assets

What ships in a stable release

A vX.Y.Z tag publishes:

  • Tarballs for five platforms: macOS arm64, macOS x86_64, Linux arm64, Linux x86_64, Windows x86_64
  • A GitHub release marked prerelease: false with all tarballs attached
  • A Homebrew tap bump in siropkin/homebrew-budi/Formula/budi.rb so brew upgrade siropkin/budi/budi picks the version up within a few minutes
  • A CHANGELOG entry — every release lands a section in CHANGELOG.md

Pre-tag checklist

Before tagging vX.Y.Z:

  1. CI is green on the candidate SHA (gh run list --workflow=ci.yml --branch main).
  2. e2e guards pass locally for any contract touched in the diff. Each scripts/e2e/test_<slug>.sh pins a specific bug or contract; new fixes land alongside a new guard verified to fail when the fix is reverted.
  3. Embedded pricing manifest is fresh enough. The release checklist runs scripts/pricing/sync_baseline.sh to refresh crates/budi-core/src/pricing/manifest.embedded.json against current LiteLLM upstream. The 95 % retention floor and $1,000/M sanity ceiling defined in Model Pricing – Embedded Baseline and Runtime Refresh §2 apply.
  4. CHANGELOG.md has a section for the new version with what changed.
  5. Cargo.toml is at the target version. If the workflow is rejecting the tag, verify_tag_version is the canonical check.
  6. SOUL.md is consistent with current behavior — release boundaries are good moments to fold drift back in.

Schema-version policy

The SQLite schema carries a schema_version integer. Bumps are coupled with code that migrates forward; the daemon refuses to start against a DB version it doesn't recognize.

  • Backward-compatible migrations (add column, add index) land in any release and are applied automatically on daemon startup via crates/budi-core/src/migration.rs.
  • Cloud-sync envelope schema_version is bumped when the wire format adds or renames fields. The cloud accepts both the previous and current versions during the dual-emit window — the 2026-05-15 org_idworkspace_id rename (banner at the top of Cloud Data Contract and Privacy Boundary) is the canonical worked example. The most recent bump shipped in v8.5.7-rc.2 (cloud sync schema → 5).
  • Major schema breakage (drop column, repurpose semantics) requires a MAJOR version bump and a written migration story in CHANGELOG.md.

Dual-emit / dual-accept windows

When a rename crosses repos (daemon and cloud), the daemon emits both the new and legacy keys until ≥ 30 days of mixed-version operation has been observed in production ingest logs. After that, a follow-up commit drops the legacy paths. The current worked example: org_idworkspace_id, where:

  • Wire format: every envelope carries both workspace_id and org_id keys (identical values) and accepts either on responses.
  • TOML: workspace_id is canonical; org_id is read via serde(alias).
  • CLI flag: --workspace-id canonical, --org-id accepted as a clap alias.
  • JSON output: dual-emits both keys.

Order of operations is documented on the tracking issue. The 30-day soak is the gate — not a calendar date — because the goal is "a daemon that has shipped the rename keeps working against a cloud that has not, and vice versa."

Upgrade-path compatibility

Every release should be upgradable from the previous stable without manual intervention. The scripts/e2e/ directory has dedicated guards for upgrade edges:

  • Legacy proxy residue detection
  • Schema migration paths
  • Cloud sync schema-version mismatches (422 handling)
  • Mixed-binary detection (budi init warning when multiple binaries are on PATH)

When a release would break an upgrade path, the CHANGELOG entry leads on the breakage and the workflow includes the manual recovery command. The v8.1 → v8.2 proxy-removal cutover (pinned in JSONL Tailing as Live Ingestion) is the canonical "release notes must lead on this" example — users with v8.1-era shell-profile exports need to run budi init --cleanup.

Distribution channels

Channel What it carries Update mechanism
Homebrew tap (siropkin/homebrew-budi) Stable releases only. macOS + Linux. brew upgrade siropkin/budi/budi
GitHub releases Stable + prerelease tarballs for all five platforms Manual download, or scripted via gh release download
Install script (scripts/install.sh) Builds release locally; installs to ~/.local/bin/ Re-run the script
Cargo-bin fallback cargo install --path crates/budi-{cli,daemon} --bin … --force --locked Re-run the install commands

The Cargo-bin fallback exists primarily for environments where anti-virus blocks the install script (common on Windows).

Sister-repo coordination

The budi ecosystem is six independent repos. budi-core does not block on sister repos for a release, but a few coordination notes:

  • budi-cloud ships its own Next.js deployments to app.getbudi.dev. The POST /v1/ingest schema is the coordination point — additions are forward-compatible, renames go through dual-emit (see above).
  • budi-cursor is a VS Code Marketplace extension. It checks the daemon's api_version on startup (MIN_API_VERSION = 1); a budi-core release that bumps api_version must coordinate with a Marketplace publish.
  • budi-jetbrains is the JetBrains plugin. Tracks its own release cadence on the JetBrains Marketplace.
  • homebrew-budi is the tap. The auto-bump from the release workflow is the only path that writes to it — manual edits are not part of the contract.
  • getbudi.dev is the marketing site (Astro). Independent release cadence.

Reference implementations

Out of scope here

Clone this wiki locally