From 9fb49ec4f407eb78275986cbedff078057300440 Mon Sep 17 00:00:00 2001 From: Lann Martin Date: Sat, 22 Aug 2026 23:38:50 -0400 Subject: [PATCH] polyengine 0.5.0; @polymorph/webrtc-datachannels 0.5.0 Adopt the A22 protocol/runtime split: polyengine-impl (the published host module) depends on @polyengine/protocol alone (jsr:@polyengine/protocol@^0.2.2). Vocabulary imports move to protocol, and recognition goes brand-predicate per the A22 rule that instanceof against engine classes is not contract behavior: "stream instanceof Stream" becomes isStream(stream), "error instanceof ComponentException" becomes isComponentException(error). The driver-ct configs (deno + browser) bump the lockstep pins to 0.5.0 and exact-pin @polyengine/protocol@0.2.2; signaling.ts's ComponentException import moves to protocol. check-polyengine-pin.sh learns the two-line world: one runtime-line version across the two driver locks that still load the embedder, one protocol version across all three locks, and polyengine-impl names no @polyengine/runtime specifier at all. Gates: polyengine-check (pin gates + impl check/tests 13/13 + driver check), conformance loopback matrix all pass (24 solo + 13x2 pair cases, node-datachannel backend). --- conformance/driver-ct/polyengine/README.md | 34 ++++++---- .../driver-ct/polyengine/browser/deno.json | 11 ++-- .../driver-ct/polyengine/browser/deno.lock | 42 +++++++------ conformance/driver-ct/polyengine/deno.json | 13 ++-- conformance/driver-ct/polyengine/deno.lock | 52 ++++++++-------- conformance/driver-ct/polyengine/run.ts | 13 ++-- conformance/driver-ct/polyengine/signaling.ts | 2 +- polyengine-impl/README.md | 21 ++++--- polyengine-impl/deno.json | 6 +- polyengine-impl/deno.lock | 15 ++--- polyengine-impl/src/types.ts | 2 +- polyengine-impl/src/webrtc.ts | 28 ++++++--- polyengine-impl/tests/webrtc_test.ts | 2 +- scripts/check-polyengine-pin.sh | 62 ++++++++++++++----- 14 files changed, 178 insertions(+), 125 deletions(-) diff --git a/conformance/driver-ct/polyengine/README.md b/conformance/driver-ct/polyengine/README.md index c95bbff..4786a3b 100644 --- a/conformance/driver-ct/polyengine/README.md +++ b/conformance/driver-ct/polyengine/README.md @@ -37,23 +37,33 @@ module graph — no fetch step, no network. polyengine ships as exact-pinned JSR prereleases (one per green upstream commit; the hash in the version names the commit — see the [polyengine README's "Consuming the unstable -prereleases"](https://github.com/polymorph-components/polyengine#readme)). The version is -pinned in **three** places, cross-checked by `just polyengine-check`'s pin -gate (`../../../scripts/check-polyengine-pin.sh`): +prereleases"](https://github.com/polymorph-components/polyengine#readme)). As +of A22, the `@polyengine/protocol` line versions independently of the +`@polyengine/{runtime,translator,wasi,ct-runner}` lockstep line: `just +polyengine-check`'s pin gate (`../../../scripts/check-polyengine-pin.sh`) +asserts one resolved `@polyengine/runtime` version across the two configs +that load the embedder, one resolved `@polyengine/protocol` version across +all three configs, and that `polyengine-impl` names no `@polyengine/runtime` +specifier at all (published host modules must not import +`@polyengine/runtime` — see `../../../polyengine-impl/README.md`). - `deno.json` (this directory) — import-map versions (`jsr:@polyengine/@`) for `@polyengine/ct-runner`, `@polyengine/runtime/embedder`, `@polyengine/runtime/shim`, - `@polyengine/wasi-shims`, `@polyengine/translator`. `deno.lock` carries - integrity hashes for that module graph, enforced with `--frozen`. + `@polyengine/wasi`, `@polyengine/translator`, `@polyengine/protocol`. + `deno.lock` carries integrity hashes for that module graph, enforced + with `--frozen`. +- [`browser/deno.json`](browser/deno.json) — the SAME `@polyengine/runtime` + and `@polyengine/protocol` pins again (the module-identity constraint: + stateful handles minted by one embedder copy are refused by another, so + every config that loads the embedder must agree), with the npm WebRTC + backends stubbed out (never executed in a page). - [`../../../polyengine-impl/deno.json`](../../../polyengine-impl/deno.json) — - the SAME `@polyengine/runtime/embedder` version (the module-identity - constraint: polyengine's `wasi-shims` imports that specifier by bare name - internally, so every config resolving it must agree, or the embedder - module loads twice and `instanceof ComponentException` stops holding across - the boundary). -- [`browser/deno.json`](browser/deno.json) — the SAME pins again, with - the npm WebRTC backends stubbed out (never executed in a page). + the SAME `@polyengine/protocol` version only. As of A22 this package (a + published host module) does not map `@polyengine/runtime` at all: its + copies of `@polyengine/protocol` are harmless by construction, so it no + longer participates in the runtime module-identity constraint above. + Each `deno.json` also carries a `"minimumDependencyAge": { "age": "P1D", "exclude": ["jsr:@polyengine/*"] }` diff --git a/conformance/driver-ct/polyengine/browser/deno.json b/conformance/driver-ct/polyengine/browser/deno.json index a3b8850..699ef8c 100644 --- a/conformance/driver-ct/polyengine/browser/deno.json +++ b/conformance/driver-ct/polyengine/browser/deno.json @@ -1,11 +1,12 @@ { - "//": "The browser-bundle import map: identical polyengine pins to ../deno.json, with the npm WebRTC backends mapped to ./stub.ts (never executed in a page; see stub.ts). polyengine ships as exact-pinned JSR prereleases; the pin gate (`just polyengine-check`) asserts one version repo-wide.", + "//": "The browser-bundle import map: identical polyengine pins to ../deno.json, with the npm WebRTC backends mapped to ./stub.ts (never executed in a page; see stub.ts). polyengine ships as exact-pinned JSR prereleases; the pin gate (`just polyengine-check`) asserts one @polyengine/runtime version across this file and ../deno.json (the two configs that load the embedder — polyengine-impl no longer does, as of A22) and one @polyengine/protocol version repo-wide.", "minimumDependencyAge": { "age": "P1D", "exclude": ["jsr:@polyengine/*"] }, "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/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/protocol": "jsr:@polyengine/protocol@0.2.2", "node-datachannel": "./stub.ts", "node-datachannel/polyfill": "./stub.ts", "werift": "./stub.ts" diff --git a/conformance/driver-ct/polyengine/browser/deno.lock b/conformance/driver-ct/polyengine/browser/deno.lock index c860073..b4f8af7 100644 --- a/conformance/driver-ct/polyengine/browser/deno.lock +++ b/conformance/driver-ct/polyengine/browser/deno.lock @@ -1,41 +1,43 @@ { "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/wasi@0.4.0": "0.4.0" + "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/wasi@0.5.0": "0.5.0" }, "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/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" ] } }, "workspace": { "dependencies": [ - "jsr:@polyengine/ct-runner@0.4.0", - "jsr:@polyengine/runtime@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/wasi@0.5.0" ] } } diff --git a/conformance/driver-ct/polyengine/deno.json b/conformance/driver-ct/polyengine/deno.json index aa1d417..0cecc5d 100644 --- a/conformance/driver-ct/polyengine/deno.json +++ b/conformance/driver-ct/polyengine/deno.json @@ -1,13 +1,14 @@ { - "//": "MODULE-IDENTITY CONSTRAINT: polyengine's wasi module imports @polyengine/runtime/embedder by bare specifier internally. Every config in this repo (this file AND polyengine-impl/deno.json AND browser/deno.json) must map that specifier to the IDENTICAL exact-pinned JSR version, or the embedder module loads twice and `instanceof ComponentException` stops holding across the module boundary. polyengine ships as exact-pinned JSR releases (caret-honest upstream; per-commit `-pre.g` prereleases exist between releases — pin exactly); the lock carries integrity and --frozen enforces it; the pin gate (`just polyengine-check`) asserts one version repo-wide. The npm mappings mirror polyengine-impl/deno.json: the host module's bare `node-datachannel/polyfill` / `werift` specifiers resolve against THIS config when run.ts is the entry module.", + "//": "MODULE-IDENTITY CONSTRAINT (A22): this file AND browser/deno.json still load the embedder (@polyengine/runtime/embedder) and must map it to the IDENTICAL exact-pinned JSR version, or stateful handles minted by one copy are refused by another. polyengine-impl/deno.json no longer participates in this constraint: as of A22, published host modules (like polyengine-impl) depend only on @polyengine/protocol, whose copies are harmless by construction. @polyengine/protocol itself must resolve to one version across ALL THREE configs in this repo (this file, browser/deno.json, and polyengine-impl/deno.json), since it carries the vocabulary (ComponentException, Stream, brand predicates, ...) that crosses those module boundaries. polyengine ships as exact-pinned JSR releases (caret-honest upstream; per-commit `-pre.g` prereleases exist between releases — pin exactly); the lock carries integrity and --frozen enforces it; the pin gate (`just polyengine-check`) asserts this repo-wide. The npm mappings mirror polyengine-impl/deno.json: the host module's bare `node-datachannel/polyfill` / `werift` specifiers resolve against THIS config when run.ts is the entry module.", "nodeModulesDir": "auto", "minimumDependencyAge": { "age": "P1D", "exclude": ["jsr:@polyengine/*", "npm:@jsr/*"] }, "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", "node-datachannel": "npm:node-datachannel@0.32.3", "node-datachannel/polyfill": "npm:node-datachannel@0.32.3/polyfill", "werift": "npm:werift@0.22.2" diff --git a/conformance/driver-ct/polyengine/deno.lock b/conformance/driver-ct/polyengine/deno.lock index d6b82c9..dd8c6dc 100644 --- a/conformance/driver-ct/polyengine/deno.lock +++ b/conformance/driver-ct/polyengine/deno.lock @@ -1,44 +1,45 @@ { "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:@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", "npm:@jsr/polymorph__test@0.2.1": "0.2.1", "npm:node-datachannel@0.32.3": "0.32.3", "npm:playwright-core@^1.61.1": "1.62.1", "npm:werift@0.22.2": "0.22.2" }, "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" ] } }, @@ -605,10 +606,11 @@ }, "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", "npm:node-datachannel@0.32.3", "npm:werift@0.22.2" ], diff --git a/conformance/driver-ct/polyengine/run.ts b/conformance/driver-ct/polyengine/run.ts index 141453e..1befd31 100644 --- a/conformance/driver-ct/polyengine/run.ts +++ b/conformance/driver-ct/polyengine/run.ts @@ -19,12 +19,13 @@ // ` remains as an optional override (a documented interface for // swapping in a locally built shim), but is no longer required to run. // -// MODULE-IDENTITY CONSTRAINT: polyengine's wasi module imports -// `@polyengine/runtime/embedder` by bare specifier internally; this leg's -// `deno.json` AND `polyengine-impl/deno.json` (AND `browser/deno.json`) must -// map that specifier to the IDENTICAL pinned JSR version, or the embedder -// module loads twice and `instanceof ComponentException` stops holding across the -// module boundary. +// MODULE-IDENTITY CONSTRAINT (A22): this leg's `deno.json` AND +// `browser/deno.json` still load the embedder (`@polyengine/runtime/embedder`) +// and must map it to the IDENTICAL pinned JSR version, or stateful handles +// minted by one copy are refused by another. `polyengine-impl/deno.json` no +// longer maps `@polyengine/runtime` at all — as of A22 it depends only on +// `@polyengine/protocol`, whose copies are harmless by construction, so it +// no longer participates in this constraint. import { Translator } from "@polyengine/runtime/shim"; import { defaultTranslator } from "@polyengine/translator"; diff --git a/conformance/driver-ct/polyengine/signaling.ts b/conformance/driver-ct/polyengine/signaling.ts index 3ec513b..2421c09 100644 --- a/conformance/driver-ct/polyengine/signaling.ts +++ b/conformance/driver-ct/polyengine/signaling.ts @@ -10,7 +10,7 @@ // Failures are thrown as the WIT `error` variant's `other` case, which the // runtime lifts into the `result<_, error>` the mailbox interface declares. -import { ComponentException } from "@polyengine/runtime/embedder"; +import { ComponentException } from "@polyengine/protocol"; /** The mailbox interface's WIT id (conformance/wit/deps/conformance-signaling). */ export const MAILBOX_INTERFACE = "conformance:signaling/mailbox@0.1.0"; diff --git a/polyengine-impl/README.md b/polyengine-impl/README.md index c6a1981..4db550e 100644 --- a/polyengine-impl/README.md +++ b/polyengine-impl/README.md @@ -38,14 +38,17 @@ a subset of the protocol surface; the conformance suite ## Module identity -`deno.json` pins `@polyengine/runtime/embedder` to an exact-pinned -`jsr:@polyengine/runtime@/embedder` specifier that MUST stay -byte-identical with the one in +As of A22 (`@polyengine/runtime@0.5.0` / `@polyengine/protocol@0.2.2`), this +package depends on `@polyengine/protocol` only — host modules must not import +`@polyengine/runtime` (protocol copies are harmless by construction, so no +module-identity constraint applies to this package's pin). `deno.json` maps +`@polyengine/protocol` to a caret range (published dependency constraints +must be ranges). The runtime-module-identity constraint still applies between +the two driver configs that load the embedder, [`conformance/driver-ct/polyengine/deno.json`](../conformance/driver-ct/polyengine/deno.json) -(and its `browser/deno.json`): polyengine's `wasi-shims` imports that -specifier by bare name internally, so two divergent mappings load the -embedder module twice and `instanceof ComponentException` stops holding across -the module boundary. The bump procedure lives in -[`conformance/driver-ct/polyengine/README.md`](../conformance/driver-ct/polyengine/README.md); -`just polyengine-check` asserts all three configs agree on one version +and its `browser/deno.json` sibling: see +[`conformance/driver-ct/polyengine/README.md`](../conformance/driver-ct/polyengine/README.md). +`just polyengine-check` asserts one resolved `@polyengine/runtime` version +across those two configs, one resolved `@polyengine/protocol` version across +all three, and that this package names no `@polyengine/runtime` specifier (the pin gate, `scripts/check-polyengine-pin.sh`). diff --git a/polyengine-impl/deno.json b/polyengine-impl/deno.json index 8bb4c86..d230cd4 100644 --- a/polyengine-impl/deno.json +++ b/polyengine-impl/deno.json @@ -1,13 +1,13 @@ { "name": "@polymorph/webrtc-datachannels", - "version": "0.4.0", + "version": "0.5.0", "license": "Apache-2.0", "exports": "./src/webrtc.ts", - "//": "MODULE-IDENTITY CONSTRAINT: polyengine's wasi module imports @polyengine/runtime/embedder by bare specifier internally. Every config in this repo (this file AND conformance/driver-ct/polyengine/deno.json AND conformance/driver-ct/polyengine/browser/deno.json) must map that specifier to the IDENTICAL exact-pinned JSR version, or the embedder module loads twice and `instanceof ComponentException` stops holding across the module boundary. polyengine ships as exact-pinned JSR releases (caret-honest upstream; per-commit `-pre.g` prereleases exist between releases — the OTHER two configs still pin exactly, since they load the same on-disk polyengine module and must resolve identically; this JSR-published package instead depends on a caret range, since published dependency constraints must be ranges — the pin gate (`just polyengine-check`) asserts one RESOLVED version across all three repo deno.locks). The lock carries integrity and --frozen enforces it.", + "//": "MODULE-IDENTITY CONSTRAINT (A22): this package is a host module and MUST NOT import @polyengine/runtime (published host modules couple only to @polyengine/protocol, whose copies are harmless by construction — the module-identity story that used to require matching this package's runtime pin against conformance/driver-ct/polyengine/deno.json and its browser/ sibling no longer applies here). This config depends on @polyengine/protocol via a caret range (published dependency constraints must be ranges); the pin gate (`just polyengine-check`) asserts one RESOLVED @polyengine/protocol version across all three repo deno.locks, one RESOLVED @polyengine/runtime version across the two driver configs that still load the embedder, and that this package's deno.json/deno.lock name no @polyengine/runtime specifier at all. The lock carries integrity and --frozen enforces it.", "nodeModulesDir": "auto", "minimumDependencyAge": { "age": "P1D", "exclude": ["jsr:@polyengine/*"] }, "imports": { - "@polyengine/runtime/embedder": "jsr:@polyengine/runtime@^0.4.0/embedder", + "@polyengine/protocol": "jsr:@polyengine/protocol@^0.2.2", "node-datachannel": "npm:node-datachannel@0.32.3", "node-datachannel/polyfill": "npm:node-datachannel@0.32.3/polyfill", "werift": "npm:werift@0.22.2" diff --git a/polyengine-impl/deno.lock b/polyengine-impl/deno.lock index 98bd89a..17cf65b 100644 --- a/polyengine-impl/deno.lock +++ b/polyengine-impl/deno.lock @@ -1,22 +1,15 @@ { "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", "npm:node-datachannel@0.32.3": "0.32.3", "npm:werift@0.22.2": "0.22.2" }, "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", @@ -583,7 +576,7 @@ }, "workspace": { "dependencies": [ - "jsr:@polyengine/runtime@0.4", + "jsr:@polyengine/protocol@~0.2.2", "npm:node-datachannel@0.32.3", "npm:werift@0.22.2" ] diff --git a/polyengine-impl/src/types.ts b/polyengine-impl/src/types.ts index bc920f8..4217037 100644 --- a/polyengine-impl/src/types.ts +++ b/polyengine-impl/src/types.ts @@ -5,7 +5,7 @@ // read-only reference). Enums are kebab-case string literal unions; variants // are `{ kind, value? }`; records are plain camelCase objects. -import type { Stream, StreamSource } from "@polyengine/runtime/embedder"; +import type { Stream, StreamSource } from "@polyengine/protocol"; // --- error ----------------------------------------------------------------- diff --git a/polyengine-impl/src/webrtc.ts b/polyengine-impl/src/webrtc.ts index d22ee8d..8d6f88a 100644 --- a/polyengine-impl/src/webrtc.ts +++ b/polyengine-impl/src/webrtc.ts @@ -30,21 +30,31 @@ // messages) / `ReadableStream` (produced, e.g. `receive-via-stream`'s // result — one of the natural JS producers the conventions accept where // a `stream` is expected). Imported from -// `@polyengine/runtime/embedder` (pinned in this package's `deno.json` to -// the exact release URL every polyengine-facing module in this repository -// shares), NOT reimplemented locally: `ComponentException` is a plain branded +// `@polyengine/protocol` (this package's only polyengine dependency, per +// A22 — host modules must not import `@polyengine/runtime`; pinned in +// this package's `deno.json` to a caret range), NOT reimplemented +// locally: `ComponentException` is a plain branded // class with no `Store` involvement, so a local clone would produce a // second class identity and every `throw` from this port would fail -// `instanceof ComponentException` at a real component boundary — silently -// becoming an unbranded-throw trap instead of a guest-visible err. -// `deno.json` documents the module-identity constraint. +// the `isComponentException` brand check at a real component boundary — +// silently becoming an unbranded-throw trap instead of a guest-visible +// err. Brand predicates (`isComponentException`, `isStream`), not +// `instanceof`, are used against these classes throughout this module — +// `instanceof` is not contract behavior across separately-loaded +// protocol module copies. // - the inbound buffer bound stays a module-level setter // (`setMaxInboundBufferBytes`), exactly as in the reference: the WIT // does not expose the bound as guest-configurable (it is host policy // per the `data-channel` resource's doc comment), so there is no // guest-facing shape to convert. -import { Stream, type StreamSource, ComponentException } from "@polyengine/runtime/embedder"; +import { + ComponentException, + isComponentException, + isStream, + type Stream, + type StreamSource, +} from "@polyengine/protocol"; import type { ConfigError, ConnectionState, @@ -477,7 +487,7 @@ export class DataChannel { sent += 1n; } } catch (error) { - const payload: WebrtcError = error instanceof ComponentException + const payload: WebrtcError = isComponentException(error) ? (error.payload as WebrtcError) : (isWebrtcError(error) ? error : { kind: "closed" }); throw new ComponentException({ error: payload, sent }); @@ -1082,7 +1092,7 @@ async function collectByteStream( } finally { reader.releaseLock(); } - } else if (stream instanceof Stream) { + } else if (isStream(stream)) { const READ_BATCH = 65536; for (;;) { const chunk = await stream.read(READ_BATCH); diff --git a/polyengine-impl/tests/webrtc_test.ts b/polyengine-impl/tests/webrtc_test.ts index cb4e949..181d742 100644 --- a/polyengine-impl/tests/webrtc_test.ts +++ b/polyengine-impl/tests/webrtc_test.ts @@ -21,7 +21,7 @@ import { resetMaxInboundBufferBytes, setMaxInboundBufferBytes, } from "../src/webrtc.ts"; -import { ComponentException } from "@polyengine/runtime/embedder"; +import { ComponentException } from "@polyengine/protocol"; import type { IceCandidate, Message, WebrtcError } from "../src/types.ts"; const NO_SANITIZE = { sanitizeResources: false, sanitizeOps: false }; diff --git a/scripts/check-polyengine-pin.sh b/scripts/check-polyengine-pin.sh index 8526f1d..f4803e3 100755 --- a/scripts/check-polyengine-pin.sh +++ b/scripts/check-polyengine-pin.sh @@ -3,29 +3,59 @@ # retired one-tag-everywhere assertion that used to live in the (now # deleted) deno translator-fetch script (see polyengine-jsr-contract.md). # -# polyengine-impl/deno.json is JSR-published, so its manifest maps -# @polyengine/runtime to a caret RANGE (published dependency constraints must -# be ranges); the other two configs (conformance/driver-ct/polyengine and its -# browser/ import map) still map it to an exact pin, since they load the -# same on-disk polyengine module and must resolve identically. Manifests can -# therefore no longer be compared directly: this gate instead asserts that -# every jsr:@polyengine/* package (excluding @polyengine/protocol, a transitive -# dependency that deliberately floats independently) RESOLVES to the same -# version across all three repo deno.locks. Run as part of -# `just polyengine-check` (CI: gha::conformance-matrix), the natural -# fail-loud point since every leg's lock is on disk there. +# As of A22 (the protocol/runtime split), polyengine ships two independently +# versioned lines: `@polyengine/{runtime,translator,wasi,ct-runner}` +# (lockstep) and `@polyengine/protocol` (the host-ABI version, versioned +# separately). polyengine-impl/deno.json is JSR-published AND a host module, +# so per A22 it must not name `@polyengine/runtime` at all — it maps only +# `@polyengine/protocol`, to a caret RANGE (published dependency constraints +# must be ranges). The other two configs (conformance/driver-ct/polyengine +# and its browser/ import map) still map both `@polyengine/runtime` and +# `@polyengine/protocol` to exact pins, since the runtime ones load the same +# on-disk polyengine module and must resolve identically (stateful handles +# minted by one copy are refused by another). Manifests can therefore no +# longer be compared directly: this gate instead asserts, from the three +# repo deno.locks — +# 1. every jsr:@polyengine/* package EXCLUDING @polyengine/protocol +# resolves to the same version across the two driver-ct locks that +# still load the embedder (conformance/driver-ct/polyengine[/browser]); +# 2. @polyengine/protocol resolves to the same version across ALL THREE +# locks (it is the vocabulary that crosses every module boundary here); +# 3. polyengine-impl/deno.lock names no @polyengine/runtime specifier at +# all (the A22 host-module MUST). +# Run as part of `just polyengine-check` (CI: gha::conformance-matrix), the +# natural fail-loud point since every leg's lock is on disk there. set -euo pipefail -locks=( - polyengine-impl/deno.lock +impl_lock=polyengine-impl/deno.lock +runtime_locks=( conformance/driver-ct/polyengine/deno.lock conformance/driver-ct/polyengine/browser/deno.lock ) +all_locks=("$impl_lock" "${runtime_locks[@]}") -v=$(grep -ohP '"@polyengine/(?!protocol)[a-zA-Z-]+@[^"]+"(?=: \{)' "${locks[@]}" | sed 's/.*@//;s/"$//' | sort -u) +# (1) one @polyengine/runtime-line version across the two driver-ct locks. +v=$(grep -ohP '"@polyengine/(?!protocol)[a-zA-Z-]+@[^"]+"(?=: \{)' "${runtime_locks[@]}" | sed 's/.*@//;s/"$//' | sort -u) n=$(printf '%s\n' "$v" | wc -l) if [ "$n" != 1 ]; then - echo "polyengine pin drift across ${locks[*]}: $v" >&2 + echo "polyengine runtime-line pin drift across ${runtime_locks[*]}: $v" >&2 exit 1 fi -echo "polyengine pin OK: $v" + +# (2) one @polyengine/protocol version across all three locks. +p=$(grep -ohP '"@polyengine/protocol@[^"]+"(?=: \{)' "${all_locks[@]}" | sed 's/.*@//;s/"$//' | sort -u) +pn=$(printf '%s\n' "$p" | wc -l) +if [ "$pn" != 1 ] || [ -z "$p" ]; then + echo "@polyengine/protocol pin drift (or missing) across ${all_locks[*]}: $p" >&2 + exit 1 +fi + +# (3) polyengine-impl (a published host module, A22) must name no +# @polyengine/runtime specifier at all. +if grep -qP '"@polyengine/runtime[^"]*"' "$impl_lock" polyengine-impl/deno.json; then + echo "polyengine-impl must not import @polyengine/runtime (A22 host-module MUST); found a reference in polyengine-impl/deno.json or its lock" >&2 + exit 1 +fi + +echo "polyengine pin OK: runtime=$v protocol=$p; polyengine-impl names no @polyengine/runtime" +