Skip to content

[release] Publish a semantic release on every merge - #3

Merged
drycode merged 1 commit into
mainfrom
dy/release-on-merge
Aug 19, 2026
Merged

[release] Publish a semantic release on every merge#3
drycode merged 1 commit into
mainfrom
dy/release-on-merge

Conversation

@drycode

@drycode drycode commented Aug 19, 2026

Copy link
Copy Markdown
Owner

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 publish from 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.yml triggers on pushes to main. The tag becomes the record, not the trigger.
  • scripts/release_surface.py is the single registry of the sixteen authored version locations and the six published artifacts. set_version.py --bump patch|minor|major rewrites 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 a release:override label so it cannot deadlock a repair.
  • guard asks each registry what exists and publishes only what is missing, so rerunning a partially failed release completes it. record re-interrogates the registries before tagging, since 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.
  • ci.yml drops its push: main trigger; release.yml runs 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 check and npm install --package-lock-only produce 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.py verified 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

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
@drycode
drycode merged commit 40b8b90 into main Aug 19, 2026
5 checks passed
@drycode
drycode deleted the dy/release-on-merge branch August 19, 2026 20:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant