---
name: release-harn
short: Merge-queue-safe Harn patch/minor/major release workflow.
description: Cut a Harn release through the merge queue. One PR carries CHANGELOG + Cargo.toml bump + regenerated artifacts; candidate archive builds run before the tag, and tag push promotes those exact archives without recompiling.
when_to_use: Use when cutting a Harn `vX.Y.Z` patch / minor / major release from main, or recovering from a partially-failed release run.
---
# Release Harn
Use this skill when cutting a Harn `vX.Y.Z` release from `main`, or
recovering from a partial release.
Pair it with [[harn-providers]] when the release includes provider
catalog or capability matrix changes (the bump regenerates those
artifacts).
## Shape of a release
A Harn release is **one human PR** titled `Release vX.Y.Z` carrying
the consolidated bump:
- `CHANGELOG.md` — a new `## vX.Y.Z` section at the top with
`### Added` / `### Changed` / `### Fixed` subsections summarising
everything that lands in this version.
- `Cargo.toml` (workspace `version` field) bumped to `X.Y.Z`.
- `Cargo.lock` re-locked.
- Any per-crate manifest bumps the release-prep script touches
(`crates/*/Cargo.toml`).
- Regenerated mirror artifacts the release-prep script refreshes
(provider catalog JSON / TS / Swift, capability matrix, highlight
keywords, etc. — whatever the previous release shipped).
After this PR lands through the merge queue, two GitHub Actions
workflows cascade automatically under the `harn-release-bot` App
identity:
```text
immutable candidate OID
→ candidate_only archive matrix (sign/notarize/attest) in parallel with certification
→ join receipts → signed tag
→ tag push promotes exact archives + finalize + GHCR container (no compile)
```
Fleet `release_harn` owns that orchestration. Direct tag recovery without a
candidate receipt fails closed unless `force_rebuild=true`.
## Local entry points
The default flow:
```bash
./scripts/release_ship.sh --prepare --bump <patch|minor|major>
```
Recovery / partial-run reentry:
```bash
./scripts/release_ship.sh --finalize
./scripts/release_gate.sh <audit|prepare|publish|notes|full> ...
```
Manual workflow_dispatch entry points for recovery:
```bash
gh workflow run publish-release.yml --ref main
gh workflow run build-release-binaries.yml --ref main \
-f candidate_only=true \
-f candidate_source_ref=release-certify/<sha> \
-f candidate_source_sha=<40-hex>
gh workflow run build-release-binaries.yml --ref main \
-f promote_only=true -f tag=vX.Y.Z -f candidate_run_id=<id>
gh workflow run build-release-binaries.yml --ref main -f tag=vX.Y.Z -f force_rebuild=true
gh workflow run bump-release.yml --ref main # reconstruct a missed bump PR
```
## Before opening the PR
Sanity-check the local developer surface so any release-note bump that
mentions setup still works:
- `README.md`
- `CONTRIBUTING.md`
- `docs/src/portal.md`
- `scripts/dev_setup.sh`
- `Makefile`
- `.githooks/`
## Commit pattern
A real release lands as **one** commit on `main` after squash:
1. `Release vX.Y.Z` — code + docs + `CHANGELOG.md` + Cargo.toml /
Cargo.lock + per-crate manifest bumps + regenerated mirrors.
Authored by you via `release_ship.sh --prepare --bump <type>`.
Then **rebased onto latest `origin/main` before push**, because
`--prepare` takes 1–15 min and main may have moved meanwhile.
Landed through PR / merge queue with `gh pr merge --auto`
enabled, so it lands as soon as CI is green.
That's it. The bot takes over once it lands.
## Cross-repo consumers don't wait on releases
An IDE host's `scripts/fetch-harn.sh --local` builds Harn from
`~/projects/harn` and installs the binaries directly. Use that during
cross-repo iteration instead of waiting for crates.io. Release batching
is a published-version concern, not a developer-loop concern.
## Workflows
- `.github/workflows/publish-release.yml` (display name: "Publish
release") — fires on push to main when `Cargo.toml` is ahead of
the latest `vX.Y.Z` tag. Pushes the tag using the App token so
downstream cascades fire (a `GITHUB_TOKEN` tag push would be
suppressed by GHA).
- `.github/workflows/build-release-binaries.yml` (display name:
"Build release binaries") — `candidate_only` builds signed archives
for an exact SHA; tag push / `promote_only` attaches those exact
archives without compiling; `force_rebuild` is audited recovery.
- `.github/workflows/bump-release.yml`, display name "Open version bump
PR (recovery)". It is `workflow_dispatch` only. Use it to reconstruct
a bump PR if a "Prepare vX.Y.Z release"-style commit accidentally
lands on main without the consolidated bump.
## Hard rules
- **Never push to a PR already in the merge queue.** GitHub silently
snapshots the PR at enqueue time and ignores subsequent pushes.
The pre-push hook detects this and aborts.
- **Always rebase the release PR onto latest `origin/main` before
the final push.** `release_ship.sh --prepare` takes long enough
that main usually moves; not rebasing means the merge queue sees
a stale base and reorders the bump behind unrelated work.
- **Never amend the release commit after CI starts.** Open a new
bump PR via the recovery workflow instead.
## Required repo state
- Secrets: `RELEASE_APP_ID`, `RELEASE_APP_PRIVATE_KEY`,
`CARGO_REGISTRY_TOKEN`.
- App permissions on the repo: `Contents: write`, `Pull requests:
write`, `Actions: write`, `Metadata: read`.
## Verify
- The release bump passes the repository check seam: `make check`.
- Conformance still passes: `make conformance`.
- Catalog artifacts are in sync: `harn provider catalog generate --check`.
- The CHANGELOG entry is non-empty and cites the issue / PR numbers
the release contains.
- `Cargo.toml` workspace `version` matches the PR title's `vX.Y.Z`.