From d75e85b61a38d9469ce3e2c588ee2d6073cd7833 Mon Sep 17 00:00:00 2001 From: Lann Martin Date: Sat, 22 Aug 2026 23:38:46 -0400 Subject: [PATCH] polyengine 0.5.0; @polymorph/websocket 0.5.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adopt the A22 protocol/runtime split: the published host module depends on @polyengine/protocol alone (jsr:@polyengine/protocol@^0.2.2). websocket.ts's vocabulary imports (ComponentException, brand predicates, STREAM, Stream/StreamSource types) move to @polyengine/protocol, which is where they live as of runtime 0.5.0 — the embedder's A9 courtesy re-exports are gone. Application legs bump the five lockstep pins to 0.5.0 and exact-pin @polyengine/protocol@0.2.2; the demo's isComponentException import moves to protocol; browser-bundle-entry re-exports protocol alongside the embedder so bundle consumers keep seeing vocabulary off one namespace. exam-polyengine learns the two-line world: one resolved runtime-family version, one resolved protocol version, and js/polyengine names no @polyengine/runtime specifier. Gates: polyengine-module-check (deno check + 14 tests), exam-polyengine, driver deno task check, conformance run-polyengine (55/55). The WPT parity recipe fails identically on pre-migration HEAD (pre-existing groups-manifest resolution issue, unrelated); its polyengine carrier bundle step succeeds under the new pins. --- AGENTS.md | 27 ++++---- conformance/driver-ct/polyengine/README.md | 55 +++++++++------- .../polyengine/browser-bundle-entry.ts | 8 ++- conformance/driver-ct/polyengine/deno.json | 13 ++-- conformance/driver-ct/polyengine/deno.lock | 66 +++++++++++-------- examples/polyengine-demo/run.ts | 3 +- js/componentize/wpt/justfile | 6 +- js/polyengine/README.md | 22 ++++--- js/polyengine/deno.json | 6 +- js/polyengine/deno.lock | 15 ++--- js/polyengine/tests/websocket_test.ts | 2 +- js/polyengine/websocket.ts | 2 +- justfile | 42 ++++++++---- 13 files changed, 154 insertions(+), 113 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 89425ed..9ba0333 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -36,18 +36,21 @@ Layout (each directory's justfile module in parentheses): (`conformance/driver-ct/polyengine/`, the WPT parity round trip, the demo). Its knobs are exported functions (`configure`, `setMaxInboundBufferBytes`, `setConnectTimeoutMs`, `setCloseTimeoutMs`); - the module reads no ambient configuration. The - polyengine release is pinned in TWO import maps — `js/polyengine/deno.json` and - `conformance/driver-ct/polyengine/deno.json` — as exact-pinned JSR - prereleases (`jsr:@polyengine/@0.3.0-pre.g`; a version names one - upstream commit) which must agree on the `@polyengine/runtime` version - (polyengine's `wasi` module imports `@polyengine/runtime/embedder` by bare - specifier internally, so a drift would load the embedder module twice - and split the runtime/translator plan-format pairing; value recognition - itself is brand-based — polyengine's `@polyengine/protocol` predicates, not - `instanceof` — and survives a multi-copy graph); the root - justfile's `exam-polyengine` recipe (CI-wired) asserts one version - repo-wide. Bump procedure: `conformance/driver-ct/polyengine/README.md`. + the module reads no ambient configuration. Since the A22 + protocol/runtime split, this published package (`@polymorph/websocket`) + depends on `@polyengine/protocol` only (`js/polyengine/deno.json`, + caret-pinned `^0.2.2`) — no `@polyengine/runtime` import anywhere in + it; value recognition is brand-based (`@polyengine/protocol` predicates, + not `instanceof`), so its copy of `@polyengine/protocol` is harmless + even in a multi-copy graph. The runtime-linked application legs + (`conformance/driver-ct/polyengine/`, the WPT parity round trip, the + demo) separately exact-pin the `@polyengine/{runtime,translator,wasi, + ct-runner}` lockstep family (`0.5.0`) plus `@polyengine/protocol` + (`0.2.2`) in `conformance/driver-ct/polyengine/deno.json`; the root + justfile's `exam-polyengine` recipe (CI-wired) asserts one resolved + runtime-family version, one resolved protocol version, and that + `js/polyengine` names no `@polyengine/runtime` specifier. Bump + procedure: `conformance/driver-ct/polyengine/README.md`. - `js/componentize/` (`just wpt::…`) — `websocket.js`, the WHATWG-API shim for componentize-js guests (deviations registry in its header), and the WPT parity gate (`wpt/README.md` is the vendoring policy; losses diff --git a/conformance/driver-ct/polyengine/README.md b/conformance/driver-ct/polyengine/README.md index e3fdc82..de0ac5d 100644 --- a/conformance/driver-ct/polyengine/README.md +++ b/conformance/driver-ct/polyengine/README.md @@ -22,39 +22,46 @@ module graph — no fetch step, no net grant. ## The pin polyengine publishes `@polyengine/{runtime,translator,wasi,ct-runner}` to -JSR as exact-pinned prereleases (`0.3.0-pre.g`, the same short -hash as the corresponding GitHub release — a version names one exact -upstream commit; see polyengine's README "Consuming the unstable -prereleases"). It is pinned in **two** places, both required to agree: - -- `deno.json` (this directory) — import-map versions - (`jsr:@polyengine/@0.3.0-pre.g`) for `@polyengine/ct-runner`, - `@polyengine/runtime/embedder`, `@polyengine/runtime/shim`, `@polyengine/wasi`, - `@polyengine/translator`. `deno.lock` carries integrity hashes for that - module graph, enforced with `--frozen`. -- [`../../../js/polyengine/deno.json`](../../../js/polyengine/deno.json) — the - SAME `@polyengine/runtime` version (the module-identity constraint: polyengine's - `wasi` module imports `@polyengine/runtime/embedder` by bare specifier - internally, so every config resolving it must agree, or the embedder - module loads twice — the host module's brand-based value recognition - survives that, but a second copy splits the runtime/translator - plan-format pairing). +JSR as a lockstep-versioned line (this directory exact-pins `0.5.0`) and +`@polyengine/protocol` (the host-ABI vocabulary package) as an +independently-versioned line (exact-pinned `0.2.2` here). It is pinned in +this directory's `deno.json` import map +(`jsr:@polyengine/@0.5.0` for `@polyengine/ct-runner`, +`@polyengine/runtime/embedder`, `@polyengine/runtime/shim`, +`@polyengine/wasi`, `@polyengine/translator`; `jsr:@polyengine/protocol@0.2.2` +for vocabulary consumed by driver code such as `examples/polyengine-demo/run.ts`). +`deno.lock` carries integrity hashes for that module graph, enforced with +`--frozen`. + +As of the A22 protocol/runtime split, `@polymorph/websocket` +([`../../../js/polyengine/deno.json`](../../../js/polyengine/deno.json)) +depends on `@polyengine/protocol` only — it resolves no +`@polyengine/runtime` at all, so its copy of `@polyengine/protocol` need +not match this directory's (the host module's brand-based value +recognition is harmless across copies by construction). This directory's +own runtime-family pins still need to be internally consistent: if this +repo ever loads the embedder from more than one config, stateful handles +(streams/futures) minted by one copy are refused by another. `@polyengine/translator` ships the translator wasm asset **for the same commit** as `@polyengine/runtime`, so the plan-format coupling is self-consistent per graph by construction — there is no separate sha256 -to track. The root justfile's `exam-polyengine` recipe (wired into CI) is the -one-version-everywhere gate: it asserts every `jsr:@polyengine/*` import -across both `deno.json` files names the identical version. +to track. The root justfile's `exam-polyengine` recipe (wired into CI) is +the pin gate: it asserts one resolved `@polyengine/runtime`-family +version and one resolved `@polyengine/protocol` version across both +`deno.lock` files, plus a cheap guard that `js/polyengine` (the published +host module) names no `@polyengine/runtime` specifier at all. Both `deno.json`s carry `"minimumDependencyAge": { "age": "P1D", "exclude": ["jsr:@polyengine/*"] }` (verbatim from polyengine's README): Deno's 24-hour supply-chain gate would otherwise block resolving a same-day publish. -To bump: update the version in both `deno.json` import maps, delete -**both** `deno.lock` files (this directory and `js/polyengine/`), regenerate -with `deno install` (or `deno check`) in each directory, then re-run +To bump: update the version in this directory's `deno.json` import map +(and `js/polyengine/deno.json`'s `@polyengine/protocol` caret range, if +bumping that independently-versioned line), delete **both** `deno.lock` +files (this directory and `js/polyengine/`), regenerate with +`deno install` (or `deno check`) in each directory, then re-run `just conformance-ct::run-polyengine` and commit the diff (including the regenerated `matrix.md`, via `just conformance-ct::matrix-update`). -`exam-polyengine` fails loud on any drift between the two configs. +`exam-polyengine` fails loud on any drift. diff --git a/conformance/driver-ct/polyengine/browser-bundle-entry.ts b/conformance/driver-ct/polyengine/browser-bundle-entry.ts index f65f45e..dd20e16 100644 --- a/conformance/driver-ct/polyengine/browser-bundle-entry.ts +++ b/conformance/driver-ct/polyengine/browser-bundle-entry.ts @@ -10,8 +10,14 @@ // browser/worker-entry.ts imports as `@polyengine/release-bundle-entry` // used to. Same export surface: instantiate/Translator/runSuite/ // wasi/ComponentException/artifactsFromEnvelope and friends (49 exports -// verified against the retired upstream entry). +// verified against the retired upstream entry). A22 split the vocabulary +// (ComponentException and friends) out of @polyengine/runtime/embedder +// into @polyengine/protocol; both re-exports are combined here so the +// bundle's consumers (the worker message loop, @polymorph/test's +// polyengine-worker-main injection path) keep seeing the same names off +// one namespace — no collisions, since the embedder no longer exports them. export * from "@polyengine/runtime/embedder"; +export * from "@polyengine/protocol"; export { Translator } from "@polyengine/runtime/shim"; export * from "@polyengine/ct-runner"; export { wasi } from "@polyengine/wasi"; diff --git a/conformance/driver-ct/polyengine/deno.json b/conformance/driver-ct/polyengine/deno.json index 6428050..4cc6ee8 100644 --- a/conformance/driver-ct/polyengine/deno.json +++ b/conformance/driver-ct/polyengine/deno.json @@ -1,11 +1,12 @@ { - "//": "MODULE-IDENTITY CONSTRAINT: polyengine's wasi module imports @polyengine/runtime/embedder by bare specifier internally. The @polyengine/runtime/embedder entry here must resolve to the IDENTICAL @polyengine/runtime version as ../../../js/polyengine/deno.json's caret range (in both deno.locks), or the embedder module loads twice: value recognition survives that (websocket.ts recognizes polyengine values by @polyengine/protocol brand predicates, not instanceof), but a second copy splits the runtime/translator plan-format pairing, and stateful handles (streams/futures) minted by one copy are refused by the other. Pin story: exact-pinned JSR releases (caret-honest upstream: within a minor line releases stay compatible, breaking bumps the minor; per-commit `-pre.g` prereleases exist between releases — pin exactly); @polyengine/translator ships the translator wasm for that SAME commit; deno.lock carries integrity, --frozen enforced everywhere; one version repo-wide is asserted by the pin gate (justfile: exam-polyengine).", + "//": "A22 MODULE-IDENTITY POSTURE: @polyengine/wasi@0.5.0 is protocol-only internally (the old module-identity clause tying it to @polyengine/runtime/embedder is gone), so this file's runtime-family pins only need to be exact and mutually consistent with each other and with ../../../js/polyengine/deno.json when that package's caret range resolves the same commit — this repo's driver code (run.ts, examples/polyengine-demo/run.ts) that also loads @polyengine/protocol vocabulary must still resolve to ONE @polyengine/runtime version if it loads the embedder in more than one config, since stateful handles (streams/futures) minted by one copy are refused by another; the published @polymorph/websocket host module, by contrast, now couples only to @polyengine/protocol, whose copies are harmless by construction. Pin story: exact-pinned JSR releases (caret-honest upstream: within a minor line releases stay compatible, breaking bumps the minor; per-commit `-pre.g` prereleases exist between releases — pin exactly); @polyengine/translator ships the translator wasm for that SAME commit; deno.lock carries integrity, --frozen enforced everywhere; the pin gate (justfile: exam-polyengine) asserts one resolved @polyengine/runtime version and one resolved @polyengine/protocol version repo-wide.", "imports": { - "@polyengine/ct-runner": "jsr:@polyengine/ct-runner@0.4.0", - "@polyengine/runtime/embedder": "jsr:@polyengine/runtime@0.4.0/embedder", - "@polyengine/runtime/shim": "jsr:@polyengine/runtime@0.4.0/shim", - "@polyengine/wasi": "jsr:@polyengine/wasi@0.4.0", - "@polyengine/translator": "jsr:@polyengine/translator@0.4.0", + "@polyengine/ct-runner": "jsr:@polyengine/ct-runner@0.5.0", + "@polyengine/runtime/embedder": "jsr:@polyengine/runtime@0.5.0/embedder", + "@polyengine/runtime/shim": "jsr:@polyengine/runtime@0.5.0/shim", + "@polyengine/wasi": "jsr:@polyengine/wasi@0.5.0", + "@polyengine/translator": "jsr:@polyengine/translator@0.5.0", + "@polyengine/protocol": "jsr:@polyengine/protocol@0.2.2", "@polymorph/test/polyengine-worker-main": "jsr:@polymorph/test@0.2.1/polyengine-worker-main" }, "minimumDependencyAge": { "age": "P1D", "exclude": ["jsr:@polyengine/*", "jsr:@polymorph/*"] }, diff --git a/conformance/driver-ct/polyengine/deno.lock b/conformance/driver-ct/polyengine/deno.lock index 8c87cab..3521ec0 100644 --- a/conformance/driver-ct/polyengine/deno.lock +++ b/conformance/driver-ct/polyengine/deno.lock @@ -1,53 +1,67 @@ { "version": "5", "specifiers": { - "jsr:@polyengine/ct-runner@0.4.0": "0.4.0", - "jsr:@polyengine/protocol@~0.2.1": "0.2.1", - "jsr:@polyengine/runtime@0.4": "0.4.0", - "jsr:@polyengine/runtime@0.4.0": "0.4.0", - "jsr:@polyengine/translator@0.4.0": "0.4.0", - "jsr:@polyengine/wasi@0.4.0": "0.4.0", - "jsr:@polymorph/test@0.2.1": "0.2.1" + "jsr:@polyengine/ct-runner@0.5.0": "0.5.0", + "jsr:@polyengine/protocol@0.2.2": "0.2.2", + "jsr:@polyengine/protocol@~0.2.2": "0.2.2", + "jsr:@polyengine/runtime@0.5": "0.5.0", + "jsr:@polyengine/runtime@0.5.0": "0.5.0", + "jsr:@polyengine/translator@0.5.0": "0.5.0", + "jsr:@polyengine/wasi@0.5.0": "0.5.0", + "jsr:@polymorph/test@0.2.1": "0.2.1", + "npm:@jsr/polymorph__test@0.2.1": "0.2.1", + "npm:playwright-core@^1.61.1": "1.62.1" }, "jsr": { - "@polyengine/ct-runner@0.4.0": { - "integrity": "a309b2cfc20549068718cea6813ec1553a5c2b2700eb46855582a21116fbc63a", + "@polyengine/ct-runner@0.5.0": { + "integrity": "be3219d9ed283a10386fc76590149419888544980155e84f72b988f1b248a2e5", "dependencies": [ - "jsr:@polyengine/runtime@0.4" + "jsr:@polyengine/protocol@~0.2.2", + "jsr:@polyengine/runtime@0.5" ] }, - "@polyengine/protocol@0.2.1": { - "integrity": "857bef84afedc414d47daedf203448f72e8803cf96301afb783b1ac975f3dcec" + "@polyengine/protocol@0.2.2": { + "integrity": "ff470d7183924119ae181cc513093318f1587f7ad5c4bc02db7344e90c9ff52b" }, - "@polyengine/runtime@0.4.0": { - "integrity": "d34a59d9df58bd48541483619d1987319d8aad4d2e0f3775890177ffb0878a5a", + "@polyengine/runtime@0.5.0": { + "integrity": "36ea9b74dd38c0f5fb941695cfa3223190fda54bc18f7328600ece082dbbb794", "dependencies": [ - "jsr:@polyengine/protocol" + "jsr:@polyengine/protocol@~0.2.2" ] }, - "@polyengine/translator@0.4.0": { - "integrity": "227cf2f92a5b11bce6155742b2a42cf14a642a89d3867ffd1789d6e6e8d49fdc", + "@polyengine/translator@0.5.0": { + "integrity": "2f9770f468bd0c649732e6107d69715dea715c71564fe5b0ba0997b5d70e81c3", "dependencies": [ - "jsr:@polyengine/runtime@0.4" + "jsr:@polyengine/runtime@0.5" ] }, - "@polyengine/wasi@0.4.0": { - "integrity": "b946443255e98cb6156237de90f051af7e483d2afdc0d721bdba64392cdbd896", + "@polyengine/wasi@0.5.0": { + "integrity": "61275e66895084807393bf93876b74be9cc1ca37641d56a07cf8c7b9a78ce091", "dependencies": [ - "jsr:@polyengine/protocol", - "jsr:@polyengine/runtime@0.4" + "jsr:@polyengine/protocol@~0.2.2" ] }, "@polymorph/test@0.2.1": { "integrity": "1934b15f69d2c5f733cb42176aee8899f6da507a30ba1e25fad942fb82c0546e" } }, + "npm": { + "@jsr/polymorph__test@0.2.1": { + "integrity": "sha512-5bkpwmjHp8Hjk69ADkQp1oo4CHyiPT2BAtQWbi7QrbTkrrwIDOn73JzsmZhzWT4mvzkWEL3Q4u1BSOra3sVBLQ==", + "tarball": "https://npm.jsr.io/~/11/@jsr/polymorph__test/0.2.1.tgz" + }, + "playwright-core@1.62.1": { + "integrity": "sha512-wPYSwEBJY9GHraISXqyqtx0na0LpO3XEX7jNDhntbex7tzUS7kLnZsOlFruFJB4Hi/rhDMjXGqHewDZ68nYZVw==", + "bin": true + } + }, "workspace": { "dependencies": [ - "jsr:@polyengine/ct-runner@0.4.0", - "jsr:@polyengine/runtime@0.4.0", - "jsr:@polyengine/translator@0.4.0", - "jsr:@polyengine/wasi@0.4.0", + "jsr:@polyengine/ct-runner@0.5.0", + "jsr:@polyengine/protocol@0.2.2", + "jsr:@polyengine/runtime@0.5.0", + "jsr:@polyengine/translator@0.5.0", + "jsr:@polyengine/wasi@0.5.0", "jsr:@polymorph/test@0.2.1" ], "packageJson": { diff --git a/examples/polyengine-demo/run.ts b/examples/polyengine-demo/run.ts index efc3813..c392614 100644 --- a/examples/polyengine-demo/run.ts +++ b/examples/polyengine-demo/run.ts @@ -22,7 +22,8 @@ import { Translator } from "@polyengine/runtime/shim"; import type { ComponentArtifacts } from "@polyengine/runtime/embedder"; -import { instantiate, isComponentException } from "@polyengine/runtime/embedder"; +import { instantiate } from "@polyengine/runtime/embedder"; +import { isComponentException } from "@polyengine/protocol"; import { defaultTranslator } from "@polyengine/translator"; import { wasi } from "@polyengine/wasi"; import { websocketImports } from "../../js/polyengine/websocket.ts"; diff --git a/js/componentize/wpt/justfile b/js/componentize/wpt/justfile index 94d20be..24f554a 100644 --- a/js/componentize/wpt/justfile +++ b/js/componentize/wpt/justfile @@ -10,9 +10,9 @@ # # The polyengine pin is single-site: the carrier bundle resolves @polyengine/* # through conformance/driver-ct/polyengine/deno.json (exact-pinned JSR -# prereleases; see that deno.json's MODULE-IDENTITY comment) and its -# translator asset is extracted from the packaged `@polyengine/translator` -# JSR package's lock-pinned module cache (no fetch step). +# releases) and its translator asset is extracted from the packaged +# `@polyengine/translator` JSR package's lock-pinned module cache (no +# fetch step). import '../../../justfile.shared.just' diff --git a/js/polyengine/README.md b/js/polyengine/README.md index 685cf39..aa7ba5d 100644 --- a/js/polyengine/README.md +++ b/js/polyengine/README.md @@ -34,16 +34,18 @@ identically on Deno. ## Module identity -`deno.json`'s `@polyengine/runtime/embedder` import maps to the exact same -pinned URL as `conformance/driver-ct/polyengine/deno.json`. polyengine's -`wasi-shims` module imports that specifier by bare name internally; if the -two configs ever disagreed, the embedder module would load twice. This -module recognizes polyengine values by `@polyengine/protocol` brand -predicates (`isComponentException`, the `STREAM` brand), so recognition -holds even in a multi-copy graph — but a duplicated embedder still splits -the runtime/translator plan-format pairing, and stateful handles -(streams/futures) minted by one copy are refused by the other. Keep both -import maps byte-identical for that one entry. +This package (`@polymorph/websocket`) depends on `@polyengine/protocol` +only (A22: published host modules must not import +`@polyengine/runtime`). It recognizes polyengine values by +`@polyengine/protocol` brand predicates (`isComponentException`, the +`STREAM` brand), not `instanceof`, so recognition holds even in a +multi-copy graph — this package's own copy of `@polyengine/protocol` is +harmless by construction and need not agree with any other config's. +Applications that separately load the `@polyengine/runtime` embedder +(e.g. `conformance/driver-ct/polyengine/deno.json`) still need ONE +resolved runtime version across their own configs, since stateful +handles (streams/futures) minted by one copy are refused by another — +but that constraint is theirs, not this package's. ## Unit tests diff --git a/js/polyengine/deno.json b/js/polyengine/deno.json index df8684b..6f5d088 100644 --- a/js/polyengine/deno.json +++ b/js/polyengine/deno.json @@ -1,11 +1,11 @@ { "name": "@polymorph/websocket", - "version": "0.4.0", + "version": "0.5.0", "license": "Apache-2.0", "exports": "./websocket.ts", - "//": "MODULE-IDENTITY CONSTRAINT: polyengine's wasi module imports @polyengine/runtime/embedder by bare specifier internally. This file's @polyengine/runtime range and conformance/driver-ct/polyengine/deno.json's exact pin must resolve to the IDENTICAL @polyengine/runtime version in both deno.locks, or the embedder module loads twice: value recognition survives that (websocket.ts recognizes polyengine values by @polyengine/protocol brand predicates, not instanceof), but a second copy splits the runtime/translator plan-format pairing, and stateful handles (streams/futures) minted by one copy are refused by the other. Pin story: this published package takes a caret range (caret-honest upstream: within a minor line releases stay compatible, breaking bumps the minor); the repo-internal conformance harness exact-pins instead so its lock is the single source of truth for the resolved version; deno.lock carries integrity, --frozen enforced everywhere; one RESOLVED version repo-wide (across both locks) is asserted by the pin gate (justfile: exam-polyengine).", + "//": "A22 MODULE-IDENTITY POSTURE: this package couples only to @polyengine/protocol (the host-ABI vocabulary package) — no @polyengine/runtime import anywhere in this module, per polyengine's A22 rule that published host modules must not import @polyengine/runtime. websocket.ts recognizes polyengine values by @polyengine/protocol brand predicates, not instanceof, so its copies are harmless by construction: any application that also loads the embedder resolves its own runtime version independently, and this package neither constrains nor is constrained by that choice. Pin story: this published package takes a caret range on @polyengine/protocol (caret-honest upstream: within a minor line releases stay compatible, breaking bumps the minor); deno.lock carries integrity, --frozen enforced everywhere; the pin gate (justfile: exam-polyengine) asserts this file names no @polyengine/runtime specifier.", "imports": { - "@polyengine/runtime/embedder": "jsr:@polyengine/runtime@^0.4.0/embedder" + "@polyengine/protocol": "jsr:@polyengine/protocol@^0.2.2" }, "minimumDependencyAge": { "age": "P1D", "exclude": ["jsr:@polyengine/*"] }, "compilerOptions": { diff --git a/js/polyengine/deno.lock b/js/polyengine/deno.lock index bfb9aef..b430e1b 100644 --- a/js/polyengine/deno.lock +++ b/js/polyengine/deno.lock @@ -1,20 +1,13 @@ { "version": "5", "specifiers": { - "jsr:@polyengine/protocol@~0.2.1": "0.2.1", - "jsr:@polyengine/runtime@0.4": "0.4.0", + "jsr:@polyengine/protocol@~0.2.2": "0.2.2", "jsr:@std/assert@1": "1.0.19", "jsr:@std/internal@^1.0.12": "1.0.14" }, "jsr": { - "@polyengine/protocol@0.2.1": { - "integrity": "857bef84afedc414d47daedf203448f72e8803cf96301afb783b1ac975f3dcec" - }, - "@polyengine/runtime@0.4.0": { - "integrity": "d34a59d9df58bd48541483619d1987319d8aad4d2e0f3775890177ffb0878a5a", - "dependencies": [ - "jsr:@polyengine/protocol" - ] + "@polyengine/protocol@0.2.2": { + "integrity": "ff470d7183924119ae181cc513093318f1587f7ad5c4bc02db7344e90c9ff52b" }, "@std/assert@1.0.19": { "integrity": "eaada96ee120cb980bc47e040f82814d786fe8162ecc53c91d8df60b8755991e", @@ -28,7 +21,7 @@ }, "workspace": { "dependencies": [ - "jsr:@polyengine/runtime@0.4" + "jsr:@polyengine/protocol@~0.2.2" ] } } diff --git a/js/polyengine/tests/websocket_test.ts b/js/polyengine/tests/websocket_test.ts index 0f7b171..ae49448 100644 --- a/js/polyengine/tests/websocket_test.ts +++ b/js/polyengine/tests/websocket_test.ts @@ -9,7 +9,7 @@ // consumer's own conformance suite, executed by conformance/run.ts. import { assert, assertEquals, assertRejects, assertThrows } from "jsr:@std/assert@^1.0.0"; -import { ComponentException } from "@polyengine/runtime/embedder"; +import { ComponentException } from "@polyengine/protocol"; import { currentConfig, resetConfig, diff --git a/js/polyengine/websocket.ts b/js/polyengine/websocket.ts index 5a3ecce..cef9f90 100644 --- a/js/polyengine/websocket.ts +++ b/js/polyengine/websocket.ts @@ -33,7 +33,7 @@ import { STREAM, type Stream, type StreamSource, -} from "@polyengine/runtime/embedder"; +} from "@polyengine/protocol"; // ----- WIT value types (contracts/embedder-api.md §"Value mapping") --------- diff --git a/justfile b/justfile index aea3861..af55e42 100644 --- a/justfile +++ b/justfile @@ -69,26 +69,40 @@ test: polyengine-module-check: cd js/polyengine && deno task check && deno task test -# The one-version-everywhere gate: every `jsr:@polyengine/*` package -# resolved in BOTH deno.locks (js/polyengine and -# conformance/driver-ct/polyengine) must agree on the exact same -# resolved version, or the embedder module can load twice across the -# module boundary (see js/polyengine/deno.json's MODULE-IDENTITY -# comment). js/polyengine's manifest takes a caret range (required for -# publishing to JSR) so the *lock*, not the manifest, is the source of -# truth for the resolved version. @polyengine/protocol is excluded: it -# versions independently of the runtime/wasi/translator family it's a -# transitive dependency of. +# The A22 two-line pin gate: every `jsr:@polyengine/{runtime,translator, +# wasi,ct-runner}` package resolved in either deno.lock (js/polyengine +# and conformance/driver-ct/polyengine) must agree on ONE resolved +# runtime-family version, and every `jsr:@polyengine/protocol` resolved +# in either lock must agree on ONE resolved protocol version — the two +# lines version independently (see js/polyengine/deno.json's and +# conformance/driver-ct/polyengine/deno.json's A22 MODULE-IDENTITY +# comments). js/polyengine's manifest no longer resolves the +# runtime-family at all (A22: published host modules couple only to +# @polyengine/protocol); the cheap guard below asserts that directly by +# grepping js/polyengine for a stray @polyengine/runtime specifier. exam-polyengine: #!/usr/bin/env bash set -euo pipefail - v=$(jq -r '.specifiers // {} | to_entries[] | select(.key | test("^jsr:@polyengine/(?!protocol)")) | .value' \ + rv=$(jq -r '.specifiers // {} | to_entries[] | select(.key | test("^jsr:@polyengine/(runtime|translator|wasi|ct-runner)")) | .value' \ js/polyengine/deno.lock conformance/driver-ct/polyengine/deno.lock | sort -u) - if [ "$(printf '%s\n' "$v" | wc -l)" != 1 ]; then - echo "polyengine pin drift: $v" >&2 + if [ "$(printf '%s\n' "$rv" | wc -l)" != 1 ]; then + echo "polyengine runtime-family pin drift: $rv" >&2 exit 1 fi - echo "polyengine pin: $v" + echo "polyengine runtime-family pin: $rv" + pv=$(jq -r '.specifiers // {} | to_entries[] | select(.key | test("^jsr:@polyengine/protocol")) | .value' \ + js/polyengine/deno.lock conformance/driver-ct/polyengine/deno.lock | sort -u) + if [ "$(printf '%s\n' "$pv" | wc -l)" != 1 ]; then + echo "polyengine protocol pin drift: $pv" >&2 + exit 1 + fi + echo "polyengine protocol pin: $pv" + if jq -e '.imports // {} | keys[] | select(startswith("@polyengine/runtime"))' js/polyengine/deno.json >/dev/null 2>&1 \ + || jq -e '.specifiers // {} | keys[] | select(test("^jsr:@polyengine/runtime"))' js/polyengine/deno.lock >/dev/null 2>&1; then + echo "js/polyengine (published host module) must not name @polyengine/runtime (A22)" >&2 + exit 1 + fi + echo "js/polyengine: no @polyengine/runtime specifier (ok)" # The JS runner core's one-version gate: the polyengine-browser driver's # npm tree (@jsr/polymorph__test, JSR's npm-compat form of