Release process
The normalized rules and order for every Omena release channel.
Every Omena release follows one normalized process. It is contract-enforced by
release/release/notes (scripts/release-notes.ts check), the publish-train
closure gate, and the provenance source guard — not by operator memory. The
shape is deliberately close to high-discipline OSS trains (oxc's
prepare-release → human approval → version-diff-triggered publish), adapted to
this repository's stronger primitives: a machine-declared semver-intent
contract, immutable-tag publication, and post-publication body verification.
Tag families
| family | channel | example |
|---|---|---|
release-v* | Rust crate train, CLI binaries, npm bindings | release-v0.4.0 |
vscode-v* | Editor extension (Marketplace + Open VSX) | vscode-v5.4.0 |
v* (legacy) | Pre-rebrand css-module-explainer era, frozen | v4.1.19 |
Rules: one tag per family per version; tags are immutable source selectors, but
pushing them starts no publication workflow; release names never repeat the tag string; both active
families are registered in docs/releases/manifest.json before their
workflows run (the notes gate fails otherwise).
The no-trigger rule is ancestry-scoped: a tag that points to a commit predating this policy resolves that commit's older workflow files. Historical rebuilds therefore use explicit dispatch; never create a probe tag to test this rule. Dependency and Dependabot housekeeping is not release-train work: land it before the preflight window or after closeout, not between print and publication.
Release order
- Verify before printing — the full release verifier
(
release/release/verify) must be green at the pre-version pin. The preflight note must also cite a lane-green Release Rehearsal run from the default branch no older than 14 days; this reviewer-checked receipt is not a push-tier gate. - Print versions with their evidence — stage the whole print first, then
run
pnpm check:rust-domain-claim-census -- --writeso its index-backed census sees every tracked file. Commit the print, measure writer-less numeric evidence such as product-surface boundary commit counts, update its values and number wording, and amend the same commit. Runrelease/release/verifyafter the amend.rust/release-print-sensitive-evidence.jsonis the complete registry of evidence that must travel with the print. - Tag — create
release-v<version>only at the merged, verified commit. Pushing the immutable source selector starts no publication job. - Dry-run first — dispatch every publish workflow explicitly with its non-uploading path. The CLI default is stage-only; the crate train also runs the steady-state semver gate against live registry state.
- Publish with scoped provenance — npm and extension workflows must be
dispatched on the tag ref because their guards bind
github.shato checkout. The crate train and CLI may be dispatched from the default-branch workflow withinputs.tag: their tooling comes from the branch while source builds and the train's two guards resolve the immutable tag. Partial train failures resume withresume; registry versions are never overwritten. - Verify after publishing — registry state, release bodies
(
verify-github), attestations, and the same-pin push CI must all be green before the release is closed out. - Rotate the compatibility window — after live publication is verified,
append the immutable Rust release tag to
rust/omena-published-release-baselines.json, move the active semver-intent baseline to that version, and open the next0.xminor window with an empty intent set. The release-semver intent gate binds these two records and rejects a completed train left active after publication.
Provenance rule
The npm and extension dispatch ref and publication checkout must be the same
commit. Both publish workflows carry an inline guard immediately after checkout
that hard-fails before any publish step when github.sha differs from the
checked-out HEAD — dispatching a tag build from a moving branch silently
poisons the signed SLSA/attestation source metadata (learned irreversibly in
the 0.4.0 train; nine npm packages and the VSIX permanently attest a commit
that was not built). Dispatch those workflows from the tag ref itself. The
train is the explicit exception: default-branch tooling accepts inputs.tag,
then both jobs prove checked-out SHA equals tag SHA. CLI builds use the tag
while stage/upload use current tooling and an exported body.
Release notes pipeline
- The source of truth is the authored release page in
docs/releases/(the docs-site frontmatter is stripped at render time and never appears in a release body). - The rendered body is: authored highlights →
## Changes(generated: declared breaking changes from the semver-intent contract, then Features/Fixes/Performance from conventional-commit subjects over the compare range) →## Distribution(channel-specific facts from the manifest) →## Release links(source, changelog, full diff). - The crate train and extension render with
--changelog; CLI rebuilds export the already-persisted GitHub Release body and fail if it is absent. Every upload usesbody_path, thenverify-githubchecks byte equality. - GitHub's
generate_release_notesis never used for registered releases.
Historical releases
The 60 legacy v* releases are frozen history: bodies carry a pre-rebrand
banner (and a pointer when a duplicate exists under the current families),
assets are never modified, and tags are never deleted. The
release:notes backfill command is the only sanctioned editor for published
bodies; it is idempotent and non-destructive.