docs(exploration): 0370 — version number discipline and renumbering - #585
Merged
Conversation
Follow-on from #571/#583, which made every human-readable surface say "alpha" while @xnetjs/core still says 2.5.0. The headline finding is that the appealing fix is unavailable: npm never lets a version string be reused, and since npm 11 (which we pin) publish refuses to move `latest` backwards. The unpublish escape needs <300 downloads/week and core pulls 2,120. A true 0.x needs a scope rename — @xnet/* looks free, and that option is costed rather than dismissed. The more useful finding is that the number was never the problem. Four verified enforcement gaps: - the Stop hook checks a changeset EXISTS, never that the bump matches the diff — and no JS tool can (cargo-semver-checks has no equivalent) - schema-check.yml is a stub: placeholder schemas both sides, zeroed fallback, so breakingChanges is always 0 - ~25 protocol constants are hand-maintained with no link to semver, and one has already drifted — TS says protocol version 4, Swift says 3 - ignored packages (hub, editor) ship wire-visible changes with no release intent Recommends keeping the numbers, writing STABILITY.md, and scoping semver to a surface we can hold via api-extractor release tags — a smaller honest promise over a bigger dishonest one. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: xNet Test <test@xnet.dev>
Contributor
|
✓ Changelog fragment found — thanks! |
Contributor
|
Preview removed for PR #585. |
…e idea seriously Two corrections and one real addition. Correction 1 — namespaces. The draft treated "@xnet/core is 404" as evidence the scope was free. It isn't: unscoped package names and org-derived scopes are separate namespaces, and @Xnet is TAKEN as an org (confirmed by attempt). So the rename that would have justified its cost is unavailable. @xnetts and @xnetfyi are now secured as fallbacks, but both read worse than @xnetjs, so Option B moves from "costed alternative" to "held, revisit only if xnet is acquired". Adds the deprecation path (npm deprecate accepts a range) since that was the stated worry about renaming. Correction 2 — the xnet package is 4 years dormant, not 7: created 2019-10-24, last published 2022-05-25, sole maintainer camilotd, 1 download/week. Notes the hypothesis that this package is what reserves the @Xnet org — if true, acquiring it unlocks the scope and is worth much more than the name. Addition — Option D, the prerelease flag, which the first draft deferred too quickly. The instinct is right, but there's a prerequisite: 2.5.0 came from ordinary bumps, not from deliberate breaking changes, so "we ship a lot of breaking changes" is currently UNMEASURED. A prerelease channel pointed at unmeasured churn just relabels it. Tier the surface first, count for six weeks, then let the number pick between pre mode (D1), a manual dist-tag (D2), or simply accepting frequent majors (D3). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: xNet Test <test@xnet.dev>
crs48
added a commit
that referenced
this pull request
Jul 19, 2026
…ty, packaging validity (#587) Implements phases 1–4 of [exploration 0370](docs/explorations/). Phases 5–6 are deliberately left open — they're time-based (a six-week measurement) or external (emailing a stranger, contacting npm support), plus one judgement call that's yours. ## The framing The version number can't be made honest — npm forbids renumbering downward — so this writes the promise down and builds the gates that make it enforceable. ## What landed - **`STABILITY.md`** — what carries a compatibility promise, what doesn't, how protocol versions differ from package semver. Linked from README and `CONTRIBUTING.md#versioning`. - **Swift protocol drift fixed** (`3` → `4`) plus `protocol-version-parity.test.ts` covering Rust and Swift. `protocolVersion` sits inside the hashed canonical object, so Swift was producing different change hashes than the rest of the fleet. It survived because Swift is only `swift build`-ed in CI, never conformance-tested. - **`schema-check.yml` deleted** — it wrote placeholder `{"schemas":[]}` for *both* sides and substituted a zeroed diff on error, so it reported `breakingChanges: 0` unconditionally. - **`PROTOCOL_SENTINELS` in the Stop hook** — a wire-visible constant moving under a patch/minor is now blocked. - **api-extractor** on react/core/data/sync, reports committed under `packages/*/etc/`, CODEOWNERS-gated, drift gate in CI. - **`publint`** over all 19 publishable packages. ## Two real bugs the new gates found Both were shipped and broken before this PR: 1. **`types` was ordered after `import` in 48 export subpaths across 19 packages.** Export conditions are order-sensitive, so TypeScript could mis-resolve types for every published package. 2. **`@xnetjs/data/portability` didn't resolve at all.** The package advertised the subpath but it was never added to the build — that's the `.xnetpack` codec we tell users to back up with. ## Corrections I made to the exploration's own plan Following the checklist literally would have introduced bugs: - **`HubConnection.swift`'s `protocolVersion: 1` is correct** — it's the hub *WebSocket handshake* version, a different number sharing a field name. Changing it to 4 would cause a spurious `version-mismatch`. Left alone, documented in place. - **Removing `hub`/`editor` from the changeset ignore list is a no-op** — both are `private: true`, already outside the hook. Recorded as a known gap instead. - **`LWW_TIEBREAK_KEY_VERSION` must not be collapsed into `CURRENT_PROTOCOL_VERSION`** — same value today, different meanings. My first cut of the parity test asserted equality, which would have gone spuriously red on the next protocol bump. Now asserts `LWW <= CURRENT`. - **`api-extractor run` alone cannot gate** — it reports a changed signature as a *warning* and exits 0, the exact "gate that can't fail" the exploration criticises. The gate runs `--local` and uses `git status` (not `git diff`, which ignores untracked files). ## One validation deliberately left failing **"Deleting an `@internal` export does not trip the gate."** It does. api-extractor treats untagged exports as `@public`, and I tagged 5 symbols out of ~372 — so today these reports are a **change-visibility** gate, not a tiering gate. Real value on its own (a removed export can't land silently), but not the scoped promise the doc describes. `STABILITY.md` says so plainly rather than implying the surface is already narrow. ## For you to decide - **`useXNet`** is tagged `@internal ... Not part of public API` in source but listed as **stable root contract** in `packages/react/README.md`. Left untouched — that's a maintainer call. - **C2** (ship `3.0.0` as a stability re-declaration) — your judgement. - The `xnet` acquisition email and the npm-support question are yours to send. ## Verification - `pnpm turbo run typecheck` — 85/85 pass - `pnpm test` — 10,784 pass, **1 pre-existing failure**: `tests/reliability/restore/restore-drill.test.ts` fails locally with an 8 KB pipe truncation. Not touched by this branch, and the nightly soak was already red on main this morning; #585 (docs-only) passed all required test shards, so it's environment-specific. - Every gate verified in both directions — blocks the bad case, passes the good one, no false positive. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
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.
Follow-on from #571 / #583, which made every human-readable surface say "alpha" while
@xnetjs/corestill says2.5.0.The short version
The renumber isn't available, and the number was never the problem.
npm-release.yml:40) publish refuses to movelatestbackwards.@xnetjs/core@0.5.0is publishable as a string but incoherent as a release.@xnet/corereturns 404, so it looks free. Costed as a real option rather than dismissed, since nobody's meaningfully depending on us yet.^0.2.3and~0.2.3are identical. 0.x buys safety through total friction, not signalling.Four enforcement gaps, all verified against the code
assert-coverage.mjs:95-105— regex forname: patch|minor|majorschema-check.ymlis a stub that can never fail:33,:41placeholder{"schemas":[]}both sides;:49zeroed fallbackCURRENT_PROTOCOL_VERSION = 4vs SwiftprotocolVersion: Int64 = 3hub(Docker image),editor(content-v4) both in.changesetignoreThe Swift drift is a live correctness bug, not just hygiene — it's the enforcement gap made visible.
Also relevant: JS has no tool that detects a breaking change.
cargo-semver-checkshas no equivalent; semver-ts.org's own tooling appendix recommends hand-written assertions. That's whyCLAUDE.md's "bump from the diff" is a human instruction.Recommendation
Keep the numbers. Write
STABILITY.md. Scope semver to a surface we can actually hold via api-extractor release tags (@public/@beta/@alpha/@internal) — a smaller honest promise beats a bigger dishonest one. Fix the drift, delete or implement the stub. Then decide the rename separately, because a rename without gates lands us back at@xnet/2.5.0in a year.Verification
Two things flagged as unproven in the doc: whether the
@xnetscope is genuinely claimable (an unpublished-but-reserved scope looks identical from outside), and what the 2,120/week download traffic actually is.Docs only — no changeset needed.
🤖 Generated with Claude Code