diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index baa5587..6431b4f 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -33,7 +33,12 @@ jobs:
- name: Checkout development family sources outside runtime Cargo scope
env:
GH_TOKEN: ${{ github.token }}
- run: node scripts/checkout-family-sources.mjs --development --parent "$GITHUB_WORKSPACE/.."
+ DEVELOPMENT_REF: ${{ github.head_ref || github.ref_name }}
+ run: >-
+ node scripts/checkout-family-sources.mjs
+ --development
+ --development-ref "$DEVELOPMENT_REF"
+ --parent "$GITHUB_WORKSPACE/.."
- uses: dtolnay/rust-toolchain@1.95
with: { targets: "wasm32-unknown-unknown,wasm32-wasip1" }
- uses: jetli/wasm-pack-action@v0.4.0
@@ -43,7 +48,7 @@ jobs:
- name: Install build tool and local interop type package
run: >-
npm install --ignore-scripts --no-save node-gyp@13.0.1
- ../vinary-tree-interop/bindings/javascript
+ "@vinary-tree/vinary-tree-interop@file:../vinary-tree-interop/bindings/javascript"
- name: Build and test integrated backends
env:
VINARY_TREE_INTEROP_ROOT: ${{ github.workspace }}/../vinary-tree-interop
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 0c18969..334107e 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -68,7 +68,7 @@ jobs:
shell: bash
run: >-
npm install --ignore-scripts --no-save node-gyp@13.0.1
- ../vinary-tree-interop/bindings/javascript
+ "@vinary-tree/vinary-tree-interop@file:../vinary-tree-interop/bindings/javascript"
- name: Build relocatable SDK and native addon
shell: bash
env:
@@ -109,7 +109,9 @@ jobs:
with: { version: v0.13.1 }
- run: bash .github/setup-binaryen.sh
- name: Install local interop type package
- run: npm install --ignore-scripts --no-save ../vinary-tree-interop/bindings/javascript
+ run: >-
+ npm install --ignore-scripts --no-save
+ "@vinary-tree/vinary-tree-interop@file:../vinary-tree-interop/bindings/javascript"
- name: Build and test both WebAssembly hosts
env:
VINARY_TREE_INTEROP_ROOT: ${{ github.workspace }}/../vinary-tree-interop
diff --git a/.gitignore b/.gitignore
index 0da9f42..c06095a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,5 @@
# C8 property tests install fast-check into an isolated package so npm never has
-# to resolve the unpublished @vinary-tree/interop sibling. The lockfile is
+# to resolve the unpublished @vinary-tree/vinary-tree-interop sibling. The lockfile is
# committed for reproducibility; the installed tree is not.
test-property/node_modules/
node_modules/
diff --git a/README.md b/README.md
index f33f6e5..090086b 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# Vinary Tree JavaScript runtime
-`@vinary-tree/vinary-tree` is the single-instance JavaScript runtime for
+`@vinary-tree/javascript-runtime` is the single-instance JavaScript runtime for
libdictenstein, liblevenshtein, lling-llang, and duallity. It lets their
project-specific facades exchange retained dictionaries and weighted
finite-state transducers (WFSTs) without copying or loading incompatible native
@@ -8,7 +8,7 @@ runtimes.
| Release property | Value |
|---|---|
-| Candidate | `4.0.0-rc.4` |
+| Candidate | `4.0.0-rc.5` |
| npm dist-tag | `next` |
| Node | 22.14 or newer |
| Backends | Native N-API, browser WebAssembly, Node WASI |
@@ -17,12 +17,12 @@ runtimes.
## Install the release candidate
```sh
-npm install @vinary-tree/vinary-tree@next
+npm install @vinary-tree/javascript-runtime@next
```
Use the package root for Node's native backend,
-`@vinary-tree/vinary-tree/wasm` in a browser, or
-`@vinary-tree/vinary-tree/wasi` when Node/WASI filesystem preopens are needed.
+`@vinary-tree/javascript-runtime/wasm` in a browser, or
+`@vinary-tree/javascript-runtime/wasi` when Node/WASI filesystem preopens are needed.
Applications normally install a project facade rather than importing the
shared runtime directly.
@@ -32,7 +32,7 @@ Dictionaries follow the synchronous `Map` vocabulary while retaining explicit
native lifetime control:
```js
-import { libdictenstein, liblevenshtein } from "@vinary-tree/vinary-tree";
+import { libdictenstein, liblevenshtein } from "@vinary-tree/javascript-runtime";
using dictionary = libdictenstein.dynamicDawg("unicode");
dictionary.set("cat", 1n).set("cot", 2n).set("cut", null);
@@ -60,9 +60,9 @@ Garbage collection is exceptional-path containment, not resource scheduling.
| Import | Backend | Intended host |
|---|---|---|
-| `@vinary-tree/vinary-tree` | Prebuilt N-API addon | Node services and tools |
-| `@vinary-tree/vinary-tree/wasm` | `wasm-bindgen` module | Browsers and web workers |
-| `@vinary-tree/vinary-tree/wasi` | Explicit WASI linear-memory ABI | Node with preopened persistent storage |
+| `@vinary-tree/javascript-runtime` | Prebuilt N-API addon | Node services and tools |
+| `@vinary-tree/javascript-runtime/wasm` | `wasm-bindgen` module | Browsers and web workers |
+| `@vinary-tree/javascript-runtime/wasi` | Explicit WASI linear-memory ABI | Node with preopened persistent storage |
All three expose the same snapshot, collection, query, and WFST semantics.
Resources carry an immutable runtime identity; passing a resource between
@@ -93,6 +93,7 @@ directories.
- [Architecture and invariants](docs/architecture.md)
- [Testing strategy and property models](docs/testing.md)
- [Release order, platform matrix, and rollback](docs/releasing.md)
+- [npm coordinate migration and compatibility](docs/npm-coordinate-migration.md)
- [History-preserving extraction provenance](docs/extraction-provenance.md)
The project-specific packages own their idiomatic user APIs. This repository
diff --git a/docs/diagrams/release-dependency-graph.puml b/docs/diagrams/release-dependency-graph.puml
index fb7b0fa..8c53830 100644
--- a/docs/diagrams/release-dependency-graph.puml
+++ b/docs/diagrams/release-dependency-graph.puml
@@ -4,18 +4,18 @@ skinparam backgroundColor #FAFAFA
skinparam shadowing false
skinparam ArrowColor #455A64
skinparam defaultFontName DejaVu Sans
-title Immutable 4.0.0-rc.4 JavaScript release dependency graph
+title Immutable 4.0.0-rc.5 JavaScript release dependency graph
rectangle "llattice\n0.1.0" as lattice #ECEFF1
-rectangle "vinary-tree-interop\n4.0.0-rc.4" as interop #FFF59D
-rectangle "libdictenstein\n4.0.0-rc.4" as dict #C8E6C9
-rectangle "liblevenshtein\n4.0.0-rc.4" as lev #C8E6C9
-rectangle "lling-llang\n4.0.0-rc.4" as lling #C8E6C9
-rectangle "duallity\n4.0.0-rc.4" as dual #C8E6C9
+rectangle "@vinary-tree/vinary-tree-interop\n4.0.0-rc.5 · npm next" as interop #FFF59D
+rectangle "libdictenstein\n4.0.0-rc.5" as dict #C8E6C9
+rectangle "liblevenshtein\n4.0.0-rc.5" as lev #C8E6C9
+rectangle "lling-llang\n4.0.0-rc.5" as lling #C8E6C9
+rectangle "duallity\n4.0.0-rc.5" as dual #C8E6C9
rectangle "platform native prebuilds\n+ browser WASM + WASI" as artifacts #B2DFDB
-rectangle "@vinary-tree/vinary-tree\n4.0.0-rc.4 · npm next" as runtime #80CBC4
-rectangle "project npm facades\n4.0.0-rc.4 · npm next" as facades #C5CAE9
-rectangle "liblevenshtein compatibility facade\n4.0.0-rc.4 · npm next" as legacy #FFCCBC
+rectangle "@vinary-tree/javascript-runtime\n4.0.0-rc.5 · npm next" as runtime #80CBC4
+rectangle "project npm facades\n4.0.0-rc.5 · npm next" as facades #C5CAE9
+rectangle "liblevenshtein compatibility facade\n4.0.0-rc.5 · npm next" as legacy #FFCCBC
lattice --> dict
interop --> dict
diff --git a/docs/diagrams/release-dependency-graph.svg b/docs/diagrams/release-dependency-graph.svg
index 80d660e..cf747cd 100644
--- a/docs/diagrams/release-dependency-graph.svg
+++ b/docs/diagrams/release-dependency-graph.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/docs/diagrams/runtime-architecture.puml b/docs/diagrams/runtime-architecture.puml
index 0d28335..11b6405 100644
--- a/docs/diagrams/runtime-architecture.puml
+++ b/docs/diagrams/runtime-architecture.puml
@@ -16,7 +16,7 @@ package "Project facades" #E8EAF6 {
component "@vinary-tree/duallity" as dualFacade #C5CAE9
}
-package "@vinary-tree/vinary-tree 4.0.0-rc.4" #E0F2F1 {
+package "@vinary-tree/javascript-runtime 4.0.0-rc.5" #E0F2F1 {
component "Runtime identity\n(resource compatibility gate)" as identity #80CBC4
component "Node N-API" as napi #B2DFDB
component "Browser WASM" as wasm #B2DFDB
@@ -26,7 +26,7 @@ package "@vinary-tree/vinary-tree 4.0.0-rc.4" #E0F2F1 {
component "liblevenshtein" as lev #C8E6C9
component "lling-llang" as lling #C8E6C9
component "duallity" as dual #C8E6C9
- component "vinary-tree-interop\nretained resources" as interop #FFF59D
+ component "@vinary-tree/vinary-tree-interop\nretained resources" as interop #FFF59D
}
}
diff --git a/docs/diagrams/runtime-architecture.svg b/docs/diagrams/runtime-architecture.svg
index 4031d4d..3d5a897 100644
--- a/docs/diagrams/runtime-architecture.svg
+++ b/docs/diagrams/runtime-architecture.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/docs/npm-coordinate-migration.md b/docs/npm-coordinate-migration.md
new file mode 100644
index 0000000..440ec50
--- /dev/null
+++ b/docs/npm-coordinate-migration.md
@@ -0,0 +1,78 @@
+# npm coordinate migration for the JavaScript runtime
+
+## Canonical public identity
+
+The standalone runtime repository publishes
+`@vinary-tree/javascript-runtime`. The name states what the package is and
+matches the source repository; it is not a family-wide umbrella package. The
+runtime hosts native N-API, browser WebAssembly (WASM), and WebAssembly System
+Interface (WASI) backends for the project-specific facades.
+
+RC4 was published under `@vinary-tree/vinary-tree`. That coordinate is an
+immutable historical publication mistake. It does not rename the Vinary Tree
+project, and it must not appear in new manifests, imports, examples, or release
+graphs.
+
+| Role | Coordinate | Policy from RC5 onward |
+|---|---|---|
+| Canonical shared runtime | `@vinary-tree/javascript-runtime` | Publish and consume directly. |
+| Canonical resource contracts | `@vinary-tree/vinary-tree-interop` | Exact RC dependency of the runtime. |
+| Legacy RC4 runtime | `@vinary-tree/vinary-tree` | Preserve immutable bytes; deprecate only after canonical public-install verification. |
+
+## Single source of truth
+
+[`release/version.json`](../release/version.json) owns
+`coordinates.npmPackage`, the private property-test package name, the RC
+version, and exact upstream versions and source tags. The synchronizer derives
+package manifests from those fields and rejects legacy, malformed, or drifted
+coordinates. In particular, it rejects an accidental concatenation such as
+`@vinary-tree/javascript-runtime-interop`; the runtime and interop package are
+two independent coordinates joined by an exact dependency edge.
+
+## Safe migration algorithm
+
+The following literate procedure distinguishes immutable artifacts from
+mutable registry pointers:
+
+```text
+build native, WASM, and WASI artifacts from the exact RC5 source graph
+pack the canonical runtime and inspect its package name and dependency keys
+publish canonical RC5 with provenance under the `next` dist-tag
+install from the public registry in an empty project
+exercise all three backends and cross-project retained-resource handoff
+promote canonical `latest` only after the full train passes
+deprecate the legacy coordinate with a canonical replacement message
+read back package metadata and dist-tags without local workspace overlays
+```
+
+If verification fails, leave the immutable version in place, keep or restore
+the previous distribution tag, correct the source, and issue the next unused
+candidate. Never overwrite a version or silently redirect module resolution.
+
+## Consumer migration
+
+Applications should normally install a project facade. Direct runtime users
+replace both the package and any backend subpath:
+
+```sh
+npm install @vinary-tree/javascript-runtime@next
+```
+
+```js
+import runtime from "@vinary-tree/javascript-runtime";
+import wasmRuntime from "@vinary-tree/javascript-runtime/wasm";
+import wasiRuntime from "@vinary-tree/javascript-runtime/wasi";
+```
+
+The runtime's public semantics do not change: one package instance owns the
+runtime identity, and resources from another instance are rejected before
+native dispatch.
+
+## Trust boundary
+
+The package is published through npm's repository-bound trusted publisher and
+the protected `npm` environment. Public read-back must match the expected
+repository, exact tag, version, coordinate, dependency coordinate, and
+provenance before distribution tags or deprecations are changed. Authentication
+material must never be written into package metadata, generated artifacts,
+logs, or documentation.
diff --git a/docs/releasing.md b/docs/releasing.md
index 7e4b9cb..108fbe2 100644
--- a/docs/releasing.md
+++ b/docs/releasing.md
@@ -1,4 +1,4 @@
-# Releasing `@vinary-tree/vinary-tree`
+# Releasing `@vinary-tree/javascript-runtime`
The runtime is a downstream assembly artifact. It must never publish before
the exact Rust crates and shared interop package it consumes are publicly
@@ -9,11 +9,14 @@ resolvable.
## Release identity
`release/version.json` is authoritative. For this train, every Rust and npm
-coordinate is `4.0.0-rc.4`, and npm publication uses the `next` distribution
+coordinate is `4.0.0-rc.5`, and npm publication uses the `next` distribution
tag. npm's first-publication behavior assigned `latest` to the inert `0.0.0`
bootstrap reservation. After the OIDC-published runtime passes installed
native, WASM, and WASI smoke tests, retarget the new scoped package's `latest`
-pointer to `4.0.0-rc.4`, remove `bootstrap`, and deprecate `0.0.0`.
+pointer to `4.0.0-rc.5`, remove `bootstrap`, and deprecate `0.0.0`.
+The [npm coordinate migration](npm-coordinate-migration.md) records the exact
+identity correction and the compatibility policy for the immutable RC4
+artifacts.
The runtime's exact Rust requirements deliberately reject a mixed family. The
development overlay changes only source location; it does not relax versions.
@@ -24,33 +27,10 @@ lockfile. Every component lock must remain byte-for-byte unchanged.
## Exact-tag workflow protocol
-Pushing the annotated tag creates only the immutable source ref. Release
+Pushing the annotated `v4.0.0-rc.5` tag creates only the immutable source ref. Release
validation and publication are explicit manual dispatches so the complete
cross-project tag graph and public prerequisites can be established first.
-The canonical tag predates protected GitHub-release approval. Append-only
-corrective source `v4.0.0-rc.4-release.1` added those authority boundaries.
-Its validate-only matrix exposed a separate topology defect: family repositories
-were cloned beneath the runtime checkout, so owner-crate Cargo builds inherited
-the runtime's `[patch.crates-io]` overlay and rejected their otherwise unchanged
-lockfiles. No package-registry job ran.
-
-Append-only corrective source `v4.0.0-rc.4-release.2` fixes the topology and
-records every family checkout in `release/version.json` as an exact immutable
-tag. Native, browser-WASM, WASI, and development integration jobs now place
-family owners beside the runtime checkout. Both the checkout helper and local
-layout validation reject nested owner roots. Package identity remains
-`4.0.0-rc.4`; neither prior tag is moved.
-
-The exact-source gate then exposed a malformed CRLF blob in interop's Windows
-Gradle launcher: a fresh checkout of the otherwise exact tag appeared locally
-modified after Git applied the declared `text eol=crlf` filter. Interop
-corrective source `v4.0.0-rc.4-release.3` stores canonical LF object data and
-retains CRLF checkout semantics. Runtime corrective source
-`v4.0.0-rc.4-release.3` changes only the immutable source map to consume that
-interop correction. It preserves the package version, platform topology, and
-all other owner refs from runtime release.2.
-
The release workflow has two fail-closed modes. `validate-only` builds all six
native prebuilds plus browser WebAssembly and WASI, assembles the npm tarball,
verifies its contents, and creates the checksummed GitHub prerelease. `npm`
@@ -60,12 +40,12 @@ environment.
```bash
gh workflow run release.yml \
--repo vinary-tree/javascript-runtime \
- --ref v4.0.0-rc.4-release.3 \
+ --ref v4.0.0-rc.5 \
-f registry=validate-only
gh workflow run release.yml \
--repo vinary-tree/javascript-runtime \
- --ref v4.0.0-rc.4-release.3 \
+ --ref v4.0.0-rc.5 \
-f registry=npm
```
@@ -79,6 +59,31 @@ The `github-release` environment has the same required reviewer and `v*` tag
policy as npm but stores no secret; it gates only the job-scoped
`GITHUB_TOKEN` used to create the checksummed prerelease.
+### RC4 historical source record
+
+The RC4 canonical tag predated protected GitHub-release approval. Append-only
+corrective source `v4.0.0-rc.4-release.1` added those authority boundaries.
+Its validate-only matrix exposed a separate topology defect: family repositories
+were cloned beneath the runtime checkout, so owner-crate Cargo builds inherited
+the runtime's `[patch.crates-io]` overlay and rejected their otherwise unchanged
+lockfiles. No package-registry job ran.
+
+Append-only corrective source `v4.0.0-rc.4-release.2` fixed the topology and
+recorded every family checkout in `release/version.json` as an exact immutable
+tag. Native, browser-WASM, WASI, and development integration jobs place family
+owners beside the runtime checkout. Both the checkout helper and local layout
+validation reject nested owner roots. RC4 package identity remained immutable;
+neither prior tag was moved.
+
+The exact-source gate then exposed a malformed CRLF blob in interop's Windows
+Gradle launcher: a fresh checkout of the otherwise exact tag appeared locally
+modified after Git applied the declared `text eol=crlf` filter. Interop
+corrective source `v4.0.0-rc.4-release.3` stores canonical LF object data and
+retains CRLF checkout semantics. Runtime corrective source
+`v4.0.0-rc.4-release.3` changed only the immutable source map to consume that
+interop correction. It preserved the package version, platform topology, and
+all other owner refs from runtime release.2.
+
## Required order
1. Publish independent leaf crate `llattice` at its own `0.1.0` version if the
@@ -86,13 +91,14 @@ policy as npm but stores no secret; it gates only the job-scoped
2. Publish `vinary-tree-interop` and verify installation from each supported
registry coordinate.
3. Publish `libdictenstein`, `liblevenshtein`, `lling-llang`, and `duallity`
- crates at `4.0.0-rc.4` in dependency order.
+ crates at `4.0.0-rc.5` in dependency order.
4. Build the runtime's native prebuild matrix from the exact component tags in
`release/version.json`; build browser WASM and WASI from the same source map.
5. Merge the platform artifacts, run package-content and installed-tarball
- smoke tests, then publish `@vinary-tree/vinary-tree@4.0.0-rc.4` with `next`.
+ smoke tests, then publish `@vinary-tree/javascript-runtime@4.0.0-rc.5` with
+ `next`.
6. Publish project-specific npm facades against that exact runtime.
-7. Publish the unscoped `liblevenshtein@4.0.0-rc.4` compatibility facade with
+7. Publish the unscoped `liblevenshtein@4.0.0-rc.5` compatibility facade with
`next`; do not change the legacy `latest` tag.
## Native platform matrix
diff --git a/index.d.ts b/index.d.ts
index 058a8d6..cfaa826 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -1,6 +1,6 @@
///
-import type { DictionaryResource, RuntimeIdentity, UnitDomain } from "@vinary-tree/interop";
+import type { DictionaryResource, RuntimeIdentity, UnitDomain } from "@vinary-tree/vinary-tree-interop";
export type Algorithm = "standard" | "transposition" | "merge-and-split" | "damerau-levenshtein";
export type QueryOrder = "traversal" | "distance-then-term";
diff --git a/native.cjs b/native.cjs
index 44ec61f..3c64136 100644
--- a/native.cjs
+++ b/native.cjs
@@ -10,7 +10,7 @@ try {
ffi = require(addon);
} catch (cause) {
throw new Error(
- `@vinary-tree/vinary-tree has no usable native addon for ${platform}; `
+ `@vinary-tree/javascript-runtime has no usable native addon for ${platform}; `
+ "install a supported prebuilt package or build the addon from source",
{ cause },
);
diff --git a/native.mjs b/native.mjs
index 23a301c..020bbe5 100644
--- a/native.mjs
+++ b/native.mjs
@@ -20,7 +20,7 @@ try {
ffi = require(fileURLToPath(addon));
} catch (cause) {
throw new Error(
- `@vinary-tree/vinary-tree has no usable native addon for ${platform}; `
+ `@vinary-tree/javascript-runtime has no usable native addon for ${platform}; `
+ "install a supported prebuilt package or build the addon from source",
{ cause },
);
diff --git a/package.json b/package.json
index 981d172..cf50389 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
- "name": "@vinary-tree/vinary-tree",
- "version": "4.0.0-rc.4",
+ "name": "@vinary-tree/javascript-runtime",
+ "version": "4.0.0-rc.5",
"description": "Single-instance JavaScript runtime for related Vinary Tree libraries",
"author": {
"name": "Dylon Edwards",
@@ -17,7 +17,7 @@
"node": ">=22.14"
},
"dependencies": {
- "@vinary-tree/interop": "4.0.0-rc.4"
+ "@vinary-tree/vinary-tree-interop": "4.0.0-rc.5"
},
"types": "./index.d.ts",
"exports": {
diff --git a/release/version.json b/release/version.json
index 68f25b9..793db16 100644
--- a/release/version.json
+++ b/release/version.json
@@ -1,10 +1,14 @@
{
"schemaVersion": 1,
"component": "javascript-runtime",
- "canonical": "4.0.0-rc.4",
- "npm": "4.0.0-rc.4",
+ "canonical": "4.0.0-rc.5",
+ "coordinates": {
+ "npmPackage": "@vinary-tree/javascript-runtime",
+ "npmPropertyTestPackage": "@vinary-tree/javascript-runtime-property-tests"
+ },
+ "npm": "4.0.0-rc.5",
"distTag": "next",
- "sourceTag": "v4.0.0-rc.4-release.3",
+ "sourceTag": "v4.0.0-rc.5",
"nativePrebuilds": [
"linux-x64",
"linux-arm64",
@@ -14,20 +18,20 @@
"win32-arm64"
],
"dependencies": {
- "duallity": "4.0.0-rc.4",
+ "duallity": "4.0.0-rc.5",
"llattice": "0.1.0",
- "libdictenstein": "4.0.0-rc.4",
- "liblevenshtein": "4.0.0-rc.4",
- "lling-llang": "4.0.0-rc.4",
- "vinary-tree-interop": "4.0.0-rc.4",
- "@vinary-tree/interop": "4.0.0-rc.4"
+ "libdictenstein": "4.0.0-rc.5",
+ "liblevenshtein": "4.0.0-rc.5",
+ "lling-llang": "4.0.0-rc.5",
+ "vinary-tree-interop": "4.0.0-rc.5",
+ "@vinary-tree/vinary-tree-interop": "4.0.0-rc.5"
},
"sourceRefs": {
- "vinary-tree-interop": "v4.0.0-rc.4-release.3",
+ "vinary-tree-interop": "v4.0.0-rc.5",
"llattice": "v0.1.0",
- "libdictenstein": "v4.0.0-rc.4-release.1",
- "liblevenshtein-rust": "v4.0.0-rc.4-release.3",
- "lling-llang": "v4.0.0-rc.4-release.1",
- "duallity": "v4.0.0-rc.4-release.1"
+ "libdictenstein": "v4.0.0-rc.5",
+ "liblevenshtein-rust": "v4.0.0-rc.5",
+ "lling-llang": "v4.0.0-rc.5",
+ "duallity": "v4.0.0-rc.5"
}
}
diff --git a/rust/Cargo.lock b/rust/Cargo.lock
index 5333966..517b07c 100644
--- a/rust/Cargo.lock
+++ b/rust/Cargo.lock
@@ -180,7 +180,7 @@ dependencies = [
[[package]]
name = "duallity"
-version = "4.0.0-rc.4"
+version = "4.0.0-rc.5"
dependencies = [
"libdictenstein",
"liblevenshtein",
@@ -274,7 +274,7 @@ checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
[[package]]
name = "libdictenstein"
-version = "4.0.0-rc.4"
+version = "4.0.0-rc.5"
dependencies = [
"arc-swap",
"bincode-next",
@@ -296,7 +296,7 @@ dependencies = [
[[package]]
name = "liblevenshtein"
-version = "4.0.0-rc.4"
+version = "4.0.0-rc.5"
dependencies = [
"ahash",
"arc-swap",
@@ -317,7 +317,7 @@ version = "0.1.0"
[[package]]
name = "lling-llang"
-version = "4.0.0-rc.4"
+version = "4.0.0-rc.5"
dependencies = [
"ahash",
"blake3",
@@ -866,11 +866,11 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "vinary-tree-interop"
-version = "4.0.0-rc.4"
+version = "4.0.0-rc.5"
[[package]]
name = "vinary-tree-js-runtime"
-version = "4.0.0-rc.4"
+version = "4.0.0-rc.5"
dependencies = [
"console_error_panic_hook",
"duallity",
diff --git a/rust/Cargo.toml b/rust/Cargo.toml
index 65e2b9b..5e59739 100644
--- a/rust/Cargo.toml
+++ b/rust/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "vinary-tree-js-runtime"
-version = "4.0.0-rc.4"
+version = "4.0.0-rc.5"
edition = "2021"
rust-version = "1.95"
license-file = "../LICENSE"
@@ -12,11 +12,11 @@ publish = false
crate-type = ["cdylib", "rlib"]
[dependencies]
-liblevenshtein = { version = "=4.0.0-rc.4", features = ["bindings-phonetic"] }
-libdictenstein = { version = "=4.0.0-rc.4", features = ["bindings-core"] }
-lling-llang = { version = "=4.0.0-rc.4", default-features = false, features = ["bindings-core"] }
-duallity = { version = "=4.0.0-rc.4", features = ["bindings-core"] }
-vinary-tree-interop = "=4.0.0-rc.4"
+liblevenshtein = { version = "=4.0.0-rc.5", features = ["bindings-phonetic"] }
+libdictenstein = { version = "=4.0.0-rc.5", features = ["bindings-core"] }
+lling-llang = { version = "=4.0.0-rc.5", default-features = false, features = ["bindings-core"] }
+duallity = { version = "=4.0.0-rc.5", features = ["bindings-core"] }
+vinary-tree-interop = "=4.0.0-rc.5"
wasm-bindgen = "=0.2.100"
js-sys = "=0.3.77"
console_error_panic_hook = "0.1"
diff --git a/rust/src/lib.rs b/rust/src/lib.rs
index 875570c..5f6d3b2 100644
--- a/rust/src/lib.rs
+++ b/rust/src/lib.rs
@@ -1,5 +1,5 @@
#![deny(missing_docs)]
-//! Shared WebAssembly implementation behind `@vinary-tree/vinary-tree`.
+//! Shared WebAssembly implementation behind `@vinary-tree/javascript-runtime`.
#[cfg(all(feature = "browser", target_arch = "wasm32", target_os = "unknown"))]
mod browser;
diff --git a/scripts/checkout-family-sources.mjs b/scripts/checkout-family-sources.mjs
index d21df4c..dc5eb56 100644
--- a/scripts/checkout-family-sources.mjs
+++ b/scripts/checkout-family-sources.mjs
@@ -7,15 +7,6 @@ import { fileURLToPath } from "node:url";
import { readReleaseModel, sourceOwners, validateSourceRefs } from "./release-source-refs.mjs";
const runtimeRoot = dirname(dirname(fileURLToPath(import.meta.url)));
-const developmentRefs = Object.freeze({
- "vinary-tree-interop": "master",
- llattice: "v0.1.0",
- libdictenstein: "master",
- "liblevenshtein-rust": "master",
- "lling-llang": "release/4.0.0-rc.4",
- duallity: "release/4.0.0-rc.4",
-});
-
export function validateCheckoutParent(value) {
const parent = resolve(value);
if (parent !== dirname(runtimeRoot)) {
@@ -37,6 +28,37 @@ function output(command, args) {
return result.stdout.trim();
}
+export function selectDevelopmentRef(candidate) {
+ if (typeof candidate !== "string" || !candidate.startsWith("release/")) return "master";
+ const components = candidate.split("/");
+ const forbidden = /[\x00-\x20\x7f~^:?*\[\\]/;
+ if (
+ candidate === "@"
+ || candidate.startsWith("-")
+ || candidate.endsWith(".")
+ || candidate.includes("..")
+ || candidate.includes("@{")
+ || forbidden.test(candidate)
+ || components.some((component) => (
+ component.length === 0
+ || component.startsWith(".")
+ || component.startsWith("-")
+ || component.endsWith(".lock")
+ ))
+ ) {
+ throw new Error(`invalid coordinated development ref: ${candidate}`);
+ }
+ return candidate;
+}
+
+function developmentSourceRefs(candidate) {
+ const coordinated = selectDevelopmentRef(candidate);
+ return Object.freeze(Object.fromEntries(sourceOwners.map((owner) => [
+ owner,
+ owner === "llattice" ? "v0.1.0" : coordinated,
+ ])));
+}
+
function checkout(parent, refs, immutable) {
for (const owner of sourceOwners) {
const target = resolve(parent, owner);
@@ -63,6 +85,20 @@ function selfTest() {
}
if (!rejected) throw new Error(`unsafe checkout parent passed validation: ${forbidden}`);
}
+ if (selectDevelopmentRef(undefined) !== "master") throw new Error("missing event ref must use master");
+ if (selectDevelopmentRef("feature/local") !== "master") throw new Error("feature refs must use master siblings");
+ if (selectDevelopmentRef("release/4.0.0-rc.5") !== "release/4.0.0-rc.5") {
+ throw new Error("coordinated release ref was not preserved");
+ }
+ for (const malformed of ["release/-option", "release/../escape", "release/bad ref", "release/x.lock"]) {
+ let rejected = false;
+ try {
+ selectDevelopmentRef(malformed);
+ } catch {
+ rejected = true;
+ }
+ if (!rejected) throw new Error(`malformed coordinated ref passed validation: ${malformed}`);
+ }
console.log("external family-checkout topology tests passed");
}
@@ -83,6 +119,8 @@ if (argumentsByName.has("--self-test")) {
} else {
const parent = validateCheckoutParent(argumentsByName.get("--parent") ?? dirname(runtimeRoot));
const development = argumentsByName.has("--development");
- const refs = development ? developmentRefs : validateSourceRefs(readReleaseModel());
+ const refs = development
+ ? developmentSourceRefs(argumentsByName.get("--development-ref"))
+ : validateSourceRefs(readReleaseModel());
checkout(parent, refs, !development);
}
diff --git a/scripts/release-source-refs.mjs b/scripts/release-source-refs.mjs
index 449c078..5409125 100644
--- a/scripts/release-source-refs.mjs
+++ b/scripts/release-source-refs.mjs
@@ -60,31 +60,31 @@ export function readReleaseModel() {
function selfTest() {
const valid = {
dependencies: {
- "vinary-tree-interop": "4.0.0-rc.4",
+ "vinary-tree-interop": "4.0.0-rc.5",
llattice: "0.1.0",
- libdictenstein: "4.0.0-rc.4",
- liblevenshtein: "4.0.0-rc.4",
- "lling-llang": "4.0.0-rc.4",
- duallity: "4.0.0-rc.4",
+ libdictenstein: "4.0.0-rc.5",
+ liblevenshtein: "4.0.0-rc.5",
+ "lling-llang": "4.0.0-rc.5",
+ duallity: "4.0.0-rc.5",
},
sourceRefs: {
- "vinary-tree-interop": "v4.0.0-rc.4-release.3",
+ "vinary-tree-interop": "v4.0.0-rc.5",
llattice: "v0.1.0",
- libdictenstein: "v4.0.0-rc.4-release.1",
- "liblevenshtein-rust": "v4.0.0-rc.4-release.3",
- "lling-llang": "v4.0.0-rc.4-release.1",
- duallity: "v4.0.0-rc.4-release.1",
+ libdictenstein: "v4.0.0-rc.5",
+ "liblevenshtein-rust": "v4.0.0-rc.5",
+ "lling-llang": "v4.0.0-rc.5",
+ duallity: "v4.0.0-rc.5",
},
};
validateSourceRefs(valid);
const mutations = [
(model) => delete model.sourceRefs.duallity,
- (model) => { model.sourceRefs.unexpected = "v4.0.0-rc.4"; },
+ (model) => { model.sourceRefs.unexpected = "v4.0.0-rc.5"; },
(model) => { model.sourceRefs.libdictenstein = "master"; },
(model) => { model.sourceRefs.libdictenstein = "208d9cd6ccfc4993acddd3c166bb314049dfb258"; },
(model) => { model.sourceRefs.libdictenstein = "v4.0.0-rc.3"; },
- (model) => { model.sourceRefs.libdictenstein = "v4.0.0-rc.4-release.0"; },
- (model) => { model.sourceRefs.libdictenstein = "v4.0.0-rc.4-release.next"; },
+ (model) => { model.sourceRefs.libdictenstein = "v4.0.0-rc.5-release.0"; },
+ (model) => { model.sourceRefs.libdictenstein = "v4.0.0-rc.5-release.next"; },
];
for (const mutate of mutations) {
const malformed = structuredClone(valid);
diff --git a/scripts/sync-release-version.mjs b/scripts/sync-release-version.mjs
index 82a3556..fc0e34e 100644
--- a/scripts/sync-release-version.mjs
+++ b/scripts/sync-release-version.mjs
@@ -1,4 +1,4 @@
-import { readFileSync, writeFileSync } from "node:fs";
+import { readdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import { validateSourceRefs } from "./release-source-refs.mjs";
@@ -9,6 +9,20 @@ const write = process.argv.includes("--write");
if (!/^\d+\.\d+\.\d+-rc\.\d+$/.test(model.canonical)) {
throw new Error(`canonical version is not a numbered RC: ${model.canonical}`);
}
+const npmPackage = model.coordinates?.npmPackage;
+const npmPropertyTestPackage = model.coordinates?.npmPropertyTestPackage;
+const legacyInteropPackage = ["@vinary-tree", "interop"].join("/");
+if (npmPackage !== "@vinary-tree/javascript-runtime") {
+ throw new Error(
+ "release/version.json coordinates.npmPackage must be @vinary-tree/javascript-runtime",
+ );
+}
+if (npmPropertyTestPackage !== "@vinary-tree/javascript-runtime-property-tests") {
+ throw new Error(
+ "release/version.json coordinates.npmPropertyTestPackage must be " +
+ "@vinary-tree/javascript-runtime-property-tests",
+ );
+}
const nativePrebuilds = [
"linux-x64",
"linux-arm64",
@@ -52,17 +66,25 @@ function rewriteCandidateTokens(paths) {
const packageJson = updateJson("package.json", (value) => {
if (write) {
+ value.name = npmPackage;
value.version = model.npm;
- value.dependencies["@vinary-tree/interop"] = model.dependencies["@vinary-tree/interop"];
+ delete value.dependencies[legacyInteropPackage];
+ value.dependencies["@vinary-tree/vinary-tree-interop"] =
+ model.dependencies["@vinary-tree/vinary-tree-interop"];
value.publishConfig.tag = model.distTag;
}
});
const propertyPackage = updateJson("test-property/package.json", (value) => {
- if (write) value.version = model.canonical;
+ if (write) {
+ value.name = npmPropertyTestPackage;
+ value.version = model.canonical;
+ }
});
const propertyLock = updateJson("test-property/package-lock.json", (value) => {
if (write) {
+ value.name = npmPropertyTestPackage;
value.version = model.canonical;
+ value.packages[""].name = npmPropertyTestPackage;
value.packages[""].version = model.canonical;
}
});
@@ -105,7 +127,6 @@ if (write) writeFileSync(cargoLockPath, cargoLock);
rewriteCandidateTokens([
".github/workflows/ci.yml",
"README.md",
- "docs/releasing.md",
"docs/diagrams/release-dependency-graph.puml",
"docs/diagrams/runtime-architecture.puml",
]);
@@ -114,7 +135,12 @@ const failures = [];
const expect = (name, actual, wanted) => {
if (actual !== wanted) failures.push(`${name}: expected ${wanted}, got ${actual}`);
};
-expect("corrective source tag", model.sourceTag, `v${model.canonical}-release.3`);
+const escapedCanonical = model.canonical.replaceAll(".", "\\.");
+if (!new RegExp(`^v${escapedCanonical}(?:-release\\.[1-9][0-9]*)?$`).test(model.sourceTag)) {
+ failures.push(
+ `source tag must be v${model.canonical} or an append-only numbered correction, got ${model.sourceTag}`,
+ );
+}
validateSourceRefs(model);
const releaseWorkflow = readFileSync(join(root, ".github", "workflows", "release.yml"), "utf8");
for (const marker of [
@@ -124,11 +150,20 @@ for (const marker of [
]) {
if (!releaseWorkflow.includes(marker)) failures.push(`release workflow is missing ${marker}`);
}
+expect("npm package", packageJson.name, npmPackage);
expect("npm", packageJson.version, model.npm);
-expect("npm interop", packageJson.dependencies["@vinary-tree/interop"], model.dependencies["@vinary-tree/interop"]);
+expect(
+ "npm interop",
+ packageJson.dependencies["@vinary-tree/vinary-tree-interop"],
+ model.dependencies["@vinary-tree/vinary-tree-interop"],
+);
+expect("legacy npm interop dependency", packageJson.dependencies[legacyInteropPackage], undefined);
expect("npm dist-tag", packageJson.publishConfig.tag, model.distTag);
+expect("property package name", propertyPackage.name, npmPropertyTestPackage);
expect("property package", propertyPackage.version, model.canonical);
+expect("property lock name", propertyLock.name, npmPropertyTestPackage);
expect("property lock", propertyLock.version, model.canonical);
+expect("property lock root name", propertyLock.packages[""].name, npmPropertyTestPackage);
expect("property lock root", propertyLock.packages[""].version, model.canonical);
expect("Rust package", cargo.match(/^version = "([^"]+)"/m)?.[1], model.canonical);
for (const dependency of ["duallity", "libdictenstein", "liblevenshtein", "lling-llang"]) {
@@ -152,5 +187,78 @@ for (const [name, version] of lockedReleasePackages) {
version,
);
}
+
+const ignoredCoordinateTrees = new Set([
+ ".git",
+ ".build",
+ "build",
+ "dist",
+ "generated",
+ "native/prebuilds",
+ "node_modules",
+ "target",
+]);
+const coordinateMigrationRecord = "docs/npm-coordinate-migration.md";
+const forbiddenCoordinates = [
+ ["legacy interop coordinate", legacyInteropPackage],
+ ["legacy runtime coordinate", ["@vinary-tree", "vinary-tree"].join("/")],
+ [
+ "malformed runtime/interop composition",
+ ["@vinary-tree", "javascript-runtime-interop"].join("/"),
+ ],
+];
+
+function coordinateViolation(source) {
+ for (const [label, coordinate] of forbiddenCoordinates) {
+ const escaped = coordinate.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
+ const match = new RegExp(`${escaped}(?![A-Za-z0-9._-])`).exec(source);
+ if (match !== null) return { label, index: match.index };
+ }
+ return undefined;
+}
+
+for (const coordinate of [
+ "@vinary-tree/vinary-tree-interop",
+ "@vinary-tree/javascript-runtime",
+ "@vinary-tree/javascript-runtime/wasm",
+]) {
+ expect(`coordinate gate accepts ${coordinate}`, coordinateViolation(coordinate), undefined);
+}
+for (const [label, coordinate] of forbiddenCoordinates) {
+ expect(
+ `coordinate gate rejects ${label}`,
+ coordinateViolation(`"${coordinate}@4.0.0-rc.4"`)?.label,
+ label,
+ );
+}
+
+function validateNpmCoordinates(directory = root, relativeDirectory = "") {
+ for (const entry of readdirSync(directory)) {
+ const relative = relativeDirectory === "" ? entry : `${relativeDirectory}/${entry}`;
+ if ([...ignoredCoordinateTrees].some((tree) => relative === tree || relative.startsWith(`${tree}/`))) {
+ continue;
+ }
+ const absolute = join(directory, entry);
+ if (statSync(absolute).isDirectory()) {
+ validateNpmCoordinates(absolute, relative);
+ continue;
+ }
+ if (relative === coordinateMigrationRecord) continue;
+ let source;
+ try {
+ source = readFileSync(absolute, "utf8");
+ } catch {
+ continue;
+ }
+ if (source.includes("\0")) continue;
+ const violation = coordinateViolation(source);
+ if (violation !== undefined) {
+ const line = source.slice(0, violation.index).split("\n").length;
+ failures.push(`${relative}:${line}: ${violation.label} is forbidden`);
+ }
+ }
+}
+
+validateNpmCoordinates();
if (failures.length > 0) throw new Error(failures.join("\n"));
console.log(`release versions agree with ${model.canonical}`);
diff --git a/scripts/verify-package.mjs b/scripts/verify-package.mjs
index 294f32d..d087929 100644
--- a/scripts/verify-package.mjs
+++ b/scripts/verify-package.mjs
@@ -1,4 +1,14 @@
import { spawnSync } from "node:child_process";
+import { readFileSync } from "node:fs";
+import { dirname, join } from "node:path";
+import { fileURLToPath } from "node:url";
+
+const root = dirname(dirname(fileURLToPath(import.meta.url)));
+const release = JSON.parse(readFileSync(join(root, "release", "version.json"), "utf8"));
+const packageName = release.coordinates?.npmPackage;
+if (packageName !== "@vinary-tree/javascript-runtime") {
+ throw new Error("release manifest does not name the canonical JavaScript runtime package");
+}
const result = spawnSync("npm", ["pack", "--dry-run", "--ignore-scripts", "--json"], {
encoding: "utf8",
@@ -11,7 +21,7 @@ const report = Array.isArray(parsed)
? parsed[0]
: parsed.files
? parsed
- : parsed["@vinary-tree/vinary-tree"] ?? Object.values(parsed)[0];
+ : parsed[packageName] ?? Object.values(parsed)[0];
if (!report || !Array.isArray(report.files)) {
throw new Error("npm pack returned no package file report");
}
diff --git a/test-property/native.property.test.mjs b/test-property/native.property.test.mjs
index 55bf3d0..2b85348 100644
--- a/test-property/native.property.test.mjs
+++ b/test-property/native.property.test.mjs
@@ -1,7 +1,7 @@
// C8 native property-based tests for the javascript-runtime facade (fast-check).
//
// This suite lives in an isolated package whose only dependency is fast-check,
-// so `npm install` never has to resolve the unpublished @vinary-tree/interop
+// so `npm install` never has to resolve the unpublished @vinary-tree/vinary-tree-interop
// sibling that the umbrella package declares. It imports the native runtime by
// relative path (`../native.mjs`), which loads the prebuilt N-API addon.
//
diff --git a/test-property/package-lock.json b/test-property/package-lock.json
index 982c951..ccb68e0 100644
--- a/test-property/package-lock.json
+++ b/test-property/package-lock.json
@@ -1,12 +1,12 @@
{
- "name": "@vinary-tree/vinary-tree-property-tests",
- "version": "4.0.0-rc.4",
+ "name": "@vinary-tree/javascript-runtime-property-tests",
+ "version": "4.0.0-rc.5",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
- "name": "@vinary-tree/vinary-tree-property-tests",
- "version": "4.0.0-rc.4",
+ "name": "@vinary-tree/javascript-runtime-property-tests",
+ "version": "4.0.0-rc.5",
"devDependencies": {
"fast-check": "^4.0.0"
}
diff --git a/test-property/package.json b/test-property/package.json
index 9a0b899..b0f1d4a 100644
--- a/test-property/package.json
+++ b/test-property/package.json
@@ -1,6 +1,6 @@
{
- "name": "@vinary-tree/vinary-tree-property-tests",
- "version": "4.0.0-rc.4",
+ "name": "@vinary-tree/javascript-runtime-property-tests",
+ "version": "4.0.0-rc.5",
"private": true,
"type": "module",
"description": "C8 fast-check property tests for the native runtime in an isolated private package",
diff --git a/wasi.d.ts b/wasi.d.ts
index a61fff6..06ba575 100644
--- a/wasi.d.ts
+++ b/wasi.d.ts
@@ -1,4 +1,4 @@
-import type { RuntimeIdentity, UnitDomain } from "@vinary-tree/interop";
+import type { RuntimeIdentity, UnitDomain } from "@vinary-tree/vinary-tree-interop";
import type {
Dictionary, QueryCursor, Algorithm, LlingLlangNamespace, DuallityNamespace,
} from "./index.js";