Release process
This project uses a two-phase release process. The key invariant: nothing reaches a registry without human approval of the version number.
How it works
Push to main (feat/fix/perf/refactor) ↓release.yml: prepare-release job → scripts/prepare-release.sh analyzes conventional commits since last tag → determines next version (semver: breaking → major, feat → minor, fix → patch) → updates root + release-managed workspace package.json versions and bun.lock → generates CHANGELOG.md entry → opens/updates "chore: release vX.Y.Z" PR ↓Reviewer checks version number + changelog (last cheap checkpoint) ↓Merge the release PR ↓release.yml: publish-release job → re-runs all four quality gates (check, typecheck, build, test) → creates vX.Y.Z tag pinned to the merge commit → creates a draft GitHub Release with changelog notes → explicitly dispatches docker.yml, build-binaries.yml, and build-hook.yml at the version tag and waits for all three workflows → verifies every expected release asset → publishes the release only when it is completeVersioning
Versions follow semver, derived from conventional commits:
| Commit type | Bump | Example |
|---|---|---|
feat!: or BREAKING CHANGE |
major | feat(deploy)!: redesign Helm chart |
feat: |
minor | feat(ingest): add cost reconciliation |
fix:, perf:, refactor: |
patch | fix(hook): capture real tool durations |
docs:, chore:, test:, ci:, fix(release):, fix(ci): |
none | chore(tasks): close Phase 14 |
Day-to-day
- Write conventional commits —
feat:,fix:,refactor:, etc. with optional scope. - Push to main — the
prepare-releasejob opens achore: release vX.Y.ZPR automatically. - Review the PR — check the synchronized versions in the root and every release-managed workspace
package.json, the matchingbun.lockupdate, and the changelog entries inCHANGELOG.md. Workspaces with"release": falseare deployed independently and excluded from version synchronization. - Merge the PR — this triggers
publish-release, which verifies every package version matches the tag, runs quality gates, creates the tag and draft release, dispatches all artifact workflows, verifies their assets, and publishes the completed release.
What gets published
On each release, release.yml explicitly dispatches three workflow definitions from main, passing the immutable release_tag, and waits for them to attach artifacts to the draft GitHub Release. Each workflow checks out and packages source from that tag:
| Workflow | Artifacts | Checksum file |
|---|---|---|
docker.yml |
OCI image tarballs, SBOMs | SHA256SUMS-images |
build-binaries.yml |
Server binaries (4 platforms), web tarball | SHA256SUMS-binaries |
build-hook.yml |
Hook binaries (4 platforms) | SHA256SUMS-hook |
Docker images are also pushed to GHCR (ghcr.io/yorch/ai-agents-observability/<component>:<tag>), signed with cosign, and given GitHub build-provenance attestations.
Idempotence and failure handling
- Re-running
prepare-releaseis safe — it updates the existing release PR if one is already open. - Re-running a completed automatic
publish-releaseis a no-op when the release is already published. - A manual repair deliberately rebuilds — it moves the selected release back to draft, rebuilds and verifies every artifact without moving the tag, then republishes it without changing which release is marked latest.
- An artifact failure leaves a draft — the release stays hidden until every workflow succeeds and every expected asset is present.
- Artifact workflows do not edit release notes —
release.ymlexclusively owns the changelog-based release body. - A failed publish (quality gate failure) does not create the tag. The next
prepare-releaserun reads the current version frompackage.json(not from the last git tag), so it correctly bumps from the unpublished version rather than re-generating it. The changelog range starts from thechore: release vX.Y.Zmerge commit, so commits already in the failed release’s changelog are not duplicated. The failed version’s CHANGELOG entry remains as a historical record; the version is simply skipped (no tag, no GitHub Release).
Repair an incomplete release
Use the manual workflow input when a tag and release already exist but artifacts are incomplete:
gh workflow run release.yml --ref main \ -f tag=v1.1.0 \ -f update_floating_tags=trueSet update_floating_tags=true only when repairing the current latest release; it updates the 1, 1.0, and latest image tags and marks the GitHub Release latest. Omit it when repairing an older release.
The repair run validates the existing tag and its synchronized package versions, moves the release back to draft, rebuilds and verifies every artifact at that tag, then republishes it. It never moves or recreates the tag.
Files
| File | Purpose |
|---|---|
.github/workflows/release.yml |
The two-phase release workflow |
scripts/prepare-release.sh |
Conventional commits → version + changelog |
scripts/release-version.ts |
Set and validate synchronized package versions |
CHANGELOG.md |
Generated changelog (one entry per release) |