Skip to content

ci: gate the public API against the published release, and stop the pins drifting - #63

Merged
vyncint merged 1 commit into
mainfrom
ci/semver-gate
Sep 9, 2026
Merged

ci: gate the public API against the published release, and stop the pins drifting#63
vyncint merged 1 commit into
mainfrom
ci/semver-gate

Conversation

@vyncint

@vyncint vyncint commented Sep 9, 2026

Copy link
Copy Markdown
Owner

Milestone 2.2.0, issue #56.

Eleven crates are on crates.io at 2.1.0 — the issue said ten — and no workflow ran cargo-semver-checks. Removing a pub fn from launchbound-space shipped without a signal.

Why --release-type is forced, measured rather than assumed

Removing Config::kernel from launchbound-space while bumping the manifest to 3.0.0 in the same PR:

invocation result exit
inferred 0 checks: 0 pass, 254 skip"no semver update required" 0
--release-type patch 1 major and 0 minor checks failed — names Config::kernel 100

cargo-semver-checks compares the manifest version to the baseline and runs only the lints that bump would not already excuse. Declare the major bump in the same commit as the break and it has nothing left to say — the gate agrees with whatever the PR claims about itself. That is exactly the done-when this issue asked for.

patch means "no API change of any kind is excused", so every lint fires. It does not forbid additions — cargo-semver-checks reports breaking changes only. Verified against this branch, which carries #61's pub use Summary and #62's known_capabilities(): 223 checks, 223 pass.

A deliberate break carries the new breaking label, which switches the job to major.

Three of the eleven published crates are binary-only (cli, tui, runner) and have no library API; the eight that do are covered by --workspace. cargo-semver-checks arrives as a prebuilt binary through the same install-action the rest of the workflow uses — never built from source, never restored from a cache.

The pins were not only cosmetic

[workspace.dependencies] pinned the path crates at 2.0.0 while workspace.package.version was 2.1.0, so launchbound-prune 2.1.0 declared compatibility with a launchbound-space release it was never built against. Harmless for a path build — which is why it survived the whole 2.1.0 line.

Not harmless for the next major. With the pins at 2.0.0, bumping the workspace to 3.0.0 makes cargo metadata refuse outright:

failed to select a version for the requirement `launchbound-build = "^2.0.0"`
candidate versions found which didn't match: 3.0.0

I hit that while building the experiment above. The drift breaks the release after next, not this one — the worst kind.

scripts/check-versions.sh (in just ci, beside check-pins.sh, bash-3.2-portable for the macOS leg) asserts the workspace version, the eight internal pins, and that no crate sets a literal version of its own. Both failure shapes were exercised:

VERSION GATE: launchbound-space is pinned at 2.0.0, workspace is 2.1.0
VERSION GATE: crates/launchbound-space/Cargo.toml sets version = "9.9.9" instead of version.workspace = true

docs/RELEASING.md

Step 1 now says to bump the pins with the version. baseline-version gets its own step after the publish: left at the old release it compares against a version nobody can install and carries this release's own breaks forward as if new; moved before the publish it names a version that does not exist yet and the job cannot fetch it.

One thing this PR does not fix

main has no branch protection at all (gh api .../branches/main/protection → 404). So this gate — and every other — reports rather than blocks; a red PR can still be merged. The milestone is called "a gate that can fail", and a gate nothing requires is half of one. I have not changed repo settings, since that is your call, but reconverge has protection and this repo does not.

Closes #56

Signed-off-by: Vyncint Ng 115854244+vyncint@users.noreply.github.com

…ins drifting

Eleven crates are on crates.io at 2.1.0 -- the issue said ten -- and no
workflow ran cargo-semver-checks. Removing a `pub fn` from
`launchbound-space` shipped without a signal.

## Why the release type is forced

Measured, not assumed. Removing `Config::kernel` from launchbound-space
while bumping the manifest to 3.0.0 in the same PR:

  inferred              "0 checks: 0 pass, 254 skip"          exit 0
  --release-type patch  "1 major and 0 minor checks failed"   exit 100

cargo-semver-checks compares the manifest version to the baseline and runs
only the lints that bump would not already excuse. Declare the major bump in
the same commit as the break and it has nothing left to say -- the gate ends
up agreeing with whatever the PR claims about itself. `patch` means "no API
change of any kind is excused", so every lint fires.

`patch` does NOT forbid additions: cargo-semver-checks reports breaking
changes only. Verified against this branch, which carries #61's `pub use
Summary` and #62's `known_capabilities()`: 223 checks, 223 pass.

A deliberate break carries the new `breaking` label, which switches the job
to `major`.

Three of the eleven published crates are binary-only (cli, tui, runner) and
have no library API; the eight that do are covered by `--workspace`.
cargo-semver-checks arrives as a prebuilt binary through the same
install-action the rest of this workflow uses -- never built from source,
never restored from a cache.

## The pins were not only cosmetic

`[workspace.dependencies]` pinned the path crates at 2.0.0 while
`workspace.package.version` was 2.1.0, so `launchbound-prune` 2.1.0 declared
compatibility with a `launchbound-space` release it was never built against.
Harmless for a path build, which is why it survived the whole 2.1.0 line.

Not harmless for the next major: with the pins at 2.0.0, bumping the
workspace to 3.0.0 makes `cargo metadata` refuse outright --

  failed to select a version for the requirement `launchbound-build = "^2.0.0"`
  candidate versions found which didn't match: 3.0.0

-- which I hit while building the experiment above. The drift breaks the
release after next, not this one, which is the worst kind.

`scripts/check-versions.sh` (in `just ci`, beside `check-pins.sh` and
bash-3.2-portable for the macOS leg) asserts the workspace version, the
eight internal pins, and that no crate sets a literal version of its own.
Both failure shapes were exercised: a pin left at 2.0.0, and a crate
replacing `version.workspace = true` with a literal.

## docs/RELEASING.md

Step 1 now says to bump the pins with the version, and `baseline-version`
gets its own step after the publish: left at the old release it compares
against a version nobody can install and carries this release's own breaks
forward; moved before the publish it names one that does not exist yet.

Closes #56

Signed-off-by: Vyncint Ng <115854244+vyncint@users.noreply.github.com>
@vyncint
vyncint merged commit d593d92 into main Sep 9, 2026
9 checks passed
@vyncint
vyncint deleted the ci/semver-gate branch September 9, 2026 10:12
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.

No cargo-semver-checks runs anywhere, and the internal version pins lag the workspace

1 participant