fix(release): derive the npm dist-tag from spec/VERSION - #43
Merged
Conversation
The release workflow ran `npm publish npm-dist/*.tgz --access public` with no
`--tag`. npm refuses that outright for a prerelease, so the publish-npm job
would have failed on any 0.x-alpha release, and on a stable one it would have
moved `latest` silently. Confirmed by hand today:
npm error You must specify a tag using --tag when publishing a prerelease version.
Rather than hardcode a tag in the workflow or in publishConfig, where it would
need editing at every phase change, this derives it from the phase already
declared once in spec/VERSION.
- tools/npm_dist_tag.py prints the tag: latest for a stable version, otherwise
the prerelease phase (alpha, beta, rc, dev).
- check_versions.py now exposes CONTRACT_PATTERN so both tools read one grammar.
A second copy of that regex would be its own drift risk, which is the class of
bug #41 was.
- The build job publishes it as an output, so the artifact is still built once
and the publish job needs no checkout.
- RELEASING.md documents the derived tag and the by-hand equivalent.
Also records a real npm behaviour worth knowing: npm sets `latest` on a
package's first published version whatever --tag says. That happened on today's
bootstrap publish of 0.1.0-alpha.1 and corrects itself at the first stable
release.
Gates: all four checks, release-tag gate, 16 repository-gate tests including
three new ones asserting a prerelease never resolves to latest.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xyu1wLe68MPCKaqUXeEpXn
Merged
imran-siddique
added a commit
that referenced
this pull request
Sep 2, 2026
Release preparation for `0.1.0-alpha.2`, following step 1 and 2 of `RELEASING.md`. ## What is in this release All three already merged to `main`: | PR | | |---|---| | #41 | wire `spec_version` now matches `spec/VERSION` | | #42 | schema `$id` moved off `agentrust.io`, a domain we do not own | | #43 | npm dist-tag derived from `spec/VERSION` | ## Why a new version instead of releasing v0.1.0-alpha.1 npm already holds `0.1.0-alpha.1` from the bootstrap publish that `RELEASING.md` explicitly sanctions: > If npm does not expose publisher settings until the first version exists, bootstrap only that first package ownership using npm's interactive 2FA flow `release.yml` publishes both registries from one release event, and `release-assets` has `needs: [publish-pypi, publish-npm]`. Cutting `v0.1.0-alpha.1` would publish PyPI, fail `publish-npm` on the duplicate version, and skip attestation entirely. A fresh version lets one build feed both registries with provenance intact, which is what the pipeline exists to do. PyPI has never been published, so `0.1.0-alpha.2` will be its first version. ## On the moved digests `spec/VERSION` is the contract version and the source both package versions derive from, so bumping it changes the wire `spec_version`, which sits inside the RFC 8785 bytes that get hashed. Both goldens were regenerated from the code rather than hand-edited, and Python and TypeScript independently agree on the new tool-transcript hash. ## A dead test case, found while bumping `tests/test_repository_gates.py` had `"0.1.0-alpha.1"` as a dict key **twice**: ```python "0.1.0-alpha.1": ("0.1.0.dev0", "0.1.0-alpha.1.0"), "0.1.0-alpha.1": ("0.1.0a1", "0.1.0-alpha.1"), ``` Python keeps the last, so the first entry was silently discarded and the `dev` phase has never been tested. Its npm spelling was wrong as well: `0.1.0-alpha.1.0` is not something `ecosystem_versions` can produce for any input. Restored as a real `0.1.0-dev` case, with both alpha spellings now asserted. ## Verification | Gate | Result | |---|---| | `check_versions` | `contract=0.1.0-alpha.2 python=0.1.0a2 npm=0.1.0-alpha.2` | | `check_release_tag v0.1.0-alpha.2` | pass | | `npm_dist_tag` | `alpha` | | `check_schemas` / `check_typescript_schemas` / `check_otel_compatibility` | pass | | Conformance fixtures | 13/13 | | Python tests | 111 pass | | TypeScript tests | 41 pass | ## After this merges 1. Configure the npm trusted publisher for `@agentrust-io/telemetry`, now possible because the package exists. 2. Create the GitHub release tagged `v0.1.0-alpha.2` targeting `main`. 3. Approve the `pypi` and `npm` deployment jobs. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01Xyu1wLe68MPCKaqUXeEpXn Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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
release.ymlpublished npm with no dist-tag:npm refuses that outright for a prerelease. Confirmed by hand during today's bootstrap publish:
So
publish-npmwould have failed on any0.xprerelease. On a stable version it would have succeeded and movedlatestwithout anyone deciding to.The change
The phase is already declared exactly once, in
spec/VERSION. Rather than hardcode a tag in the workflow or inpublishConfig, where it needs editing at every phase change, this derives it:spec/VERSION1.0.0latest0.1.0-alpha.1alpha0.2.0-beta.3beta1.0.0-rc.1rc0.1.0-devdevtools/npm_dist_tag.pyprints the tag.check_versions.pynow exposesCONTRACT_PATTERN, so both tools read one grammar. A second copy of that regex would be its own drift risk, which is exactly the class of bug fix(contract): align wire spec_version with spec/VERSION before first publish #41 fixed.buildjob publishes it as an output, so the distribution is still built once andpublish-npmneeds no checkout of its own.RELEASING.mddocuments the derived tag and the by-hand equivalent,npm publish --tag "$(python tools/npm_dist_tag.py)".One npm behaviour worth recording
npm sets
lateston a package's first published version regardless of--tag. Today's bootstrap publish of0.1.0-alpha.1went out as--tag alphaand the registry still shows:That is expected on a first publish, npm protects
latestfrom removal, and it corrects itself when the first stable version ships. Noted inRELEASING.mdso it is not mistaken for a bug later.Verification
check_versions/check_schemas/check_typescript_schemas/check_otel_compatibilitycheck_release_tag v0.1.0-alpha.1release.ymlpython tools/npm_dist_tag.pyon currentspec/VERSIONalphaThe new tests assert the mapping across all five phases, that a bad version raises, and the invariant that matters: a prerelease never resolves to
latest.🤖 Generated with Claude Code
https://claude.ai/code/session_01Xyu1wLe68MPCKaqUXeEpXn