Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,41 @@
import { readFileSync } from "node:fs";
import { resolve as resolvePath } from "node:path";
import { describe, expect, it } from "@suaveplan/testing/runner";
interface PackageJsonShape {
readonly dependencies?: Readonly<Record<string, string>>;
}
describe("@suaveplan/api-sandbox-wasm — dep graph", () => {
it("pins the runtime dep set to the documented allowlist", {
timeout: 5000,
}, () => {
const pkg = JSON.parse(
readFileSync(resolvePath(__dirname, "..", "package.json"), "utf8")
) as PackageJsonShape;
const deps = Object.keys(pkg.dependencies ?? {}).sort();
// The proposal pins the @suaveplan/* runtime surface; external
// deps (zod, tslib, @opentelemetry/api) come from the host
// stack and are intentionally not enumerated here.
expect(deps).toEqual([
"@opentelemetry/api",
"@suaveplan/api-types",
"@suaveplan/error",
"@suaveplan/logger",
"tslib",
"zod",
]);
});
it("does not depend on any sandbox-host sibling", { timeout: 5000 }, () => {
const pkg = JSON.parse(
readFileSync(resolvePath(__dirname, "..", "package.json"), "utf8")
) as PackageJsonShape;
const deps = Object.keys(pkg.dependencies ?? {});
expect(deps.some((d) => d.startsWith("@suaveplan/api-sandbox-"))).toBe(
false
);
expect(deps).not.toContain("@suaveplan/api-host");
expect(deps).not.toContain("@suaveplan/api-worker-host");
});
});

View File

@@ -0,0 +1,80 @@
import { describe, expect, it } from "@suaveplan/testing/runner";
import { buildFixtureWasm } from "./build-wasm.js";
describe("buildFixtureWasm", () => {
it("emits the WASM magic + version preamble", { timeout: 5000 }, () => {
const bytes = buildFixtureWasm({ importProcExit: false });
expect([...bytes.subarray(0, 4)]).toEqual([0x00, 0x61, 0x73, 0x6d]);
expect([...bytes.subarray(4, 8)]).toEqual([0x01, 0x00, 0x00, 0x00]);
});
it("produces a module valid under WebAssembly.validate", {
timeout: 5000,
}, () => {
expect(
WebAssembly.validate(buildFixtureWasm({ importProcExit: true }))
).toBe(true);
expect(
WebAssembly.validate(buildFixtureWasm({ importProcExit: false }))
).toBe(true);
expect(
WebAssembly.validate(
buildFixtureWasm({
capabilityImports: ["secrets_get", "secrets_require"],
})
)
).toBe(true);
});
it("threads the requested WASI version into the import module name", {
timeout: 5000,
}, async () => {
const bytes = buildFixtureWasm({
wasiVersion: "wasi_snapshot_preview0",
importProcExit: true,
});
const module = await WebAssembly.compile(bytes);
const imports = WebAssembly.Module.imports(module);
expect(imports.some((i) => i.module === "wasi_snapshot_preview0")).toBe(
true
);
});
it("omits the WASI import when importProcExit is false", {
timeout: 5000,
}, async () => {
const module = await WebAssembly.compile(
buildFixtureWasm({ importProcExit: false })
);
const imports = WebAssembly.Module.imports(module);
expect(imports.some((i) => i.module.startsWith("wasi_"))).toBe(false);
});
it("emits capability imports under suaveplan_caps", {
timeout: 5000,
}, async () => {
const module = await WebAssembly.compile(
buildFixtureWasm({ capabilityImports: ["secrets_get"] })
);
const imports = WebAssembly.Module.imports(module);
expect(imports.find((i) => i.name === "secrets_get")?.module).toBe(
"suaveplan_caps"
);
});
it("always exports _start", { timeout: 5000 }, async () => {
const module = await WebAssembly.compile(buildFixtureWasm());
const exports = WebAssembly.Module.exports(module);
expect(exports.find((e) => e.name === "_start")?.kind).toBe("function");
});
it("defaults wasiVersion to wasi_snapshot_preview1", {
timeout: 5000,
}, async () => {
const module = await WebAssembly.compile(buildFixtureWasm());
const imports = WebAssembly.Module.imports(module);
expect(imports.some((i) => i.module === "wasi_snapshot_preview1")).toBe(
true
);
});
});

View File

@@ -0,0 +1,138 @@
/**
* Tiny WASM-module builder used by in-package fixtures + the
* conformance harness. Emits hand-encoded WebAssembly bytes per the
* binary format spec (https://webassembly.github.io/spec/core/binary/).
*
* Only enough to construct test modules — one functype `() -> ()`, one
* import, one export, an `_start` body. NOT a general-purpose WAT
* compiler. Kept inside the package so the test suite has no external
* build step (`wabt` / `wat2wasm`); the fixture wasm is produced
* deterministically at runtime.
*/
const MAGIC = new Uint8Array([0x00, 0x61, 0x73, 0x6d]);
const VERSION = new Uint8Array([0x01, 0x00, 0x00, 0x00]);
interface BuilderOptions {
/** WASI version module name to import (default `wasi_snapshot_preview1`). */
readonly wasiVersion?: string;
/** Whether to import `wasi_snapshot_preview1.proc_exit`. */
readonly importProcExit?: boolean;
/** Additional capability imports — emitted under `suaveplan_caps`. */
readonly capabilityImports?: readonly string[];
/** Export a `memory` of `initial` pages — exercises sandbox memory binding. */
readonly exportMemory?: boolean;
}
/**
* Build a deterministic, validator-clean WASM module with the requested
* imports + an exported `_start` whose body is just `end`. Adequate for
* `WebAssembly.compile` + `WebAssembly.instantiate` against a matching
* imports object.
*/
export function buildFixtureWasm(options: BuilderOptions = {}): Uint8Array {
const wasiVersion = options.wasiVersion ?? "wasi_snapshot_preview1";
const importProcExit = options.importProcExit !== false;
const capImports = options.capabilityImports ?? [];
// Type section: index 0 = () -> () (for _start + capability imports);
// index 1 = (i32) -> () (for proc_exit).
const types: number[] = [
0x02 /* count */,
...funcType([], []),
...funcType([0x7f], []),
];
const typeSection = section(0x01, types);
// Import section.
const importEntries: number[][] = [];
if (importProcExit) {
importEntries.push(importEntry(wasiVersion, "proc_exit", 0x00, 1));
}
for (const cap of capImports) {
importEntries.push(importEntry("suaveplan_caps", cap, 0x00, 0));
}
const importSection = section(0x02, [
importEntries.length,
...flatten(importEntries),
]);
// Function section: one local function, type 0.
const funcSection = section(0x03, [0x01, 0x00]);
// Optional memory section: 1 limits entry, flags=0 (no max), initial=1 page.
const exportMemory = options.exportMemory === true;
const memorySection = exportMemory ? section(0x05, [0x01, 0x00, 0x01]) : [];
// Export section: "_start" + optional "memory".
const localFnIndex = importEntries.length;
const exportItems: number[][] = [exportEntry("_start", 0x00, localFnIndex)];
if (exportMemory) {
exportItems.push(exportEntry("memory", 0x02, 0));
}
const exportSection = section(0x07, [
exportItems.length,
...flatten(exportItems),
]);
// Code section: one body, locals=0, end.
const codeSection = section(0x0a, [0x01, 0x02, 0x00, 0x0b]);
return concatBytes([
MAGIC,
VERSION,
new Uint8Array(typeSection),
new Uint8Array(importSection),
new Uint8Array(funcSection),
new Uint8Array(memorySection),
new Uint8Array(exportSection),
new Uint8Array(codeSection),
]);
}
function funcType(
params: readonly number[],
results: readonly number[]
): number[] {
return [0x60, params.length, ...params, results.length, ...results];
}
function importEntry(
module: string,
field: string,
kind: number,
typeIndex: number
): number[] {
return [...encodeName(module), ...encodeName(field), kind, typeIndex];
}
function exportEntry(name: string, kind: number, index: number): number[] {
return [...encodeName(name), kind, index];
}
function encodeName(value: string): number[] {
const bytes = new TextEncoder().encode(value);
return [bytes.length, ...bytes];
}
function section(id: number, payload: readonly number[]): number[] {
return [id, payload.length, ...payload];
}
function flatten(arrays: readonly number[][]): number[] {
const out: number[] = [];
for (const a of arrays) out.push(...a);
return out;
}
function concatBytes(parts: readonly Uint8Array[]): Uint8Array {
let total = 0;
for (const p of parts) total += p.length;
const out = new Uint8Array(total);
let offset = 0;
for (const p of parts) {
out.set(p, offset);
offset += p.length;
}
return out;
}