From de8cc018ab76bad3ba2536d63fd57afe7381bed0 Mon Sep 17 00:00:00 2001 From: Lann Martin Date: Sun, 9 Aug 2026 19:07:17 -0400 Subject: [PATCH] =?UTF-8?q?release:=20deltic-embedder.mjs=20=E2=80=94=20th?= =?UTF-8?q?e=20consumer=20bundle=20artifact=20(Phase=203=20enabler)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit One platform-neutral ES module (tools/release-bundle/entry.ts) carrying the whole consumer surface: embedder API + Translator + @deltic/ct-runner (runSuite, Context, import analysis, tags inventory — now re-exported from ct-runner's mod.ts as public surface) + wasiShims. Emitted with the browser lanes' own mechanism (deno bundle --platform browser); per the #8 rescope nothing in the graph generates code at run time. Gate (core matrix + AGENTS list): tools/release-bundle/bundle_test.ts builds the artifact exactly as release.yml does, asserts no node:/npm: residues, then drives the example suite through the bundle alone — translate, instantiate, execute, tag-gate (nothing tree-shaken). Also verified by hand under plain Node 24, zero flags: identical counts (callback ABI needs no JSPI) — the premise for polymorph-test's PR-lane selftest in the migration's Phase 3 (#14). release.yml: setup-deno, build the bundle, ship it in SHA256SUMS and the asset list; notes updated. --- .github/workflows/ci.yml | 2 + .github/workflows/release.yml | 23 +++++-- AGENTS.md | 1 + ct-runner/src/mod.ts | 11 +++ tools/release-bundle/build.ts | 45 ++++++++++++ tools/release-bundle/bundle_test.ts | 103 ++++++++++++++++++++++++++++ tools/release-bundle/entry.ts | 20 ++++++ 7 files changed, 200 insertions(+), 5 deletions(-) create mode 100644 tools/release-bundle/build.ts create mode 100644 tools/release-bundle/bundle_test.ts create mode 100644 tools/release-bundle/entry.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 92c99eb..0d17ac0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -92,6 +92,8 @@ jobs: - name: ct-runner working-directory: ct-runner run: deno task test + - name: release bundle (embedder artifact gate) + run: deno test -A tools/release-bundle/bundle_test.ts - name: conformance (official CM suite, Deno lane) working-directory: harness run: deno task conformance diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 57c971a..db91d5b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -30,6 +30,9 @@ jobs: - uses: dtolnay/rust-toolchain@stable with: targets: wasm32-unknown-unknown + - uses: denoland/setup-deno@v2 + with: + deno-version: "2.9.5" - name: compute tag id: tag @@ -63,8 +66,14 @@ jobs: --config 'profile.release.strip=true' cp target/wasm32-unknown-unknown/release/translator_shim.wasm deltic-translator-shim-min.wasm + - name: build embedder bundle + # The consumer-facing platform-neutral ES module (browser pages/ + # workers + plain Node): tools/release-bundle/entry.ts, gated by + # tools/release-bundle/bundle_test.ts in the core matrix. + run: deno run -A tools/release-bundle/build.ts --out deltic-embedder.mjs + - name: checksums - run: sha256sum deltic-translator-shim.wasm deltic-translator-shim-min.wasm > SHA256SUMS + run: sha256sum deltic-translator-shim.wasm deltic-translator-shim-min.wasm deltic-embedder.mjs > SHA256SUMS - name: create prerelease run: | @@ -72,9 +81,12 @@ jobs: echo "Prerelease \`${TAG}\` at ${GITHUB_SHA}." echo echo "Artifacts: the prebuilt translator shim wasm (standard release" - echo "build — what the test suites run against) and the size-tuned" - echo "variant per crates/translator-shim/README.md. Consumers need no" - echo "Rust toolchain; see README.md for status and usage." + echo "build — what the test suites run against), the size-tuned" + echo "variant per crates/translator-shim/README.md, and the embedder" + echo "bundle deltic-embedder.mjs (one platform-neutral ES module:" + echo "embedder API + Translator + ct-runner + wasi-shims — browsers" + echo "and plain Node, no flags). Consumers need no Rust toolchain;" + echo "see README.md for status and usage." echo echo '```' cat SHA256SUMS @@ -86,7 +98,8 @@ jobs: --prerelease \ --title "deltic $TAG" \ --notes-file notes.md \ - deltic-translator-shim.wasm deltic-translator-shim-min.wasm SHA256SUMS + deltic-translator-shim.wasm deltic-translator-shim-min.wasm \ + deltic-embedder.mjs SHA256SUMS env: GH_TOKEN: ${{ github.token }} TAG: ${{ steps.tag.outputs.tag }} diff --git a/AGENTS.md b/AGENTS.md index f473bc0..6f1e546 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -32,6 +32,7 @@ cargo test -p translator-shim -p bindgen -p testgen (cd harness && deno task conformance) # official CM suite, Deno lane (cd wasi-shims && deno task test) (cd ct-runner && deno task test) +deno test -A tools/release-bundle/bundle_test.ts # embedder-bundle release asset (cd ports/websocket && deno task test) # + deno task conformance (spawns their echod) deno run --allow-read tools/smoke-tls/run.ts --exec # polymorph-tls suite (issue #18) (cd ports/webcrypto && deno test --allow-read tests/) diff --git a/ct-runner/src/mod.ts b/ct-runner/src/mod.ts index d959a2e..fc8fc7c 100644 --- a/ct-runner/src/mod.ts +++ b/ct-runner/src/mod.ts @@ -22,3 +22,14 @@ export { } from "./import-analysis.ts"; export { Context, TEST_CONTEXT_INTERFACE, testContextImportRecord } from "./context.ts"; + +export { + applies, + collectTagsSections, + firstExcluding, + loadTagsInventory, + parseTagsRecords, + TAGS_SECTION, + type TagsInventory, + tagsOf, +} from "./tags.ts"; diff --git a/tools/release-bundle/build.ts b/tools/release-bundle/build.ts new file mode 100644 index 0000000..2ce5273 --- /dev/null +++ b/tools/release-bundle/build.ts @@ -0,0 +1,45 @@ +// Builds the consumer-facing embedder bundle (see ./entry.ts) — the +// `deltic-embedder.mjs` release asset. Same emission mechanism as the +// browser lanes (tools/browser/bundle.ts): `deno bundle --platform browser`, +// which resolves the workspace's `@deltic/*` bare specifiers natively and +// fails on `node:` residues (the runtime is platform-neutral by contract, +// docs/architecture.md §4.3). +// +// Usage: deno run -A tools/release-bundle/build.ts [--out ] +// (default: tools/release-bundle/dist/deltic-embedder.mjs, gitignored) + +import { dirname, fromFileUrl, join, normalize } from "jsr:@std/path@1"; + +const repoRoot = normalize( + join(dirname(fromFileUrl(import.meta.url)), "..", ".."), +); + +export async function buildBundle(out?: string): Promise { + const outPath = out ?? + join(repoRoot, "tools", "release-bundle", "dist", "deltic-embedder.mjs"); + await Deno.mkdir(dirname(outPath), { recursive: true }); + const cmd = new Deno.Command(Deno.execPath(), { + args: [ + "bundle", + "--platform", + "browser", + "--format", + "esm", + "-o", + outPath, + join(repoRoot, "tools", "release-bundle", "entry.ts"), + ], + cwd: repoRoot, + stdout: "inherit", + stderr: "inherit", + }); + const { code } = await cmd.output(); + if (code !== 0) throw new Error(`deno bundle failed with code ${code}`); + return outPath; +} + +if (import.meta.main) { + const outIdx = Deno.args.indexOf("--out"); + const out = outIdx >= 0 ? Deno.args[outIdx + 1] : undefined; + console.log(await buildBundle(out)); +} diff --git a/tools/release-bundle/bundle_test.ts b/tools/release-bundle/bundle_test.ts new file mode 100644 index 0000000..ff4c8b5 --- /dev/null +++ b/tools/release-bundle/bundle_test.ts @@ -0,0 +1,103 @@ +// The release-asset gate for the embedder bundle: build it exactly as the +// release workflow does, then prove the artifact stands alone — imports as +// one self-contained ES module, carries no platform residues (the runtime +// is platform-neutral by contract, docs/architecture.md §4.3), and drives a +// real suite end to end INCLUDING the tag-gating path (nothing was +// tree-shaken away). Runs in the core CI matrix, which builds the +// translator wasm and the example guests earlier in the job. + +import { buildBundle } from "./build.ts"; + +const root = new URL("../../", import.meta.url); +const TRANSLATOR = new URL( + "target/wasm32-unknown-unknown/release/translator_shim.wasm", + root, +); +const SUITE = new URL( + "examples/guests/build/test-suite.component.wasm", + root, +); + +async function present(url: URL): Promise { + try { + await Deno.stat(url); + return true; + } catch { + return false; + } +} +const ready = (await present(TRANSLATOR)) && (await present(SUITE)); + +function assertEq(got: T, want: T, msg?: string): void { + const g = JSON.stringify(got); + const w = JSON.stringify(want); + if (g !== w) throw new Error(`${msg ?? "mismatch"}: got ${g}, want ${w}`); +} + +/** Append a `component-test:tags@0.1` custom section (same encoding as + * ct-runner/tests/tags_test.ts — id 0, LEB name + data, legal anywhere). */ +function withTags(bytes: Uint8Array, records: string): Uint8Array { + const enc = new TextEncoder(); + const leb = (n: number): number[] => { + const out: number[] = []; + do { + let b = n & 0x7f; + n >>>= 7; + if (n !== 0) b |= 0x80; + out.push(b); + } while (n !== 0); + return out; + }; + const name = enc.encode("component-test:tags@0.1"); + const data = enc.encode(records); + const payload = [...leb(name.length), ...name, ...data]; + const section = new Uint8Array([0x00, ...leb(payload.length), ...payload]); + const out = new Uint8Array(bytes.length + section.length); + out.set(bytes, 0); + out.set(section, bytes.length); + return out; +} + +Deno.test({ + name: "release bundle: self-contained, platform-neutral, runs a suite (tags included)", + ignore: !ready, + fn: async () => { + const out = await buildBundle(); + + // Platform purity of the ARTIFACT (the lanes pin the sources; + // this pins the emission): no node:/npm: residues, ESM shape. + const text = await Deno.readTextFile(out); + assertEq(/from\s*["']node:/.test(text), false, "node: import residue"); + assertEq(/require\(["']node:/.test(text), false, "node: require residue"); + assertEq(/from\s*["']npm:/.test(text), false, "npm: specifier residue"); + + const mod = await import(new URL(`file://${out}`).href); + + // The full consumer path through the bundle alone: translate, + // instantiate, enumerate, execute, tag-gate. + const translator = await mod.Translator.create( + await Deno.readFile(TRANSLATOR), + ); + const componentBytes = withTags( + await Deno.readFile(SUITE), + "suite/basic/pass\nsuite/basic/fail\nsuite/basic/skip\n" + + "suite/diag/chatty\nsuite/diag/slow hw\nsuite/nested/deep/leaf\n", + ); + const { plan, adapters } = translator.translate(componentBytes); + + const lines: string[] = []; + const counts = await mod.runSuite({ plan, componentBytes, adapters }, { + target: "deltic/bundle", + suiteName: "test-suite", + missing: ["hw"], + emit: (l: string) => lines.push(l), + }); + assertEq(counts, { passed: 3, failed: 1, skipped: 1, na: 1, total: 6 }); + assertEq(JSON.parse(lines[0]).run.scheduling, "tags"); + + // The wasi-shims surface came along too (polymorph consumers wire it). + assertEq(typeof mod.wasiShims, "function"); + const shims = mod.wasiShims(); + assertEq(typeof shims["wasi:cli/environment@0.2"], "object"); + }, +}); diff --git a/tools/release-bundle/entry.ts b/tools/release-bundle/entry.ts new file mode 100644 index 0000000..6c0df80 --- /dev/null +++ b/tools/release-bundle/entry.ts @@ -0,0 +1,20 @@ +// The consumer-facing embedder bundle: one platform-neutral ES module +// carrying the embedder API surface plus the L3 runner glue, for consumers +// that cannot import deltic's TS sources directly — browser pages/workers +// and plain Node (the callback ABI needs no JSPI flag, so stock `node` can +// import this). Built by ./build.ts with `deno bundle --platform browser` +// (the same emission the browser lanes use, tools/browser/bundle.ts) and +// shipped as the `deltic-embedder.mjs` release asset (#16 interim scheme). +// +// Surface discipline: everything here is already public — the embedder API +// (contracts/embedder-api.md), the shim's `Translator`, `@deltic/ct-runner` +// (runSuite + Context + import analysis + the tags inventory), and +// `@deltic/wasi-shims`. The bundle adds no API of its own; per the #8 +// rescope there is no runtime code generation anywhere in this graph +// (nothing needs CSP beyond `wasm-unsafe-eval`). + +export * from "@deltic/runtime/embedder"; +export { Translator } from "@deltic/runtime/shim"; +export * from "@deltic/ct-runner"; +export { wasiShims } from "@deltic/wasi-shims"; +export type { WasiShims, WasiShimsOptions } from "@deltic/wasi-shims";