Release Harn
Use Harn's GitHub workflows to open a release, certify its files, and publish
them. You need permission to dispatch workflows in burin-labs/harn.
Open the release pull request#
Releases are cut from green, the newest main
commit whose full CI suite passed. Confirm that green declares an
X.Y.Z-dev workspace version and contains unreleased
changelog.d/<id>.<category>.md fragments. Fragments that landed on main
after green wait for the next release. Then run:
gh workflow run bump-release.yml --repo burin-labs/harn --ref main
The opener takes no version or candidate-build inputs. It strips the declared
-dev suffix, folds the fragments, regenerates derived files, and opens
Release vX.Y.Z. It publishes one GitHub-signed commit and adds ship so
Smart Ship owns delivery through the merge queue. Required checks and review
still apply. Repeated openers leave an existing PR's delivery grant unchanged.
The daily schedule runs the same decision. A stable workspace version or no
unreleased fragments produces action=none, with a notice explaining why.
An unreadable pull-request list fails instead of opening a duplicate.
The signed commit is recorded at release-attempt/vX.Y.Z/<commit> before the
pull request opens. An existing release stays frozen at that attempt. Later
fragments remain for the next release, including when their changes merge ahead
of this release. A push changing changelog.d checks the existing attempt
without rebuilding or opening a release.
An existing release/vX.Y.Z branch without a matching attempt fails closed.
To replace an unrecorded stale attempt, close its pull request and dispatch the
opener explicitly. Inspect the replacement head's required checks and review.
Do not reset an attempt ref or dispatch a second version selector.
Repair a failed development bump#
If publication completed but the post-publication development bump failed, repair it through the same opener:
gh workflow run open-development-bump.yml --repo burin-labs/harn --ref main -f published_tag=vX.Y.Z
Omit published_tag to use the latest published stable release. The workflow
verifies publication before minting the release App token, derives the next
development identity, opens or reuses its pull request, validates its grammar
receipt, and enrolls a newly opened PR in Smart Ship. An existing PR retains
its owner's delivery grant, including an explicit stop. Resume a stopped PR
only after reading its label history and resolving the reason for the stop.
A repeated repair after the cutover lands is a no-op. Draft, prerelease, unreadable, and incomplete publication
records refuse the repair.
The development monitor reports success only when main declares the expected development version. An open update is pending, not successful convergence; an unreadable, incomplete, or ambiguous PR census fails the measurement.
The five-minute repository-state controller automatically dispatches the same
opener once for an owed development version when no open cutover PR exists.
It verifies publication and reserves the attempt on the fixed commit in
scripts/development_cutover_repair.json before dispatch. Scheduled and manual
controller checks share that reservation owner. Existing PRs, including red
ones, remain their owner's work.
If reservation or dispatch fails, inspect the controller run and use the manual repair command above. The reservation deliberately prevents another automatic attempt, including after a crash. Missing or incomplete reservation history refuses automatic repair. Do not change the fixed anchor or seed to clear a reservation, and do not infer completed integration from a reserved attempt.
Retire an unpublished candidate with failed source CI#
Read the failed producer and the main-push CI run at its exact source. If both are terminal failures and the version was never published, request retirement through the existing development bump workflow:
gh workflow run open-development-bump.yml --repo burin-labs/harn --ref main \
-f published_tag=vPREDECESSOR \
-f retire_source_sha=FULL_SHA -f retire_release_pr=PR_NUMBER \
-f retire_producer_run=PRODUCER_RUN -f retire_source_ci_run=CI_RUN
Replace the placeholders with the published predecessor and the owning release records. Omit every consumer-retirement input: mixing the two evidence paths is refused. The planner requires complete terminal job censuses with actual failed checks, exact source identity, main ancestry and the merged release PR. It also proves that the version has no tag or release and that neither publication workflow is active, with known positive reads on those paths. The opener repeats those checks after preparation before opening its normal development-version PR. Follow that PR through the merge queue, then use the release opener for the next version. This retires an unpublished identity; it does not publish files from a failed candidate.
Follow certification and publication#
Record the release pull request and the exact commit that lands on main. Follow the runs for that commit in Harn Actions.
build-release-binaries.ymlbuilds the candidate on the version commit's push to main. The release's merge group builds nothing; itsRelease candidate verdictpasses only after the exact-source consumer rehearsal passes at the commit that lands. A merge group can't build the candidate because the trusted macOS smoke host admits onlyrefs/heads/main. Other pushes only warm caches. Daily scheduled runs build signed source candidates at their exact main commit, including commits outside the warm-cache path filter. They reuse a successful candidate only while its manifest, release files, and every target archive remain available and unexpired. These runs do not publish a release.- The candidate run builds, signs, notarizes, and attests the five platform
archives. It checks those files with the release audit and smoke tests.
The
candidate-manifest-<sha>artifact binds the source commit, files, digests, and publication metadata. Keep its exact run ID with the release. Its consumer rehearsal waits for successful archive and manifest jobs, then passes that producer's run ID and attempt with the authenticated observer. The consumer verifies the exact-source archive before installing it and still runs every required product check. Rehearsals without archives keep their source or pull-request CLI path. This installation permission does not replace the terminal consumer verdict or publication checks. promote-release.ymlstarts after the successful main push run. For a version commit it waits up to 90 minutes forgreento contain it, then finds the successful candidate run at that commit and verifies the manifest, digests, and attestations. It also requires successful full-suite main-push CI at the release commit, including every deferred test family. It waits for an active run; missing or failed proof blocks publication. It creates the tag and GitHub release using those files. Promotion doesn't rebuild them.- The tag starts
publish-release.yml, which checks the same full-suite proof before publishing crates from the tag. Promotion also packages the published Linux archives into the container and opens the next patch's development-version pull request.
Before promotion, the candidate's exact source needs a successful
macos-nightly.yml run through workflow_dispatch. The candidate run starts
it: scripts/ci/dispatch_native_qualification.sh dispatches the main revision of
the workflow with the candidate's source_sha. Before executing candidate code,
the trusted workflow verifies that this source is a main ancestor and measures
both allowed execution and a refused write through process confinement on the
owned Mac. Scheduled source candidates don't start it. Arbitrary branch sources
are refused before checkout or execution on the restricted runner group.
The common publication gate requires the native workspace tests, CLI smoke and
test-case performance steps to have executed successfully in that exact attempt.
It reports named pending or failed conditions and refuses absent, partial,
skipped or foreign evidence. A run-scoped artifact binds the tested CLI source,
workflow revision, repository, run, attempt and phases. A display title alone
cannot qualify a candidate. Missing or mismatched receipts refuse publication.
Qualification dispatches never publish their older source as a main warm cache.
Windows native qualification remains advisory.
The platform policy lives in scripts/lib/release_platforms.harn; its JSON
projection in scripts/release_audit_contract.json is checked mechanically.
Hosted audit receipts carry the downloaded artifact as the macOS platform's
qualification field and retain the workflow's head_branch and policy
head_sha in its run record. The audit contract checks that same source, run,
attempt and phase binding before reusing proof.
Publication is complete only after you verify all of these:
- The release pull request merged, and the immutable tag selects its certified source.
- The exact candidate and promotion runs succeeded.
- The GitHub release has all five archives,
SHA256SUMS, andrelease-assets.json, with digests matching the candidate manifest. - The tag's crate publication succeeded, and the versioned container is anonymously pullable.
- The post-publication development bump reached main or reported a proved no-op because main had already advanced.
Main ancestry and exact-source certification bind a tag to its release source. A tag outside main also needs a trusted SSH signature naming its candidate commit. Corrected stable sources also prove the original version transition in their first-parent history, a verified source signature, and durable publication evidence. The published manifest and signed archive index must bind all five archive digests to that exact source and the owning producer's workflow, main ref, run, and attempt. This read does not depend on temporary build artifacts or retained workflow-run records. The signed index proves provenance; the public release supplies publication authority. Its author and the required manifest, index, and archive uploaders must be the owning release app's GitHub bot identity; additional SDK assets can come from their own publisher.
Read Release assets manifest for the download contract. A visible tag or release page alone doesn't prove complete publication.
Recover a failed run#
Read the failing job before choosing a retry. Keep the commit, run ID, and candidate manifest attached to the release record.
Promotion reports a stuck release on its own. When main's stable version is
still unpublished four hours after its version commit, every later promotion
run fails and one [Release] vX.Y.Z is unpublished on main issue opens. Close
it after the release publishes.
- Opener failed before publication: fix the cause and dispatch
bump-release.ymlagain. Its admission checks run again. - Candidate failed because of infrastructure: rerun the failed jobs in that
exact candidate run. A source defect needs a corrected pull request and
certification of the resulting commit. For an unpublished stable version,
keep that version, build a signed source candidate on corrected main with
source_candidate=true, then use manual promotion recovery. Recovery proves the stable version transition in the source's first-parent history and still requires exact-source CI, consumer rehearsal, and candidate certification. - Promotion failed: rerun the failed jobs in the exact promotion run. It checks the existing tag's commit and refuses a conflicting tag.
- Candidate succeeded but no main-push event started promotion: dispatch the
existing promoter on main with that certified candidate's run ID and source:
gh workflow run promote-release.yml --repo burin-labs/harn --ref main -f candidate_run_id=RUN_ID -f candidate_sha=FULL_SHA. It refuses an unsuccessful or foreign producer and a source not contained in main, then applies the normal version, manifest, digest and attestation checks. If the producer never ran a consumer rehearsal, recovery runs the existing rehearsal workflow at that exact source and requires its measured pass before publication. Missing, failed, cancelled or mismatched results refuse publication. A producer's existing failed rehearsal is not replaced. Recovery publishes the existing files without rebuilding them. When the consumer needs a prepared repair before it can land against the published tag, add-f consumer_pull_request=PR_NUMBER. Recovery selects that open pull request in the registered consumer repository through the existing pairing checks; a fork, closed pull, malformed number, or conflicting source pairing refuses. Consumer observation and publication checks still run. This selection cannot reuse an earlier default-branch rehearsal. - The consumer observer reached its deadline, but its exact dispatched child
later succeeded: after the completed-child recovery policy is on main,
dispatch the normal promoter with the same producer and source plus
-f completed_consumer_run_id=CHILD_RUN_ID. The authorization job verifies the original dispatch and deadline, every producer build/smoke/audit result, the child's complete successful census and product receipts, then rechecks both latest attempts. Only the observer deadline and its derived final verdict may have failed. Cancelled, failed, partial, replaced or mismatched children refuse. The shared publisher authenticates the attempt-bound authorization artifact before applying all normal archive and publication checks. The original deadline remains unmeasured; no result is rewritten and no equal-tree source substitution is accepted. - Crate publication failed after the tag exists: rerun the failed jobs in the
tag's
publish-release.ymlrun. Its publisher resumes remaining crates. If publication policy needed a repair on main, dispatchpublish-release.ymlwith the immutabletag, itsexpected_source_sha, and the landedexpected_policy_sha. Recovery uses that policy without changing the tag's source. - Container or development bump failed: rerun those failed promotion jobs.
Don't retag a published version or start a local publisher or watcher as a second release controller. Retired Fleet launchers and candidate-build input tuples aren't recovery entry points for these workflows.
Check downstream convergence#
Promotion's repin job dispatches the registered consumers' own update
workflows. For the fleet-owned bump adapters that is one dispatch: the
harn-bump-fleet promote-released-orchestration.yml workflow moves the
orchestration pin to the release, and each adapter's converged landing starts
that consumer's bump. Each consumer opens its own pull request, then follows its
checks, review, and merge queue. Record each consumer's terminal state separately from
Harn publication. Dispatch success doesn't prove that a consumer updated or
that its pull request merged.
Rehearse a release change offline#
Run the fixture rehearsal with an installed Harn binary:
HARN_BIN="$(command -v harn)" bash scripts/release_rehearsal.sh
The rehearsal runs publication staging and checks archive provenance, publication policy, and development cutover. It creates no remote release. CI requires it for release changes and main pushes. It doesn't prove live credentials or replace candidate certification.
For an offline audit or dry run, use:
./scripts/release_gate.sh audit
./scripts/release_gate.sh full --bump patch --dry-run