Expose the existing WABT command deadline as an optional validated per-call CLI/MCP budget, preserving its 30000 ms default and fixed safety allowances. Retain selected execution profiles in success and rejected-module Evidence. Keep typed timeout recovery through cleanup failures without duplicating command output or making incomplete cleanup retryable. Verified focused tests, real WABT CLI/MCP workflows and actual Codex recovery/blocked controls; detailed measurements and qualification limits are recorded in issue #5797. Merge uses the explicitly authorized maintainer override with pending CI and unchecked human-review attestations disclosed on PR #5798. Closes #5797 Assisted-by: Kogasa (gensokyo-skills:kogasa-surprise-testing)
997 lines
32 KiB
TypeScript
997 lines
32 KiB
TypeScript
import { createHash } from "node:crypto";
|
|
import { access, chmod, readFile, realpath, writeFile } from "node:fs/promises";
|
|
import { join } from "node:path";
|
|
import { expect, it, onTestFinished, vi } from "vitest";
|
|
import { createTestTempDirectory } from "../../tests/fixtures/temporaryDirectory.js";
|
|
import { WabtArtifactProvider } from "./WabtArtifactProvider.js";
|
|
import { wasmArtifactSchema } from "../domain/wasm/wasmArtifact.js";
|
|
import { PrivateRuntimeRoot } from "../process/PrivateRuntimeRoot.js";
|
|
import { PrivateRuntimeRootUnavailableError } from "../process/PrivateRuntimeRootUnavailableError.js";
|
|
import { spawnOwnedProviderProcess } from "../process/ProviderProcess.js";
|
|
import { OwnedCommandFailure } from "../process/OwnedCommand.js";
|
|
import { projectAnalysisError } from "../domain/analysisErrorProjection.js";
|
|
import { WABT_LIMITS } from "./WabtRelease.js";
|
|
import { wabtFailure } from "./WabtFailures.js";
|
|
|
|
const fileCloseFailure = vi.hoisted(() => ({
|
|
selectedPath: "",
|
|
attempts: 0,
|
|
failFirst: false,
|
|
failRead: undefined as (() => Error) | undefined,
|
|
}));
|
|
|
|
vi.mock("node:fs/promises", async (importOriginal) => {
|
|
const fs = await importOriginal<typeof import("node:fs/promises")>();
|
|
return {
|
|
...fs,
|
|
open: async (...args: Parameters<typeof fs.open>) => {
|
|
const handle = await fs.open(...args);
|
|
if (String(args[0]) !== fileCloseFailure.selectedPath) return handle;
|
|
const close = handle.close.bind(handle);
|
|
const failure = fileCloseFailure.failRead;
|
|
if (failure !== undefined) {
|
|
fileCloseFailure.failRead = undefined;
|
|
vi.spyOn(handle, "stat").mockImplementationOnce(() => {
|
|
throw failure();
|
|
});
|
|
}
|
|
handle.close = async () => {
|
|
fileCloseFailure.attempts += 1;
|
|
if (fileCloseFailure.failFirst) {
|
|
fileCloseFailure.failFirst = false;
|
|
throw new Error("injected WABT input close failure");
|
|
}
|
|
return close();
|
|
};
|
|
return handle;
|
|
},
|
|
};
|
|
});
|
|
|
|
const outcomeModes = [
|
|
"success",
|
|
"invalid",
|
|
"invalid-close",
|
|
"invalid-close-failure",
|
|
"snapshot-close-failure",
|
|
"snapshot-read-failure-late-abort",
|
|
"malformed",
|
|
"timeout",
|
|
"cancelled",
|
|
"cancelled-close",
|
|
"cancelled-close-failure",
|
|
"cleanup",
|
|
"changed-snapshot",
|
|
"malformed-wat",
|
|
"wat-warning",
|
|
] as const;
|
|
type OutcomeMode = (typeof outcomeModes)[number];
|
|
|
|
const failureTags: Readonly<Record<Exclude<OutcomeMode, "success">, string>> = {
|
|
invalid: "AnalysisInputError",
|
|
"invalid-close": "AnalysisInputError",
|
|
"invalid-close-failure": "ProviderAdapterError",
|
|
"snapshot-close-failure": "AnalysisOutputError",
|
|
"snapshot-read-failure-late-abort": "AnalysisOutputError",
|
|
timeout: "AnalysisTimeoutError",
|
|
cancelled: "AnalysisCancelledError",
|
|
"cancelled-close": "AnalysisCancelledError",
|
|
"cancelled-close-failure": "ProviderAdapterError",
|
|
"changed-snapshot": "AnalysisArtifactChangedError",
|
|
cleanup: "ProviderAdapterError",
|
|
malformed: "AnalysisOutputError",
|
|
"malformed-wat": "AnalysisOutputError",
|
|
"wat-warning": "AnalysisOutputError",
|
|
};
|
|
|
|
const assertCleanupRecovery = async (options: {
|
|
readonly provider: WabtArtifactProvider;
|
|
readonly result: Awaited<ReturnType<WabtArtifactProvider["inspect"]>>;
|
|
readonly path: string;
|
|
readonly runtimePath: string;
|
|
readonly cleanupAttempts: () => number;
|
|
readonly runtimeCreations: () => number;
|
|
}): Promise<void> => {
|
|
expect(options.result).toMatchObject({
|
|
error: {
|
|
cleanupIncomplete: true,
|
|
cleanupResources: [options.runtimePath],
|
|
partialObservation: { normalized_result: { validation: "valid" } },
|
|
},
|
|
});
|
|
await expect(access(options.runtimePath)).resolves.toBeUndefined();
|
|
const blocked = await options.provider.inspect({
|
|
path: options.path,
|
|
glue_paths: [],
|
|
candidate_paths: [],
|
|
});
|
|
expect(blocked).toMatchObject({
|
|
ok: false,
|
|
error: {
|
|
cleanupIncomplete: true,
|
|
cleanupResources: [options.runtimePath],
|
|
},
|
|
});
|
|
expect(options.cleanupAttempts()).toBe(2);
|
|
expect(options.runtimeCreations()).toBe(1);
|
|
await options.provider.close();
|
|
expect(options.cleanupAttempts()).toBe(3);
|
|
await expect(access(options.runtimePath)).rejects.toMatchObject({
|
|
code: "ENOENT",
|
|
});
|
|
expect(options.result).toMatchObject({
|
|
error: { cleanupIncomplete: false, cleanupResources: [] },
|
|
});
|
|
expect(blocked).toMatchObject({
|
|
error: { cleanupIncomplete: false, cleanupResources: [] },
|
|
});
|
|
};
|
|
|
|
const assertCancellationDuringRootRelease = async (options: {
|
|
readonly mode: "cancelled-close" | "cancelled-close-failure";
|
|
readonly result: Awaited<ReturnType<WabtArtifactProvider["inspect"]>>;
|
|
readonly provider: WabtArtifactProvider;
|
|
readonly runtimePath: string;
|
|
readonly cleanupAttempts: () => number;
|
|
readonly cancellationReason: unknown;
|
|
}): Promise<void> => {
|
|
if (options.mode === "cancelled-close") {
|
|
expect(options.result).toMatchObject({
|
|
ok: false,
|
|
error: {
|
|
_tag: "AnalysisCancelledError",
|
|
partialObservation: { normalized_result: { validation: "valid" } },
|
|
cleanupIncomplete: false,
|
|
},
|
|
});
|
|
if (!options.result.ok)
|
|
expect(options.result.error.cause).toBe(options.cancellationReason);
|
|
return;
|
|
}
|
|
expect(options.result).toMatchObject({
|
|
ok: false,
|
|
error: {
|
|
_tag: "ProviderAdapterError",
|
|
cleanupIncomplete: true,
|
|
cleanupResources: [options.runtimePath],
|
|
partialObservation: { normalized_result: { validation: "valid" } },
|
|
diagnostics: { primary_error: { code: "cancelled" } },
|
|
},
|
|
});
|
|
expect(options.cleanupAttempts()).toBe(1);
|
|
if (!options.result.ok)
|
|
expect(options.result.error.cleanupPrimary?.cause).toBe(
|
|
options.cancellationReason,
|
|
);
|
|
await expect(access(options.runtimePath)).resolves.toBeUndefined();
|
|
await options.provider.close();
|
|
expect(options.cleanupAttempts()).toBe(2);
|
|
expect(options.result).toMatchObject({
|
|
error: { cleanupIncomplete: false, cleanupResources: [] },
|
|
});
|
|
};
|
|
|
|
const createWabtArtifactFixture = async () => {
|
|
const selectedRoot = await createTestTempDirectory("rea-wabt-seam-");
|
|
const path = join(selectedRoot, "selected.wasm");
|
|
const bytes = Buffer.alloc(76, 1);
|
|
await writeFile(path, bytes);
|
|
for (const tool of ["wasm-validate", "wasm-objdump", "wasm2wat"])
|
|
await writeFile(
|
|
join(selectedRoot, tool + (process.platform === "win32" ? ".exe" : "")),
|
|
"source-owned executable seam",
|
|
{ mode: 0o700 },
|
|
);
|
|
const dump = await readFile(
|
|
new URL("../../tests/fixtures/wasm/objdump.txt", import.meta.url),
|
|
"utf8",
|
|
);
|
|
return { selectedRoot, path, bytes, dump };
|
|
};
|
|
|
|
const assertModeOutcome = (
|
|
mode: OutcomeMode,
|
|
result: Awaited<ReturnType<WabtArtifactProvider["inspect"]>>,
|
|
path: string,
|
|
): void => {
|
|
if (mode.startsWith("invalid"))
|
|
expect(result).toMatchObject({
|
|
error: {
|
|
partialObservation: {
|
|
parameters: { command_timeout_ms: 42_000 },
|
|
normalized_result: {
|
|
tool_profile: {
|
|
timeout_ms: 42_000,
|
|
output_budget_bytes: WABT_LIMITS.outputBytes,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
});
|
|
if (mode === "success")
|
|
expect(result).toMatchObject({
|
|
ok: true,
|
|
value: {
|
|
result: {
|
|
artifact: { path, bytes: 76 },
|
|
validation: "valid",
|
|
runtime_execution: "not-performed",
|
|
},
|
|
},
|
|
});
|
|
else
|
|
expect(result).toMatchObject({
|
|
ok: false,
|
|
error: { _tag: failureTags[mode] },
|
|
});
|
|
if (mode === "invalid" || mode === "invalid-close")
|
|
expect(result).toMatchObject({
|
|
error: { capturedOutput: { stderr: "module.wasm: invalid bytes\n" } },
|
|
});
|
|
if (mode === "malformed-wat" || mode === "wat-warning")
|
|
expect(result).toMatchObject({
|
|
error: {
|
|
capturedOutput: {
|
|
stdout:
|
|
mode === "malformed-wat"
|
|
? "unexpected decoder output\n"
|
|
: '(module (@custom "test" "payload"))\n',
|
|
stderr: mode === "wat-warning" ? "decoder warning\n" : "",
|
|
},
|
|
},
|
|
});
|
|
};
|
|
|
|
const assertValidationCleanupRecovery = async (
|
|
provider: WabtArtifactProvider,
|
|
result: Awaited<ReturnType<WabtArtifactProvider["inspect"]>>,
|
|
cleanupAttempts: () => number,
|
|
): Promise<void> => {
|
|
expect(result).toMatchObject({ error: { cleanupIncomplete: true } });
|
|
await provider.close();
|
|
expect(cleanupAttempts()).toBe(2);
|
|
if (result.ok) throw new Error("Expected validation failure");
|
|
expect(result.error.cleanupIncomplete).toBe(false);
|
|
expect(projectAnalysisError(result.error)).toMatchObject({
|
|
code: "invalid_request",
|
|
details: {
|
|
captured_output: { stderr: "module.wasm: invalid bytes\n" },
|
|
cleanup_history: expect.any(Object),
|
|
},
|
|
});
|
|
};
|
|
|
|
it("returns actionable unavailable without creating a workspace when tools are absent", async () => {
|
|
const provider = new WabtArtifactProvider({}, undefined, () => {
|
|
throw new Error("Must not allocate");
|
|
});
|
|
expect(
|
|
await provider.inspect({
|
|
path: "/module.wasm",
|
|
glue_paths: [],
|
|
candidate_paths: [],
|
|
}),
|
|
).toMatchObject({
|
|
ok: false,
|
|
error: {
|
|
_tag: "AnalysisCapabilityUnavailableError",
|
|
userMessage: expect.stringContaining("REA_WABT_BIN_DIRECTORY"),
|
|
},
|
|
});
|
|
});
|
|
|
|
it.each([
|
|
{ tool: "wasm-validate", stdout: "1.0.41\n", stderr: "" },
|
|
{ tool: "wasm-objdump", stdout: "", stderr: "" },
|
|
{ tool: "wasm2wat", stdout: "1.0.42\n", stderr: "launcher warning\n" },
|
|
{
|
|
tool: "wasm-objdump",
|
|
stdout: "",
|
|
stderr: "version launcher failed\n",
|
|
exitCode: 2,
|
|
},
|
|
])(
|
|
"retains the exact rejected $tool version probe and configuration recovery",
|
|
async ({ tool, stdout, stderr, exitCode = 0 }) => {
|
|
const root = await realpath(
|
|
await createTestTempDirectory("rea-wabt-version-recovery-"),
|
|
);
|
|
const suffix = process.platform === "win32" ? ".exe" : "";
|
|
for (const name of ["wasm-validate", "wasm-objdump", "wasm2wat"])
|
|
await writeFile(join(root, name + suffix), "owned executable seam", {
|
|
mode: 0o700,
|
|
});
|
|
const commands: string[] = [];
|
|
const provider = new WabtArtifactProvider(
|
|
{ REA_WABT_BIN_DIRECTORY: root },
|
|
(input) => {
|
|
expect(input.arguments).toEqual(["--version"]);
|
|
commands.push(input.command);
|
|
const selected = input.command === join(root, tool + suffix);
|
|
return spawnOwnedProviderProcess({
|
|
...input,
|
|
command: process.execPath,
|
|
arguments: [
|
|
"-e",
|
|
`process.stdout.write(${JSON.stringify(selected ? stdout : "1.0.42\n")}); process.stderr.write(${JSON.stringify(selected ? stderr : "")}); process.exit(${String(selected ? exitCode : 0)});`,
|
|
],
|
|
expectedCommand: null,
|
|
});
|
|
},
|
|
);
|
|
try {
|
|
const result = await provider.inspect({
|
|
path: "/must-not-read-before-configuration.wasm",
|
|
glue_paths: [],
|
|
candidate_paths: [],
|
|
});
|
|
if (result.ok) throw new Error("Expected WABT configuration rejection");
|
|
const projected = projectAnalysisError(result.error);
|
|
expect(projected).toMatchObject({
|
|
code: "capability_unavailable",
|
|
retryable: false,
|
|
details: {
|
|
diagnostics: {
|
|
configuration_key: "REA_WABT_BIN_DIRECTORY",
|
|
executable_path: join(root, tool + suffix),
|
|
tool,
|
|
expected_version: "1.0.42",
|
|
},
|
|
captured_output: { stdout, stderr, truncated: false },
|
|
},
|
|
});
|
|
expect(projected.remediation.action).toContain("--version");
|
|
expect(projected.remediation.action).toContain("restart");
|
|
expect(commands.at(-1)).toBe(join(root, tool + suffix));
|
|
} finally {
|
|
await provider.close();
|
|
}
|
|
},
|
|
);
|
|
|
|
it("retries a retained input descriptor before admitting the next WABT operation", async () => {
|
|
const root = await createTestTempDirectory("rea-wabt-read-cleanup-");
|
|
const path = join(root, "selected.wasm");
|
|
await writeFile(path, Buffer.alloc(76, 1));
|
|
for (const tool of ["wasm-validate", "wasm-objdump", "wasm2wat"])
|
|
await writeFile(join(root, tool), "source-owned executable seam", {
|
|
mode: 0o700,
|
|
});
|
|
const dump = await readFile(
|
|
new URL("../../tests/fixtures/wasm/objdump.txt", import.meta.url),
|
|
"utf8",
|
|
);
|
|
const provider = new WabtArtifactProvider(
|
|
{ REA_WABT_BIN_DIRECTORY: root },
|
|
async (input) => {
|
|
const version = input.arguments[0] === "--version";
|
|
const stdout = version
|
|
? "1.0.42\n"
|
|
: input.command.includes("wasm-objdump")
|
|
? dump
|
|
: input.command.includes("wasm2wat")
|
|
? '(module (@custom "test" "payload"))\n'
|
|
: "";
|
|
const launched = await spawnOwnedProviderProcess({
|
|
...input,
|
|
command: process.execPath,
|
|
arguments: [
|
|
"-e",
|
|
`process.stdout.write(${JSON.stringify(stdout)}); process.exit(0);`,
|
|
],
|
|
expectedCommand: null,
|
|
});
|
|
return launched;
|
|
},
|
|
);
|
|
fileCloseFailure.selectedPath = path;
|
|
fileCloseFailure.attempts = 0;
|
|
fileCloseFailure.failFirst = true;
|
|
try {
|
|
const first = await provider.inspect({
|
|
path,
|
|
candidate_paths: [],
|
|
glue_paths: [],
|
|
});
|
|
expect(first).toMatchObject({
|
|
ok: false,
|
|
error: {
|
|
cleanupIncomplete: true,
|
|
cleanupResources: [path],
|
|
cause: { message: "injected WABT input close failure" },
|
|
},
|
|
});
|
|
expect(fileCloseFailure.attempts).toBe(1);
|
|
|
|
const retried = await provider.inspect({
|
|
path,
|
|
candidate_paths: [],
|
|
glue_paths: [],
|
|
});
|
|
expect(retried.ok).toBe(true);
|
|
expect(fileCloseFailure.attempts).toBeGreaterThan(1);
|
|
expect(first).toMatchObject({
|
|
error: { cleanupIncomplete: false, cleanupResources: [] },
|
|
});
|
|
|
|
fileCloseFailure.failFirst = true;
|
|
const closeRetry = await provider.inspect({
|
|
path,
|
|
candidate_paths: [],
|
|
glue_paths: [],
|
|
});
|
|
expect(closeRetry).toMatchObject({
|
|
ok: false,
|
|
error: { cleanupIncomplete: true, cleanupResources: [path] },
|
|
});
|
|
const beforeClose = fileCloseFailure.attempts;
|
|
await provider.close();
|
|
expect(fileCloseFailure.attempts).toBeGreaterThan(beforeClose);
|
|
expect(closeRetry).toMatchObject({
|
|
error: { cleanupIncomplete: false, cleanupResources: [] },
|
|
});
|
|
} finally {
|
|
fileCloseFailure.selectedPath = "";
|
|
fileCloseFailure.failFirst = false;
|
|
await provider.close();
|
|
}
|
|
});
|
|
|
|
it.each([false, true])(
|
|
"reports an unavailable private runtime root with retryable=%s without a misleading WABT remediation",
|
|
async (retryable) => {
|
|
const toolRoot = await createTestTempDirectory("rea-wabt-tool-root-");
|
|
for (const tool of ["wasm-validate", "wasm-objdump", "wasm2wat"])
|
|
await writeFile(
|
|
join(toolRoot, tool + (process.platform === "win32" ? ".exe" : "")),
|
|
"source-owned executable seam",
|
|
{ mode: 0o700 },
|
|
);
|
|
const provider = new WabtArtifactProvider(
|
|
{ REA_WABT_BIN_DIRECTORY: toolRoot },
|
|
undefined,
|
|
() => {
|
|
throw new PrivateRuntimeRootUnavailableError(
|
|
"REA Windows native controls are unavailable: probe failure",
|
|
{ retryable },
|
|
);
|
|
},
|
|
);
|
|
const result = await provider.inspect({
|
|
path: "/module.wasm",
|
|
glue_paths: [],
|
|
candidate_paths: [],
|
|
});
|
|
expect(result).toMatchObject({
|
|
ok: false,
|
|
error: {
|
|
_tag: "AnalysisCapabilityUnavailableError",
|
|
retryable,
|
|
userMessage: undefined,
|
|
message: expect.stringContaining(
|
|
"REA Windows native controls are unavailable",
|
|
),
|
|
},
|
|
});
|
|
if (result.ok)
|
|
throw new Error("Unavailable runtime must refuse inspection");
|
|
expect(projectAnalysisError(result.error)).toMatchObject({
|
|
code: "capability_unavailable",
|
|
retryable,
|
|
category: retryable ? "unavailable" : "unsupported_provider",
|
|
});
|
|
},
|
|
);
|
|
const assertGlueObservations = (
|
|
value: unknown,
|
|
inputs: readonly {
|
|
readonly path: string;
|
|
readonly source: string;
|
|
readonly line: number;
|
|
readonly column: number;
|
|
}[],
|
|
selectedPath: string,
|
|
): void => {
|
|
const glue = wasmArtifactSchema.parse(value).glue;
|
|
expect(glue).toHaveLength(inputs.length);
|
|
for (const [index, { path, source, line, column }] of inputs.entries()) {
|
|
expect(glue[index]).toMatchObject({
|
|
artifact: {
|
|
path,
|
|
sha256: createHash("sha256").update(source).digest("hex"),
|
|
bytes: Buffer.byteLength(source),
|
|
},
|
|
parse_status: "complete",
|
|
references: [
|
|
{
|
|
value: "./selected.wasm",
|
|
line,
|
|
column,
|
|
candidate_paths: [selectedPath],
|
|
association: "local-path-candidate",
|
|
evidence_kind: "static-literal",
|
|
},
|
|
],
|
|
});
|
|
}
|
|
};
|
|
|
|
const launchWabtOutcome = async (
|
|
input: Parameters<typeof spawnOwnedProviderProcess>[0],
|
|
mode: OutcomeMode,
|
|
fixture: { bytes: Buffer; dump: string; controller: AbortController },
|
|
) => {
|
|
const { bytes, dump, controller } = fixture;
|
|
const version = input.arguments[0] === "--version";
|
|
if (!version) {
|
|
expect(await readFile(join(input.cwd ?? "", "module.wasm"))).toEqual(bytes);
|
|
if (mode === "changed-snapshot") {
|
|
const snapshot = join(input.cwd ?? "", "module.wasm");
|
|
await chmod(snapshot, 0o600);
|
|
await writeFile(snapshot, Buffer.alloc(76, 2));
|
|
}
|
|
if (mode === "timeout")
|
|
throw new OwnedCommandFailure("timeout", "Source-owned deadline seam");
|
|
}
|
|
const stdout = version
|
|
? "1.0.42\n"
|
|
: input.command.includes("objdump")
|
|
? mode === "malformed"
|
|
? "unexpected producer output"
|
|
: dump
|
|
: input.command.includes("wasm2wat")
|
|
? mode === "malformed-wat"
|
|
? "unexpected decoder output\n"
|
|
: '(module (@custom "test" "payload"))\n'
|
|
: "";
|
|
const invalid =
|
|
!version &&
|
|
(mode === "invalid" ||
|
|
mode === "invalid-close" ||
|
|
mode === "invalid-close-failure");
|
|
const stderr = invalid
|
|
? "module.wasm: invalid bytes\n"
|
|
: !version && input.command.includes("wasm2wat") && mode === "wat-warning"
|
|
? "decoder warning\n"
|
|
: "";
|
|
const script =
|
|
!version && mode === "cancelled"
|
|
? "setInterval(() => undefined, 1000)"
|
|
: `process.stdout.write(${JSON.stringify(stdout)}); process.stderr.write(${JSON.stringify(stderr)}); process.exit(${invalid ? 1 : 0});`;
|
|
const launched = await spawnOwnedProviderProcess({
|
|
...input,
|
|
command: process.execPath,
|
|
arguments: ["-e", script],
|
|
expectedCommand: null,
|
|
});
|
|
if (!version && mode === "cancelled") {
|
|
setImmediate(() => controller.abort());
|
|
}
|
|
return launched;
|
|
};
|
|
|
|
it("enforces a selected command deadline and completes a corrected retry after cleanup", async () => {
|
|
const fixture = await createWabtArtifactFixture();
|
|
let first = true;
|
|
let runtimePath = "";
|
|
const provider = new WabtArtifactProvider(
|
|
{ REA_WABT_BIN_DIRECTORY: fixture.selectedRoot },
|
|
async (input) => {
|
|
if (first) {
|
|
first = false;
|
|
return spawnOwnedProviderProcess({
|
|
...input,
|
|
command: process.execPath,
|
|
arguments: [
|
|
"-e",
|
|
'setTimeout(() => process.stdout.write("1.0.42\\n"), 250)',
|
|
],
|
|
expectedCommand: null,
|
|
});
|
|
}
|
|
return launchWabtOutcome(input, "success", {
|
|
...fixture,
|
|
controller: new AbortController(),
|
|
});
|
|
},
|
|
async () => {
|
|
const root = await PrivateRuntimeRoot.create({
|
|
prefix: "rea-wabt-budget-",
|
|
});
|
|
runtimePath = root.path;
|
|
return root;
|
|
},
|
|
);
|
|
onTestFinished(() => provider.close());
|
|
const input = { path: fixture.path, glue_paths: [], candidate_paths: [] };
|
|
const timed = await provider.inspect({ ...input, command_timeout_ms: 20 });
|
|
if (timed.ok) throw new Error("Selected 20 ms deadline was not enforced");
|
|
expect(projectAnalysisError(timed.error)).toMatchObject({
|
|
code: "provider_timeout",
|
|
details: { timeout_ms: 20 },
|
|
remediation: { action: expect.stringContaining("command_timeout_ms") },
|
|
});
|
|
expect(timed.error.cleanupIncomplete).toBe(false);
|
|
await expect(access(runtimePath)).rejects.toMatchObject({ code: "ENOENT" });
|
|
const corrected = await provider.inspect({
|
|
...input,
|
|
command_timeout_ms: 2_000,
|
|
});
|
|
if (!corrected.ok) throw corrected.error;
|
|
const report = wasmArtifactSchema.parse(corrected.value.result);
|
|
expect(report.tool_profile.timeout_ms).toBe(2_000);
|
|
expect(report.limitations.join(" ")).not.toContain("30-second");
|
|
await expect(readFile(fixture.path)).resolves.toEqual(fixture.bytes);
|
|
});
|
|
|
|
const glueSourcesForOutcome = (mode: OutcomeMode) => {
|
|
const literal = 'fetch("./selected.wasm");';
|
|
return mode === "success"
|
|
? [
|
|
{ source: literal, line: 1, column: 6 },
|
|
{ source: `\uFEFF${literal}`, line: 1, column: 7 },
|
|
{ source: `\uFEFF\n${literal}`, line: 2, column: 6 },
|
|
{ source: `#!/usr/bin/env node\n${literal}`, line: 2, column: 6 },
|
|
{
|
|
source: `\uFEFF#!/usr/bin/env node\n${literal}`,
|
|
line: 2,
|
|
column: 6,
|
|
},
|
|
{
|
|
source: String.raw`"./selected\u002ewasm";`,
|
|
line: 1,
|
|
column: 0,
|
|
},
|
|
]
|
|
: [];
|
|
};
|
|
|
|
const runOwnedArtifactOutcome = async (
|
|
mode: OutcomeMode,
|
|
cancellationReason: unknown = "authored finalization cancellation",
|
|
): Promise<void> => {
|
|
const { selectedRoot, path, bytes, dump } = await createWabtArtifactFixture();
|
|
let runtimePath = "";
|
|
let runtimeCreations = 0;
|
|
let acquiredPid: number | undefined;
|
|
let cleanupAttempts = 0;
|
|
const controller = new AbortController();
|
|
const provider = new WabtArtifactProvider(
|
|
{ REA_WABT_BIN_DIRECTORY: selectedRoot },
|
|
async (input) => {
|
|
const launched = await launchWabtOutcome(input, mode, {
|
|
bytes,
|
|
dump,
|
|
controller,
|
|
});
|
|
if (mode === "cancelled" && input.arguments[0] !== "--version")
|
|
acquiredPid = launched.process.pid;
|
|
return launched;
|
|
},
|
|
async () => {
|
|
runtimeCreations += 1;
|
|
const root = await PrivateRuntimeRoot.create({
|
|
prefix: "rea-wabt-owned-test-",
|
|
});
|
|
runtimePath = root.path;
|
|
if (
|
|
mode === "snapshot-close-failure" ||
|
|
mode === "snapshot-read-failure-late-abort"
|
|
) {
|
|
fileCloseFailure.selectedPath = join(root.path, "module.wasm");
|
|
fileCloseFailure.failFirst = true;
|
|
fileCloseFailure.attempts = 0;
|
|
if (mode === "snapshot-read-failure-late-abort")
|
|
fileCloseFailure.failRead = () => {
|
|
controller.abort("late abort after physical read failure");
|
|
return Object.assign(new Error("Authored snapshot read failure"), {
|
|
code: "EACCES",
|
|
});
|
|
};
|
|
}
|
|
return {
|
|
path: root.path,
|
|
close: async () => {
|
|
if (mode === "cancelled-close-failure") {
|
|
controller.abort(cancellationReason);
|
|
if (++cleanupAttempts === 1)
|
|
throw new Error("Source-owned cleanup uncertainty");
|
|
}
|
|
if (mode === "invalid-close-failure" && ++cleanupAttempts === 1)
|
|
throw new Error("Source-owned cleanup uncertainty");
|
|
if (mode === "cleanup" && ++cleanupAttempts <= 2)
|
|
throw new Error("Source-owned cleanup uncertainty");
|
|
await root.close();
|
|
if (mode === "cancelled-close" || mode === "invalid-close")
|
|
controller.abort(cancellationReason);
|
|
},
|
|
};
|
|
},
|
|
);
|
|
const glueSources = glueSourcesForOutcome(mode);
|
|
const glueInputs = glueSources.map(({ source, line, column }, index) => ({
|
|
path: join(selectedRoot, `glue-${index}.js`),
|
|
source,
|
|
line,
|
|
column,
|
|
}));
|
|
await Promise.all(
|
|
glueInputs.map(({ path, source }) => writeFile(path, source)),
|
|
);
|
|
const result = await provider.inspect(
|
|
{
|
|
path,
|
|
glue_paths: glueInputs.map(({ path }) => path),
|
|
candidate_paths: [],
|
|
...(mode.startsWith("invalid") ? { command_timeout_ms: 42_000 } : {}),
|
|
},
|
|
{ signal: controller.signal },
|
|
);
|
|
if (mode === "success" && result.ok)
|
|
assertGlueObservations(result.value.result, glueInputs, path);
|
|
assertModeOutcome(mode, result, path);
|
|
if (mode === "cleanup") {
|
|
await assertCleanupRecovery({
|
|
provider,
|
|
result,
|
|
path,
|
|
runtimePath,
|
|
cleanupAttempts: () => cleanupAttempts,
|
|
runtimeCreations: () => runtimeCreations,
|
|
});
|
|
}
|
|
if (mode === "cancelled-close" || mode === "cancelled-close-failure")
|
|
await assertCancellationDuringRootRelease({
|
|
mode,
|
|
result,
|
|
provider,
|
|
runtimePath,
|
|
cleanupAttempts: () => cleanupAttempts,
|
|
cancellationReason: controller.signal.reason,
|
|
});
|
|
if (mode === "invalid-close-failure")
|
|
await assertValidationCleanupRecovery(
|
|
provider,
|
|
result,
|
|
() => cleanupAttempts,
|
|
);
|
|
if (
|
|
mode === "snapshot-close-failure" ||
|
|
mode === "snapshot-read-failure-late-abort"
|
|
) {
|
|
try {
|
|
expect(result).toMatchObject({
|
|
error: {
|
|
cleanupIncomplete: true,
|
|
cleanupResources: [join(runtimePath, "module.wasm")],
|
|
},
|
|
});
|
|
} finally {
|
|
fileCloseFailure.failFirst = false;
|
|
fileCloseFailure.failRead = undefined;
|
|
await provider.close();
|
|
fileCloseFailure.selectedPath = "";
|
|
}
|
|
expect(result).toMatchObject({
|
|
error: { cleanupIncomplete: false, cleanupResources: [] },
|
|
});
|
|
if (!result.ok && mode === "snapshot-read-failure-late-abort")
|
|
expect(result.error).toMatchObject({
|
|
_tag: "AnalysisOutputError",
|
|
reason: expect.stringContaining("Authored snapshot read failure"),
|
|
});
|
|
}
|
|
await expect(access(runtimePath)).rejects.toMatchObject({ code: "ENOENT" });
|
|
expect(await readFile(path)).toEqual(bytes);
|
|
if (acquiredPid !== undefined) {
|
|
const pid = acquiredPid;
|
|
expect(() => process.kill(pid, 0)).toThrow();
|
|
}
|
|
};
|
|
|
|
it.each(outcomeModes)(
|
|
"owns private artifact bytes and retains %s outcome",
|
|
(mode) => runOwnedArtifactOutcome(mode),
|
|
);
|
|
it.each([null, { reason: "caller-owned object" }, undefined])(
|
|
"preserves finalization abort reason %j through cleanup recovery",
|
|
(reason) => runOwnedArtifactOutcome("cancelled-close-failure", reason),
|
|
);
|
|
const outputLimitFailure = ({
|
|
stdout = "(module",
|
|
stderr = "",
|
|
cleanupFailure = null,
|
|
}: {
|
|
readonly stdout?: string;
|
|
readonly stderr?: string;
|
|
readonly cleanupFailure?: string | null;
|
|
} = {}) =>
|
|
new OwnedCommandFailure(
|
|
"output-limit",
|
|
"Command diagnostic output exceeded its complete-output budget.",
|
|
{
|
|
diagnosticTruncated: true,
|
|
// Observed sizes differ from retained sizes, as after a real overflow.
|
|
stdout: {
|
|
text: stdout,
|
|
bytes: Buffer.byteLength(stdout),
|
|
observedBytes: Buffer.byteLength(stdout) + 1000,
|
|
},
|
|
stderr: {
|
|
text: stderr,
|
|
bytes: Buffer.byteLength(stderr),
|
|
observedBytes: Buffer.byteLength(stderr) + 1000,
|
|
},
|
|
exitCode: null,
|
|
signal: "SIGKILL",
|
|
},
|
|
cleanupFailure,
|
|
undefined,
|
|
cleanupFailure === null ? [] : ["/owned/runtime"],
|
|
);
|
|
it("reports WABT output beyond the budget as a resource constraint with a bounded excerpt", async () => {
|
|
const selectedRoot = await createTestTempDirectory("rea-wabt-budget-");
|
|
const path = join(selectedRoot, "selected.wasm");
|
|
await writeFile(path, Buffer.alloc(76, 1));
|
|
for (const tool of ["wasm-validate", "wasm-objdump", "wasm2wat"])
|
|
await writeFile(
|
|
join(selectedRoot, tool + (process.platform === "win32" ? ".exe" : "")),
|
|
"source-owned executable seam",
|
|
{ mode: 0o700 },
|
|
);
|
|
const dump = await readFile(
|
|
new URL("../../tests/fixtures/wasm/objdump.txt", import.meta.url),
|
|
"utf8",
|
|
);
|
|
const provider = new WabtArtifactProvider(
|
|
{ REA_WABT_BIN_DIRECTORY: selectedRoot },
|
|
(input) => {
|
|
const version = input.arguments[0] === "--version";
|
|
if (!version && input.command.includes("wasm2wat"))
|
|
throw outputLimitFailure({ stdout: "(module" + "a".repeat(5000) });
|
|
const script = version
|
|
? 'process.stdout.write("1.0.42\\n")'
|
|
: input.command.includes("objdump")
|
|
? `process.stdout.write(${JSON.stringify(dump)})`
|
|
: "";
|
|
return spawnOwnedProviderProcess({
|
|
...input,
|
|
command: process.execPath,
|
|
arguments: ["-e", script],
|
|
expectedCommand: null,
|
|
});
|
|
},
|
|
);
|
|
const result = await provider.inspect({
|
|
path,
|
|
glue_paths: [],
|
|
candidate_paths: [],
|
|
});
|
|
if (result.ok) throw new Error("Expected the output budget to fail");
|
|
expect(result.error).toMatchObject({
|
|
_tag: "AnalysisResourceConstraintError",
|
|
resource: "memory",
|
|
reportedLimits: { max_output_bytes: WABT_LIMITS.outputBytes },
|
|
});
|
|
const projection = projectAnalysisError(result.error);
|
|
expect(projection).toMatchObject({
|
|
code: "resource_constraint",
|
|
message: expect.stringContaining(
|
|
`${String(WABT_LIMITS.outputBytes)}-byte output budget`,
|
|
),
|
|
remediation: {
|
|
action: expect.stringContaining("retrying with the same input"),
|
|
},
|
|
});
|
|
expect(projection.details?.captured_output).toEqual({
|
|
stdout: "(module" + "a".repeat(4089),
|
|
stderr: "",
|
|
truncated: true,
|
|
stdout_bytes: 5007,
|
|
stderr_bytes: 0,
|
|
exit_code: null,
|
|
signal: "SIGKILL",
|
|
});
|
|
});
|
|
it("does not attribute wasm-objdump output to a wasm2wat launch failure", async () => {
|
|
const selectedRoot = await createTestTempDirectory("rea-wabt-launch-");
|
|
const path = join(selectedRoot, "selected.wasm");
|
|
await writeFile(path, Buffer.alloc(76, 1));
|
|
for (const tool of ["wasm-validate", "wasm-objdump", "wasm2wat"])
|
|
await writeFile(
|
|
join(selectedRoot, tool + (process.platform === "win32" ? ".exe" : "")),
|
|
"source-owned executable seam",
|
|
{ mode: 0o700 },
|
|
);
|
|
const dump = await readFile(
|
|
new URL("../../tests/fixtures/wasm/objdump.txt", import.meta.url),
|
|
"utf8",
|
|
);
|
|
const provider = new WabtArtifactProvider(
|
|
{ REA_WABT_BIN_DIRECTORY: selectedRoot },
|
|
(input) => {
|
|
const version = input.arguments[0] === "--version";
|
|
if (!version && input.command.includes("wasm2wat"))
|
|
throw Object.assign(new Error("spawn E2BIG"), { code: "E2BIG" });
|
|
const script = version
|
|
? 'process.stdout.write("1.0.42\\n")'
|
|
: input.command.includes("objdump")
|
|
? `process.stdout.write(${JSON.stringify(dump)})`
|
|
: "";
|
|
return spawnOwnedProviderProcess({
|
|
...input,
|
|
command: process.execPath,
|
|
arguments: ["-e", script],
|
|
expectedCommand: null,
|
|
});
|
|
},
|
|
);
|
|
const result = await provider.inspect({
|
|
path,
|
|
glue_paths: [],
|
|
candidate_paths: [],
|
|
});
|
|
if (result.ok) throw new Error("Expected the wasm2wat launch to fail");
|
|
expect(result.error.message).toContain("spawn E2BIG");
|
|
expect(result.error.capturedOutput).toBeUndefined();
|
|
});
|
|
|
|
it("bounds an oversized stderr to an excerpt", () => {
|
|
expect(
|
|
projectAnalysisError(
|
|
wabtFailure(
|
|
outputLimitFailure({ stderr: "w".repeat(5000) }),
|
|
"producer",
|
|
"/module.wasm",
|
|
),
|
|
).details?.captured_output,
|
|
).toEqual({
|
|
stdout: "(module",
|
|
stderr: "w".repeat(4096),
|
|
truncated: true,
|
|
stdout_bytes: 7,
|
|
stderr_bytes: 5000,
|
|
exit_code: null,
|
|
signal: "SIGKILL",
|
|
});
|
|
});
|
|
it("cuts the excerpt on a code point boundary", () => {
|
|
const failure = wabtFailure(
|
|
outputLimitFailure({ stdout: "a".repeat(4095) + "\u{1F600}b" }),
|
|
"producer",
|
|
"/module.wasm",
|
|
);
|
|
expect(failure.capturedOutput?.stdout).toBe("a".repeat(4095));
|
|
});
|
|
it("bounds the excerpt when cleanup also fails after the output budget", () => {
|
|
expect(
|
|
projectAnalysisError(
|
|
wabtFailure(
|
|
outputLimitFailure({
|
|
stderr: "w".repeat(5000),
|
|
cleanupFailure: "Source-owned cleanup uncertainty",
|
|
}),
|
|
"producer",
|
|
"/module.wasm",
|
|
),
|
|
),
|
|
).toMatchObject({
|
|
code: "cleanup_incomplete",
|
|
details: {
|
|
diagnostics: {
|
|
previous_error: {
|
|
failure_kind: "output-limit",
|
|
message: `WABT output exceeded the ${String(WABT_LIMITS.outputBytes)}-byte output budget`,
|
|
max_output_bytes: WABT_LIMITS.outputBytes,
|
|
},
|
|
captured_output: { stdout: "(module", stderr: "w".repeat(4096) },
|
|
},
|
|
},
|
|
});
|
|
});
|
|
it("does not blame the input when a version probe exceeds the budget", () => {
|
|
expect(
|
|
wabtFailure(outputLimitFailure(), "configuration", "/module.wasm"),
|
|
).toMatchObject({ _tag: "AnalysisOutputError" });
|
|
});
|