fix(contract): align wire spec_version with spec/VERSION before first publish - #41
Merged
Merged
Conversation
… publish spec/VERSION, pyproject and package.json all say 0.1.0-alpha.1, while the wire contract said 0.1.0-dev. envelope.schema.json pinned it as a const, so every event had to carry a version string that appears nowhere in the packaging. An adopter reading the release version and emitting it as spec_version would have been rejected by the validator. Replaces 0.1.0-dev with 0.1.0-alpha.1 across the schema const, all three schema copies, the 13 conformance fixtures, the golden compatibility events, both SDKs, the examples and four docs. The two golden digests move because spec_version is inside the RFC 8785 bytes that are hashed. Verified the change is attributable to nothing else: recomputing entry 0 with the version reverted reproduces the previous digest exactly. Python and TypeScript independently produce the same new tool-transcript hash, which is the cross-language parity the goldens exist to protect. Nothing is published on npm or PyPI yet, so this costs no adopter a migration. Gates: check_versions, check_schemas, check_typescript_schemas, check_otel_compatibility, 13/13 conformance fixtures, 108 Python tests, 41 TypeScript tests, both examples. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xyu1wLe68MPCKaqUXeEpXn
imran-siddique
added a commit
that referenced
this pull request
Sep 2, 2026
## The problem All eight schemas, in all three copies, identify themselves under `https://agentrust.io`: ```json "$id": "https://agentrust.io/telemetry/v0.1/schema/envelope.schema.json" ``` That domain is not ours. It serves 114 bytes: ```html <!DOCTYPE html><html><head><script>window.onload=function(){window.location.href="/lander"}</script></head></html> ``` A parked-domain lander. It also answers **HTTP 200 for every path**, so anything resolving a schema `$id` gets HTML with a success code rather than a 404 it could act on. Our real site is `agentrust-io.com`, served from GitHub Pages. Before this PR, `agentrust-io.com` appeared in **zero** files in the repository. This is the same class of defect trace-spec corrected in its v0.1 to v0.2 cutover, where the concern was letting "a record minted under a domain we do not own continue to pass as conformant." ## The change The `$id` host in `spec/schema/`, `src/agentrust_telemetry/schemas/` and `packages/typescript/schemas/`, plus the id template in `validation.ts`. One line per file, 25 files. Relative `$ref`s are untouched and still resolve, because swapping the host keeps the base hierarchical. ## Why this needs no hosting change to be correct Neither SDK dereferences `$id`. Both read the schemas from local files and register them in their validator by `$id`, so nothing depends on the URL being fetchable. Serving the eight files at that path is worth doing and is not a prerequisite for this to be right. Even unserved, an identifier on a domain we control is strictly better than one on a parked domain that returns 200 and HTML. ## Considered and rejected: a tag URI `tag:agentrust-io.com,2026:telemetry/...` would have matched the identifier style trace-spec v0.2 uses. It does not work here, tested both ways: - **Python** (`referencing`): relative `$ref`s cannot resolve against a non-hierarchical `tag:` base. Making all cross-schema refs absolute does fix it, and passes 6/6 valid and 7/7 invalid fixtures. - **Ajv**, used by the TypeScript SDK: cannot resolve `tag:` URIs at all, even fully absolute ones. `can't resolve reference tag:agentrust-io.com,2026:telemetry/v0.1/schema/common.schema.json#/$defs/event_id` So a tag URI would break the TypeScript SDK. trace-spec uses one as a claim **value** (`eat_profile`), not as a JSON Schema `$id` with refs hanging off it, which is why the parallel does not carry. ## Verification | Gate | Result | |---|---| | `check_versions` / `check_schemas` / `check_typescript_schemas` / `check_otel_compatibility` | pass | | Conformance fixtures | 13/13 | | Python tests | 108 pass, 2 skipped | | TypeScript tests | 41 pass | | `examples/governed_workflow.py` | pass | | Residual bare `agentrust.io` | none | Branched from `main`, so it is independent of #41 and the two can land in either order. 🤖 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>
imran-siddique
added a commit
that referenced
this pull request
Sep 2, 2026
## The problem `release.yml` published npm with no dist-tag: ```yaml run: npm publish npm-dist/*.tgz --access public ``` npm refuses that outright for a prerelease. Confirmed by hand during today's bootstrap publish: ``` npm error You must specify a tag using --tag when publishing a prerelease version. ``` So `publish-npm` would have failed on any `0.x` prerelease. On a stable version it would have succeeded and moved `latest` without anyone deciding to. ## The change The phase is already declared exactly once, in `spec/VERSION`. Rather than hardcode a tag in the workflow or in `publishConfig`, where it needs editing at every phase change, this derives it: | `spec/VERSION` | dist-tag | |---|---| | `1.0.0` | `latest` | | `0.1.0-alpha.1` | `alpha` | | `0.2.0-beta.3` | `beta` | | `1.0.0-rc.1` | `rc` | | `0.1.0-dev` | `dev` | - **`tools/npm_dist_tag.py`** prints the tag. - **`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 exactly the class of bug #41 fixed. - **The `build` job publishes it as an output**, so the distribution is still built once and `publish-npm` needs no checkout of its own. - **`RELEASING.md`** documents the derived tag and the by-hand equivalent, `npm publish --tag "$(python tools/npm_dist_tag.py)"`. ## One npm behaviour worth recording npm sets `latest` on a package's **first** published version regardless of `--tag`. Today's bootstrap publish of `0.1.0-alpha.1` went out as `--tag alpha` and the registry still shows: ``` dist-tags: { alpha: '0.1.0-alpha.1', latest: '0.1.0-alpha.1' } ``` That is expected on a first publish, npm protects `latest` from removal, and it corrects itself when the first stable version ships. Noted in `RELEASING.md` so it is not mistaken for a bug later. ## Verification | Gate | Result | |---|---| | `check_versions` / `check_schemas` / `check_typescript_schemas` / `check_otel_compatibility` | pass | | `check_release_tag v0.1.0-alpha.1` | pass | | Repository gate tests | 16 pass, including 3 new | | `release.yml` | parses as valid YAML | | `python tools/npm_dist_tag.py` on current `spec/VERSION` | `alpha` | The 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.com/claude-code) https://claude.ai/code/session_01Xyu1wLe68MPCKaqUXeEpXn Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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
spec/VERSION,pyproject.tomlandpackages/typescript/package.jsonall declare0.1.0-alpha.1. The wire contract declared0.1.0-dev, andenvelope.schema.jsonpinned it as aconst:So an adopter who read the release version and emitted it as
spec_versionwould have been rejected by our own validator. The only correct value was a string that appears nowhere in the packaging.tools/check_versions.pydid not catch this, because it derives the Python and npm versions fromspec/VERSIONand never looks at the wire const.The change
0.1.0-devto0.1.0-alpha.1across 31 files: the schema const, all three schema copies (spec/schema,src/agentrust_telemetry/schemas,packages/typescript/schemas), the 13 conformance fixtures, the golden compatibility events, both SDKs, the examples, and four docs.On the two golden digests that moved
spec_versionis inside the RFC 8785 canonical bytes that get hashed, so the evidence-chain and tool-transcript goldens change. These were regenerated from the code, not hand-edited, and the change was verified attributable to nothing else:f31be297...exactly.sha256:3ef76a71..., which is the cross-language parity these goldens exist to protect.Verification
check_versions/check_schemas/check_typescript_schemas/check_otel_compatibilityexamples/governed_workflow.py,examples/manual_governance.py0.1.0-devNothing is published on npm or PyPI yet, so this costs no adopter a migration. That is also why it is worth landing before the first publish rather than after.
Not in this PR
All eight schemas carry
$id: https://agentrust.io/telemetry/v0.1/schema/.... That domain is not ours; it serves a parked-domain lander and returns HTTP 200 for every path, so a resolver gets HTML with a success code. The real site isagentrust-io.com. This is the same class of defect trace-spec corrected in its v0.1 to v0.2 cutover. Raised separately because choosing the replacement identifier is a design decision, not a fix.🤖 Generated with Claude Code
https://claude.ai/code/session_01Xyu1wLe68MPCKaqUXeEpXn