[release] Publish a semantic release on every merge - #3
Merged
Conversation
The committer declares the version; CI verifies it and publishes. release.yml now triggers on pushes to main rather than on a tag, so a merged pull request ships a release instead of queueing one behind a manual tag. scripts/release_surface.py is the single registry of the sixteen authored version locations and the six published artifacts. set_version.py rewrites all sixteen, so lockfiles and internal pins cannot drift; the writers edit in place, which the tests pin as byte-identical. version-gate.yml refuses a pull request unless the version agrees everywhere, is exactly one semver step over the base branch, is unclaimed by every registry, and follows a fully published predecessor. The last check yields to a release:override label so it cannot deadlock a repair. guard now asks each registry what already exists and publishes only what is missing, so rerunning a partially failed release completes it rather than failing on duplicate versions — the 0.1.1 recovery that had to be done by hand. record re-interrogates the registries before tagging, because a skipped dependent job is otherwise indistinguishable from a successful one. The npm dependency range is derived from the manifest, replacing a hardcoded ^0.1.1 that would have shipped a stale pin. Solves: release on merge with a verified semantic version Tests: pytest scripts/tests (36); actionlint; npm ci + suites (852); cargo test
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Context
Publishing was tag-triggered and hand-driven: a merge shipped nothing, sixteen version locations were synced by hand, and the 0.1.1 release had to be finished with
cargo publishfrom a laptop because rerunning the failed run re-attempted the crates that had already published.From here the committer declares the version and CI does the rest.
Change
release.ymltriggers on pushes tomain. The tag becomes the record, not the trigger.scripts/release_surface.pyis the single registry of the sixteen authored version locations and the six published artifacts.set_version.py --bump patch|minor|majorrewrites all sixteen.version-gate.yml(new, on every PR) requires: the surface agrees, the version is exactly one semver step over the base branch, no registry has claimed it, and the predecessor is fully published. The last check yields to arelease:overridelabel so it cannot deadlock a repair.guardasks each registry what exists and publishes only what is missing, so rerunning a partially failed release completes it.recordre-interrogates the registries before tagging, since a skipped dependent job is otherwise indistinguishable from a successful one.^0.1.1that would have shipped a stale pin.ci.ymldrops itspush: maintrigger;release.ymlruns that exact suite on every merge, so keeping it ran the suite twice.Design:
docs/specs/2026-08-19-release-on-merge-design.md. Process:RELEASING.md.Testing
pytest scripts/tests— 36 passed. Covers every legal and illegal semver step, the full-surface rewrite, byte-identical rewriting of the current version (so generated lockfiles are never reflowed), a stale lockfile being caught, registry URL construction including the npm scoped escape, and a refusal to read an unexpected HTTP status as present or absent.actionlint— clean on all three workflows.cargo checkandnpm install --package-lock-onlyproduce no further changes, proving the hand-written lockfile edits are exactly what the toolchains write.npm ci(strict) plus suites: 558 + 294 passed.cargo test: passed.registry_status.pyverified live: 0.1.1 present in all six registries, 0.1.2 absent from all six.Merging this is the end-to-end proof: it bumps to 0.1.2, so the gate runs on this PR and the new release path publishes all six artifacts on merge.
Solves: DRY-974 follow-up — release on merge with a verified semantic version