ci(release): fail a consumer-visible change that ships no version bump - #702
Merged
Conversation
Twice in two days a change reached main declaring a version the registry already held: v0.118.0's bump was never tagged, and 0.119.0's agent-eval peer floor moved from >=0.139.2 to >=0.140.1 with no bump at all. publish.yml skips a version already on the registry, so neither could be corrected by re-tagging and neither turned a build red. Nothing asserted that a consumer-visible change arrives with a version bump. check:version-bump compares every publishable manifest against the merge base and fails when any field npm copies into the published tarball moved while version stood still. version is not part of the compared surface — it is the payment. catalog: specifiers are resolved through pnpm-workspace.yaml first. That indirection is how the drift stayed invisible: "@tangle-network/agent-knowledge": "catalog:" is byte-identical across the change that moved the installed version 7.0.3 -> 7.0.4, so no manifest diff shows anything. Resolving it also covers bench, whose published 0.4.9 still carries agent-eval 0.135.2 against a catalog that now says 0.140.1. Wired into the ci job beside check:publish-workflow. That job's checkout gains fetch-depth: 0 so the merge base exists, and the step passes the pull request's base sha. A named base that will not resolve is an error, never a silent pass. tests/version-bump-check.test.ts holds the calibration: seven cases over a throwaway git repo covering both rejection directions, the byte-identical catalog move, and the two shapes that must NOT fire — a source-only edit and a devDependency/script change.
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.
The defect, twice in two days
>=0.139.2->>=0.140.1with no bumppublish.ymlskips a version already on the registry, so neither could be fixed by re-tagging. In the 0.119.0 case the journal-envelope fix could not reach a single consumer until #700 cut 0.120.0.Root cause: nothing asserted that a consumer-visible change arrives with a version bump.
The check
scripts/check-version-bump.mjscompares every publishable manifest against the merge base and fails when any field npm copies into the published manifest moved whileversiondid not go up —name,private,type,main,module,types,typings,browser,exports,imports,typesVersions,bin,files,directories,engines,os,cpu,license,sideEffects,publishConfig,dependencies,optionalDependencies,peerDependencies,peerDependenciesMeta,bundleDependencies, and the install-lifecyclescripts(preinstall/install/postinstall, which run on a consumer's machine).versionis not part of the compared surface; it is the payment.catalog:specifiers are compared by what they RESOLVE to, throughpnpm-workspace.yaml. That indirection is how the drift stayed invisible:"@tangle-network/agent-knowledge": "catalog:"is byte-identical across the change that moved the installed version7.0.3->7.0.4, so a manifest diff shows nothing at all. Only the resolved version is compared, so moving a dependency between the default catalog, a named catalog, and an identical literal pin stays silent.Packages are keyed by name, not path, so relocating a directory still compares against what that name already published.
Resolving the pin also covers
bench. Worth knowing:@tangle-network/agent-bench@0.4.9is published againstagent-eval 0.135.2while the catalog now says0.140.1— the same drift, five minors wide, still unshipped. This check would have caught it; fixing it is a separate release.Wiring
In the
cijob besidecheck:publish-workflow. That job's checkout gainsfetch-depth: 0so the merge base exists.The base is
origin/$GITHUB_BASE_REF— the base branch, deliberately not the webhook'spull_request.base.sha. Apull_requestrun checks out a merge ref that GitHub recomputes against the current base tip, so the frozen sha can name a commit that is no longer the merge parent; a release someone else merged in that window would then pay for this pull request's unpaid change.Everything unknown fails closed: a base that will not resolve, a base-carrying event with no base at all (merge queue, workflow edit), and an unresolvable
catalog:pin are all errors, never silent passes.Two scope boundaries, stated so neither reads as an oversight
workspace:specifiers are not resolved. One resolves to a sibling in the same commit, so it moves only when that sibling's version moves — which this check already requires to be paid for, on that sibling. Resolving it here would demand a second bump for a change already accounted for, and would fire on every routine release.src/change that rewrites all ofdist/is allowed with no bump; releases, not this check, decide when code ships.Calibration — a guard that cannot fail is not a guard
Proven by construction against this repo, then frozen into
tests/version-bump-check.test.ts(18 cases over throwaway git repos).Fires — peer range moved, version stood still:
Passes — same change, paid for:
Fires on a downgrade — a version change is not payment if it lands somewhere the registry may already hold:
Fires on the real #697 shape — only the catalog pin moved;
package.jsonandbench/package.jsonare byte-identical:Does not fire on an ordinary source-only edit (nor on a devDependency or non-install script change):
The tests are load-bearing, not decorative: dropping
peerDependenciesfrom the compared fields and emptying thecatalog:resolver turns 4 of them red; restoring the guard returns them all green.Reviewed adversarially before opening
A second model was pointed at the first draft with instructions to REFUTE it, and produced twelve demonstrated defects. Every false-pass it found is fixed and now has a test:
workspace:/frozen-base-sha comparison points, a downgrade counting as payment, an emptyGITHUB_BASE_SHAfalling through to a green no-op,typesVersions/directories/bundleDependencies/install-scripts absent from the compared fields, a moved package treated as new, andprivate: truehiding a package from inspection. So are the false-fails most likely to get the check disabled: a catalog/literal refactor at the same version, a reorderedfiles, a!-excluded workspace directory, a package nested deeper than one level under**, and a missingpnpm-workspace.yamlcrashing with a raw stack.