Files
2026-05-27 23:29:53 +01:00

7.8 KiB

Tasks — TS→WASM Compiler (Javy-backed, WASI-stdio variant)

0. Pre-flight (non-negotiable)

  • 0.1 Clean working tree; account for every .claude/worktrees/agent-*
  • 0.2 api-sandbox-wasm-stdio-abi has shipped (the WASI-stdio bridge variant + protocol types are merged on genesis)
  • 0.3 §0a Sync: worktree-isolated agents fast-forward to orchestrator-supplied SHA

1. Javy binary distribution (5 wrapper packages)

  • 1.1 Author javy-cli-linux-x64 under packages/devtools/tooling/javy-cli-linux-x64/ — package.json with matching os: ["linux"] + cpu: ["x64"], bin/javy shim that execs the bundled binary
  • 1.2 Author javy-cli-linux-arm64, javy-cli-darwin-x64, javy-cli-darwin-arm64, javy-cli-win32-x64 mirroring the same structure
  • 1.3 Each wrapper carries a postinstall script that downloads the prebuilt Javy v8.1.1 release for its platform from github.com/bytecodealliance/javy/releases with SHA-256 checksum verification. Checksums committed; binaries not committed.
  • 1.4 Catalog the Javy version centrally in root package.json (catalog.wasm.javy: "v8.1.1" or extend catalog.tooling) — every wrapper pins to the same value
  • 1.5 Each wrapper exports a getJavyBinary(): string helper returning the absolute path to its packaged binary (consumers use this, not raw bin/javy, so they don't need to spawn a child process just to locate the binary)
  • 1.6 Paired README.md documenting the platform served

2. Main package scaffold

  • 2.1 packages/api-stack/api/wasm-compiler-javy/ with package.json (0.1.0, server-tier), tsconfig.lib.json, vite.config.ts, vitest.config.ts
  • 2.2 Deps: @suaveplan/api-types, @suaveplan/error, @suaveplan/logger, @suaveplan/opentelemetry, tslib, zod. Optional platform deps: every javy-cli-* wrapper. peerDeps: none.
  • 2.3 bin: { "suaveplan-wasm-compile": "./dist/bin/cli.js" }
  • 2.4 Subpath exports: ./testing for the conformance harness

3. Implementation

  • 3.1 src/bundle/bundle.ts — bundleTs({ entry }) via Bun.build({ entrypoints: [entry], target: "browser", format: "esm" }). Rejects node:* specifiers with WASM_COMPILER.BROWSER_INCOMPAT_DEPENDENCY + context.specifier. Returns bundled JS as a string.
  • 3.2 src/abi-shim/abi-shim.ts — generates the WASI-stdio ABI header JS. The shim:
    • Implements length-prefix framed read from process.stdin (or Javy.IO.readSync if available — pick whichever the v8.1.1 host model exposes).
    • Implements length-prefix framed write to process.stdout.
    • Validates PluginEnvelopeIn shape with inlined Zod-equivalent checks (cannot ship full Zod inside the WASM bundle — too heavy). Hand-written validators are acceptable and DOCUMENTED in abi-shim.md.
    • Dispatches by kind to the plugin's exported routes, handleRequest, optional start, optional dispose.
    • Catches SuaveplanError-shaped throws and emits {kind:"error", code, message} envelopes.
    • Byte-stable output for reproducible builds.
  • 3.3 src/abi-shim/abi-shim.md documents the wire format AND the protocol-version pin AND every dispatch case.
  • 3.4 src/javy/javy-locate.ts — picks the right javy-cli-* wrapper by process.platform + process.arch. Raises WASM_COMPILER.JAVY_PLATFORM_MISMATCH with context.requested if none. Uses each wrapper's getJavyBinary() export.
  • 3.5 src/javy/javy-invoke.ts — child_process.spawnSync against the binary with ["build", inputPath, "-o", outputPath] (+ -O when optimizeFor === "speed"). Captures stdout/stderr; non-zero exit → WASM_COMPILER.JAVY_INVOCATION_FAILED with {exitCode, stderr (8 KiB trunc), command}.
  • 3.6 src/validate/abi-validate.ts — WebAssembly.compile(bytes) + module reflection. Asserts memory export, _start export, AND that the module imports every entry in the WASI snapshot-preview1 allowlist needed by the bridge (fd_read, fd_write, environ_get, environ_sizes_get, proc_exit). Missing → WASM_COMPILER.ABI_VALIDATION_FAILED with context.missing: string[].
  • 3.7 src/route-extract/route-extract.ts — boots the produced WASM under a host-side WASI runner (@bjorn3/browser_wasi_shim or a thin in-house wrapper), sends a {kind:"routes"} envelope, captures the {kind:"routes", routes} reply. Returns the route list.
  • 3.8 src/compile/compile.ts — top-level compilePluginToWasm({ pluginEntry, outDir, optimizeFor?, sourcemaps?, javyVersion? }) orchestrates bundle → shim → javy → validate → route-extract → manifest emit. Returns { wasmPath, metadataPath, sizeBytes, compilationMs }.
  • 3.9 src/manifest/manifest.ts — emits <basename>.wasm.json with {routes, abiVersion, abiVariant: "wasi-stdio", javyVersion, compilerVersion, compiledAt (RFC 3339 UTC), sourceDigest}. Empty routes → WASM_COMPILER.MANIFEST_MISSING_ROUTES.
  • 3.10 src/bin/cli.ts — suaveplan-wasm-compile <entry> --out-dir <dir> [--optimize-for size|speed] [--sourcemaps]. JSON on stdout, structured error JSON on stderr.
  • 3.11 src/error-codes/error-codes.ts — 6 WASM_COMPILER codes via defineCodes + WasmCompilerError extends SuaveplanError with public constructor.
  • 3.12 src/instruments/instruments.ts — OTel meter @suaveplan/wasm-compiler-javy with the 3 instruments described in the proposal.
  • 3.13 Paired .md per implementation file.

4. Conformance harness

  • 4.1 src/testing/wasm-compiler-conformance/wasm-compiler-conformance.ts — runWasmCompilerConformance({ compileFn }) with all 4 invariants from the spec.

5. Tests

  • 5.1 tests/fixtures/tiny-plugin/src/index.ts — minimal TS plugin with two routes + a start() and dispose() hook
  • 5.2 tests/integration/tiny-plugin.test.ts — compile fixture, assert ABI validation passes, deploy via api-sandbox-wasm-host-bridge with abiVariant: "wasi-stdio", exercise both routes + lifecycle, assert manifest routes match.
  • 5.3 tests/integration/api-plugin-example.test.ts — compile @suaveplan/api-plugin-example/wasm (the WASI-stdio subpath, not the trusted-tier root), deploy through the bridge with abiVariant: "wasi-stdio", exercise every declared route via createTestHost. KEYSTONE TEST for Stage-8 gate 2.8. Requires api-plugin-example-dual-shape to have shipped first.
  • 5.4 tests/integration/determinism.test.ts — compile twice with identical inputs, assert SHA-256 of .wasm bytes is identical.
  • 5.5 src/javy/javy-locate.test.ts — covers each platform-arch combination via mocked process.platform/arch (this IS rule §2-permitted — it's testing platform branch logic, not a service).
  • 5.6 Coverage on every touched module. Realized at 90.49% stmts / 84.43% branches / 85% funcs / 89.93% lines (above package-calibrated thresholds 85/75/80/85). Uncovered branches map to (a) compile-orchestrator failure-mode propagation paths whose triggers would require mocking real Javy/sandbox failures (rule §2 bans mocks), (b) CLI default-IO closures only reachable via the bin script itself, (c) the extractRoutes routes_reply_kind defensive throw and wasm-compiler-conformance bridgeDeploy optional branch. All on-path code is covered by the keystone integration tests.
  • 5.7 No // biome-ignore; every test file imports from @suaveplan/testing/runner; no export * in any non-root barrel.

6. Gate

  • 6.1 bunx --bun turbo lint typecheck test:coverage --filter=@suaveplan/wasm-compiler-javy --filter=@suaveplan/api-sandbox-wasm-host-bridge --filter=@suaveplan/api-plugin-example --concurrency=4 green
  • 6.2 bun scripts/folder-reorg/generate-manifest.ts to refresh MANIFEST.md

7. Archive (orchestrator step)

  • 7.1 Fold delta into openspec/specs/wasm-compiler-javy/spec.md
  • 7.2 Move folder → openspec/archive/2026-05-27-wasm-compiler-javy/
  • 7.3 Refresh MANIFEST.md