anchor guard: fail a PR that changes a lockstep-stamped tree without naming the anchor - #356
Merged
Merged
Conversation
…ree without naming the anchor Every artifact ships at one shared version held in @smooai/smooth-operator (typescript/package.json). Changesets versions only npm packages, so sync-versions.mjs stamps that number onto every other published manifest. A changeset that does not name the anchor therefore republishes nothing outside npm — silently, with a green release. That is how #348 (.NET file transfer) and #352 (.NET skill resolution) both landed on main and sat unpublished: their changesets named @smooai/smooth-operator-server, which is the TYPESCRIPT server package sitting one word away from the anchor. A downstream consumer found it two days later by reading the NuGet and filed a request to build what we had already shipped. Rule: touching a stamped tree WITH a changeset requires one changeset naming the anchor. Conditioned on having a changeset at all, so docs/test-only PRs stay quiet — a guard that cries wolf gets ignored, and then it protects nothing. sync-versions.mjs is now importable (stamping runs only when invoked directly) so the guard derives the stamped trees from the very list that does the stamping. A new target cannot be added without the guard learning about it. Verified by replay against real history: the guard FIRES on #348's actual commit and PASSES #346's (the TS sibling that correctly named both packages). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012iM1Q8JC1H83H2FXQVQNs9
|
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 problem
Every smooth-operator artifact — npm, NuGet, PyPI, crates.io — ships at one shared version, and that version lives in exactly one place:
@smooai/smooth-operator(typescript/package.json). Changesets natively versions only npm packages, soscripts/sync-versions.mjsstamps that single number onto every other published manifest.The consequence people miss: a changeset that doesn't name the anchor cannot republish anything outside npm. And one word away sits
@smooai/smooth-operator-server— the TypeScript server. If you just changed the .NET server, that name is the intuitive pick and the wrong one.It fails silently and late: CI green, PR merged, release workflow succeeds, artifact never republished.
…-server+ anchor…-serveronly…-serveronlyNobody noticed until a downstream consumer read the published NuGet two days later and filed a request asking us to build a feature that had been on
mainthe whole time. #354 unstuck the release; this PR stops it recurring.The rule
Conditioned on having a changeset at all, deliberately. Docs- and test-only PRs legitimately have none, and a guard that cries wolf on them gets ignored — at which point it protects nothing. The bug being caught is not "no changeset", it's "a changeset naming the wrong package".
No second source of truth
sync-versions.mjsis now importable — stamping runs only when it's invoked directly — so the guard derives the stamped trees from the very list that does the stamping. Add a target there and the guard learns about it for free; there's no path list to keep in sync.typescript/is deliberately not a stamped tree (changesets versions it natively), so ordinary TS PRs are never forced to bump the anchor.Verification
Replayed against real history — the strongest check available:
Plus
scripts/check-changeset-anchor.test.mjs— 8 assertions run as the first CI step, so a broken guard fails loudly rather than silently passing everything. The load-bearing one isreplays the #348 bug; if that ever goes green the guard is decorative. Also covers path-segment awareness (dotnet/server/srcgenmust not match thedotnet/server/srctree) and:in changeset prose not being mistaken for a package line.End-to-end failure path exercised on a scratch commit: exit 1 with a message that names the trees, lists what the changesets did name, and calls out the
…-serverconfusion by name.Not path-filtered, on purpose: the guard needs the whole diff to know which trees a PR touched, and a path filter would make it skip exactly the PRs it exists to judge.
🤖 Generated with Claude Code
https://claude.ai/code/session_012iM1Q8JC1H83H2FXQVQNs9