diff --git a/js/runner-polyengine/README.md b/js/runner-polyengine/README.md index 06ef59b..17b176a 100644 --- a/js/runner-polyengine/README.md +++ b/js/runner-polyengine/README.md @@ -55,9 +55,12 @@ repo bundles one worker entry — the polyengine engine surface, the message loop, and its own host module, resolved through one import map — and passes `workerMain({ polyengine, suiteImports })` its inlined engine and an import-record factory. One bundle means one embedder module instance, -which is what keeps `instanceof ComponentException` true across the host-module -boundary; workers resolve no import maps, so this is the only sound -shape. The stock `browser-worker.mjs` is `workerMain()` with the +which is what keeps stateful handles (streams, futures) minted through it +usable across the host-module boundary — `@polyengine/protocol`'s errors +and brand checks (e.g. `isComponentException`) are cross-copy safe by +construction (its own stable version line), but the embedder's runtime +machinery is not; workers resolve no import maps, so this is the only +sound shape. The stock `browser-worker.mjs` is `workerMain()` with the bundleUrl-loading defaults. ## Pinning @@ -72,9 +75,13 @@ bump is one diff. Between releases every green polyengine `main` commit also publishes `-pre.g` prereleases (unordered hash versions — pin exactly) for when a not-yet-released commit is needed. -- `deno.json` — the import map holds the five `jsr:@polyengine/...@` - specifiers. `@polyengine/runtime/embedder` is mapped because - `@polyengine/wasi` imports it by bare specifier. +- `deno.json` — the import map holds the five lockstep + `jsr:@polyengine/{ct-runner,runtime/embedder,runtime/shim,translator,wasi}@` + specifiers plus a sixth, independently-versioned `@polyengine/protocol` + pin. `@polyengine/runtime/embedder` is mapped because + `browser-bundle-entry.ts` (and downstream bundled-worker consumers) + import it directly — as of upstream A22, `@polyengine/wasi` itself is + protocol-only internally, so this mapping is no longer for wasi's sake. `minimumDependencyAge` exempts the `@polyengine` scope so same-day publishes resolve (Deno >= 2.9 for the wildcard exclude). - `deno.lock` — carries JSR package integrity for that graph and is diff --git a/js/runner-polyengine/browser-bundle-entry.ts b/js/runner-polyengine/browser-bundle-entry.ts index 85bd99f..9fd5b74 100644 --- a/js/runner-polyengine/browser-bundle-entry.ts +++ b/js/runner-polyengine/browser-bundle-entry.ts @@ -12,6 +12,16 @@ // (`just polyengine-assets` builds it; verify-polyengine/viewer-build consume it.) export * from "@polyengine/runtime/embedder"; +// A22: @polyengine/runtime@0.5.0's embedder dropped its A9 courtesy +// re-exports (error classes/predicates, brands, handle classes, suspending, +// realm crossing, copy registry) — that vocabulary now lives only in +// @polyengine/protocol. worker-main.mjs's and README.md's "Browser leg" +// section document downstream bundled workers relying on `ComponentException` +// (and, by the same contract, the rest of the vocabulary) being reachable +// off this bundle's exports alongside the embedder machinery, so it is +// re-exported here too. No name collisions: the embedder no longer exports +// these names (contracts/embedder-api.md A22; brief rule 5). +export * from "@polyengine/protocol"; export { Translator } from "@polyengine/runtime/shim"; export * from "@polyengine/ct-runner"; export { wasi } from "@polyengine/wasi"; diff --git a/js/runner-polyengine/deno.json b/js/runner-polyengine/deno.json index 08d4e2a..970ee1b 100644 --- a/js/runner-polyengine/deno.json +++ b/js/runner-polyengine/deno.json @@ -9,23 +9,35 @@ // packages must carry the SAME version — `just verify-polyengine`'s pin // gate asserts that repo-wide. // - // `@polyengine/runtime/embedder` is mapped because `@polyengine/wasi` - // imports it by bare specifier. `@polyengine/translator` ships the translator + // `@polyengine/runtime/embedder` is mapped because `browser-bundle-entry.ts` + // (and this runner leg's downstream bundled-worker consumers) import it + // directly for the embedder machinery it still carries post-A22 (the wasi + // module itself is protocol-only internally as of 0.5.0, so this mapping + // no longer exists for wasi's sake — see contracts/embedder-api.md A22). + // Applications that load the embedder in more than one config must still + // resolve ONE runtime version: stateful handles minted by one copy are + // refused by another. Published host modules couple only to + // `@polyengine/protocol`, whose copies are harmless by construction (its + // own independent version line, see polyengine-pin-gate's CONTRACT note). + // `@polyengine/translator` ships the translator // wasm built from the same commit: the Deno leg loads it through the // module graph (`defaultTranslator()`, permission-free) and the browser // leg extracts it from the lock-pinned module cache — no release-asset // fetch, no sha bookkeeping. `deno.lock` carries JSR package integrity // and is enforced with `--frozen` everywhere. // - // Bumping: change the version in these five specifiers, delete deno.lock, - // re-run `deno install --entrypoint runner.ts browser-bundle-entry.ts` in - // this directory, and commit the diff (see README.md "Pinning"). + // Bumping: change the version in these five lockstep specifiers, delete + // deno.lock, re-run `deno install --entrypoint runner.ts + // browser-bundle-entry.ts` in this directory, and commit the diff (see + // README.md "Pinning"). `@polyengine/protocol` is on its own independent + // version line (currently 0.2.2) and is bumped separately. "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/translator": "jsr:@polyengine/translator@0.4.0", - "@polyengine/wasi": "jsr:@polyengine/wasi@0.4.0" + "@polyengine/ct-runner": "jsr:@polyengine/ct-runner@0.5.0", + "@polyengine/protocol": "jsr:@polyengine/protocol@0.2.2", + "@polyengine/runtime/embedder": "jsr:@polyengine/runtime@0.5.0/embedder", + "@polyengine/runtime/shim": "jsr:@polyengine/runtime@0.5.0/shim", + "@polyengine/translator": "jsr:@polyengine/translator@0.5.0", + "@polyengine/wasi": "jsr:@polyengine/wasi@0.5.0" }, // Deno's supply-chain minimum-dependency-age gate (24h default) applies // even to exact prerelease pins; polyengine publishes per commit, so the diff --git a/js/runner-polyengine/deno.lock b/js/runner-polyengine/deno.lock index 4eea9f0..4ab6829 100644 --- a/js/runner-polyengine/deno.lock +++ b/js/runner-polyengine/deno.lock @@ -1,49 +1,51 @@ { "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" }, "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" ] } }, "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" ] } } diff --git a/js/runner-polyengine/engine.mjs b/js/runner-polyengine/engine.mjs index 89bef90..4809db8 100644 --- a/js/runner-polyengine/engine.mjs +++ b/js/runner-polyengine/engine.mjs @@ -21,7 +21,9 @@ * @param {object} [input.hostImports] SUT host-import record fragments * (interface id -> implementation), merged over the engine's wasi + * test-context imports. MUST be built against the same embedder module - * instance as `bundle` (one ComponentException class; see worker-main.mjs). + * instance as `bundle` (one runtime copy for handle interop; see + * worker-main.mjs — `@polyengine/protocol` vocabulary like + * ComponentException is cross-copy safe by construction). * @returns {Promise<{newTests: () => Promise, Context, tagsOf}>} */ export async function loadSuite( diff --git a/js/runner-polyengine/worker-main.mjs b/js/runner-polyengine/worker-main.mjs index 88eee87..c6b04cd 100644 --- a/js/runner-polyengine/worker-main.mjs +++ b/js/runner-polyengine/worker-main.mjs @@ -5,9 +5,9 @@ // (`polymorph:websocket/connections`, `polymorph:webcrypto/*`, …) that // the stock worker cannot supply: workers resolve no import maps, so the // host module and the polyengine engine must arrive in ONE bundle or the -// embedder module loads twice and `instanceof ComponentException` stops -// holding across the boundary. The downstream pattern is a bundled worker -// entry: +// embedder module loads twice and stateful handles (streams, futures) +// minted through one copy are refused by the other. The downstream pattern +// is a bundled worker entry: // // // worker-entry.ts — deno bundle --platform browser // import * as polyengine from "./browser-bundle-entry.ts"; // jsr:@polyengine/* diff --git a/justfile b/justfile index 7a4d32a..b169d69 100644 --- a/justfile +++ b/justfile @@ -149,7 +149,11 @@ polyengine-pin-gate: #!/usr/bin/env bash set -euo pipefail configs=(js/runner-polyengine/deno.json) - v=$(grep -ho 'jsr:@polyengine/[a-z-]*@[^/"]*' "${configs[@]}" | sed 's/.*@//' | sort -u) + # A22: @polyengine/protocol is a second, independently-versioned line + # (repo-internal configs may exact-pin it too now) — exclude it from the + # lockstep-version extraction the same way it's excluded below against + # the lock, so pinning it in deno.json doesn't read as drift. + v=$(grep -ho 'jsr:@polyengine/[a-z-]*@[^/"]*' "${configs[@]}" | grep -v '^jsr:@polyengine/protocol@' | sed 's/.*@//' | sort -u) test -n "$v" || { echo "polyengine pin gate: no jsr:@polyengine specifiers found" >&2; exit 1; } [ "$(printf '%s\n' "$v" | wc -l)" = 1 ] || { echo "polyengine pin drift: $v" >&2; exit 1; } python3 - "$v" js/runner-polyengine/deno.lock <<'PY'