Keep parser-owned completed metadata and tensor declarations as typed partial Evidence on cancellation or physical read failure. Preserve unknown final identity and digests, exact caller inputs, primary failures, and retained cleanup owners; support inline and retained MCP error delivery. Closes #5625
664 lines
24 KiB
TypeScript
664 lines
24 KiB
TypeScript
import { writeFile, type FileHandle } from "node:fs/promises";
|
|
import { createHash } from "node:crypto";
|
|
import { join } from "node:path";
|
|
import { afterEach, beforeEach, expect, it, vi } from "vitest";
|
|
|
|
const fault = vi.hoisted(() => ({
|
|
path: "",
|
|
blockNew: true,
|
|
blocked: new Set<FileHandle>(),
|
|
handles: [] as FileHandle[],
|
|
onClose: undefined as (() => void) | undefined,
|
|
readFailure: undefined as { cause: unknown } | undefined,
|
|
laterReadFailure: undefined as
|
|
| { position: number; cause: unknown }
|
|
| undefined,
|
|
}));
|
|
|
|
vi.mock("node:fs/promises", async (importOriginal) => {
|
|
const original = await importOriginal<typeof import("node:fs/promises")>();
|
|
return {
|
|
...original,
|
|
open: async (...arguments_: Parameters<typeof original.open>) => {
|
|
const handle = await original.open(...arguments_);
|
|
if (String(arguments_[0]) !== fault.path) return handle;
|
|
const close = handle.close.bind(handle);
|
|
fault.handles.push(handle);
|
|
if (fault.blockNew) fault.blocked.add(handle);
|
|
if (fault.readFailure !== undefined) {
|
|
const failure = fault.readFailure;
|
|
handle.stat = async () => {
|
|
throw failure.cause;
|
|
};
|
|
}
|
|
if (fault.laterReadFailure !== undefined) {
|
|
const failure = fault.laterReadFailure;
|
|
const read = handle.read.bind(handle) as (
|
|
buffer: Buffer,
|
|
offset: number,
|
|
length: number,
|
|
position: number,
|
|
) => Promise<{ bytesRead: number }>;
|
|
const instrumented = handle as unknown as { read: typeof read };
|
|
let observedStart = false;
|
|
instrumented.read = async (buffer, offset, length, position) => {
|
|
if (observedStart && position > failure.position) throw failure.cause;
|
|
const result = await read(buffer, offset, length, position);
|
|
if (position === failure.position && result.bytesRead > 0)
|
|
observedStart = true;
|
|
return result;
|
|
};
|
|
}
|
|
handle.close = async () => {
|
|
fault.onClose?.();
|
|
if (fault.blocked.has(handle))
|
|
throw new Error("controlled Rust descriptor close failure");
|
|
await close();
|
|
};
|
|
return handle;
|
|
},
|
|
};
|
|
});
|
|
|
|
import { RustBinaryProvider } from "../../src/rust/RustBinaryProvider.js";
|
|
import { RustBinaryService } from "../../src/application/rust/RustBinaryService.js";
|
|
import { AnalysisError } from "../../src/domain/analysisErrorBase.js";
|
|
import { parseEvidence } from "../../src/domain/evidence.js";
|
|
import { projectAnalysisError } from "../../src/domain/analysisErrorProjection.js";
|
|
import {
|
|
AnalysisCancelledError,
|
|
AnalysisArtifactChangedError,
|
|
AnalysisAccessDeniedError,
|
|
AnalysisResourceConstraintError,
|
|
} from "../../src/domain/analysisErrorCore.js";
|
|
import {
|
|
ArtifactFileCleanupFailure,
|
|
withArtifactRegularFile,
|
|
} from "../../src/artifacts/ArtifactFileOperation.js";
|
|
import { ArtifactResourceScope } from "../../src/artifacts/ArtifactResourceScope.js";
|
|
import { ArtifactReaderFailure } from "../../src/artifacts/ArtifactReader.js";
|
|
import { GoBinaryProvider } from "../../src/go/GoBinaryProvider.js";
|
|
import { PeResourcesProvider } from "../../src/native/pe/PeResourcesProvider.js";
|
|
import { Il2cppMetadataProvider } from "../../src/il2cpp/Il2cppMetadataProvider.js";
|
|
import { ModelArtifactProvider } from "../../src/model/ModelArtifactProvider.js";
|
|
import { ggufFixture } from "../fixtures/model/model.js";
|
|
import { inspectPeResourcesInputSchema } from "../../src/domain/native/peResources.js";
|
|
import { peResourceFixture } from "../../src/native/pe/PeResources.fixture.js";
|
|
import { createServer } from "../../src/server/createServer.js";
|
|
import { createTestBinarySession } from "../fixtures/binarySession.js";
|
|
import { createRustImage } from "../fixtures/rust/image.js";
|
|
import { createIl2cppMetadataFixture } from "../fixtures/il2cpp/metadata.js";
|
|
import { createTestTempDirectory } from "../fixtures/temporaryDirectory.js";
|
|
import { parseMcpToolError } from "../fixtures/mcpToolError.js";
|
|
import { mcpTest } from "../support/mcp/mcpFixture.js";
|
|
|
|
beforeEach(() => {
|
|
fault.path = "";
|
|
fault.blockNew = true;
|
|
fault.blocked.clear();
|
|
fault.handles = [];
|
|
fault.onClose = undefined;
|
|
fault.readFailure = undefined;
|
|
fault.laterReadFailure = undefined;
|
|
});
|
|
|
|
const staticProviders = [
|
|
["Rust", () => new RustBinaryProvider()],
|
|
["Go", () => new GoBinaryProvider()],
|
|
["PE", () => new PeResourcesProvider()],
|
|
["IL2CPP metadata", () => new Il2cppMetadataProvider()],
|
|
] as const;
|
|
|
|
const expectIl2cppSource = (
|
|
error: AnalysisError,
|
|
path: string,
|
|
bytes: Buffer,
|
|
) => {
|
|
const source = parseEvidence(error.partialObservation);
|
|
const sha256 = createHash("sha256").update(bytes).digest("hex");
|
|
expect(source.operation).toBe("observe_il2cpp_metadata_source");
|
|
expect(source.subject).toMatchObject({
|
|
local_path: path,
|
|
digest: { sha256 },
|
|
});
|
|
expect(source.normalized_result).toEqual({
|
|
artifact: { path, sha256, bytes: bytes.length },
|
|
inventory_status: "not-returned-as-complete",
|
|
});
|
|
};
|
|
|
|
it.each(staticProviders)(
|
|
"preserves the exact late-close cancellation cause through %s inspection",
|
|
async (_name, makeProvider) => {
|
|
const path = await selectedImage();
|
|
const provider = makeProvider();
|
|
if (provider instanceof PeResourcesProvider)
|
|
await writeFile(path, peResourceFixture().bytes);
|
|
if (provider instanceof Il2cppMetadataProvider)
|
|
await writeFile(path, createIl2cppMetadataFixture().bytes);
|
|
const controller = new AbortController();
|
|
const reason = new Error("cancel during confirmed descriptor close");
|
|
fault.blockNew = false;
|
|
fault.onClose = () => controller.abort(reason);
|
|
try {
|
|
const result =
|
|
provider instanceof PeResourcesProvider
|
|
? await provider.inspect(
|
|
inspectPeResourcesInputSchema.parse({ path }),
|
|
{ signal: controller.signal },
|
|
)
|
|
: await provider.inspect({ path }, { signal: controller.signal });
|
|
if (result.ok) throw new Error("Expected late cancellation");
|
|
expect(result.error).toBeInstanceOf(AnalysisCancelledError);
|
|
expect(result.error.cause).toBe(reason);
|
|
if (provider instanceof Il2cppMetadataProvider)
|
|
expectIl2cppSource(
|
|
result.error,
|
|
path,
|
|
createIl2cppMetadataFixture().bytes,
|
|
);
|
|
expect(fault.handles.every((handle) => handle.fd === -1)).toBe(true);
|
|
} finally {
|
|
fault.blocked.clear();
|
|
await provider.close();
|
|
}
|
|
},
|
|
);
|
|
|
|
it.each([false, true])(
|
|
"retains completed IL2CPP source facts through failed close (cancel: %s)",
|
|
async (cancel) => {
|
|
const path = await selectedImage();
|
|
const { bytes } = createIl2cppMetadataFixture();
|
|
await writeFile(path, bytes);
|
|
const provider = new Il2cppMetadataProvider();
|
|
const controller = new AbortController();
|
|
const reason = new Error("cancel during failed close");
|
|
if (cancel) fault.onClose = () => controller.abort(reason);
|
|
try {
|
|
const result = await provider.inspect(
|
|
{ path },
|
|
{ signal: controller.signal },
|
|
);
|
|
if (result.ok) throw new Error("Expected failed close");
|
|
if (cancel) {
|
|
expect(result.error.cleanupPrimary).toBeInstanceOf(
|
|
AnalysisCancelledError,
|
|
);
|
|
expect(result.error.cleanupPrimary?.cause).toBe(reason);
|
|
}
|
|
expect(result.error.cleanupIncomplete).toBe(true);
|
|
expectIl2cppSource(result.error, path, bytes);
|
|
expect(fault.handles).toHaveLength(1);
|
|
expect(fault.handles[0]?.fd).toBeGreaterThanOrEqual(0);
|
|
fault.blocked.clear();
|
|
await provider.close();
|
|
expect(result.error.cleanupIncomplete).toBe(false);
|
|
expect(fault.handles[0]?.fd).toBe(-1);
|
|
expect(
|
|
projectAnalysisError(result.error).details?.partial_observation,
|
|
).toEqual(result.error.partialObservation);
|
|
} finally {
|
|
fault.blocked.clear();
|
|
await provider.close();
|
|
}
|
|
},
|
|
);
|
|
|
|
it.each(
|
|
staticProviders.flatMap(([name, makeProvider]) => [
|
|
[
|
|
name,
|
|
makeProvider,
|
|
new ArtifactReaderFailure("integrity", "source replacement"),
|
|
AnalysisArtifactChangedError,
|
|
] as const,
|
|
[
|
|
name,
|
|
makeProvider,
|
|
Object.assign(new Error("read denied"), { code: "EACCES" }),
|
|
AnalysisAccessDeniedError,
|
|
] as const,
|
|
[
|
|
name,
|
|
makeProvider,
|
|
new ArtifactReaderFailure("limit", "read limit"),
|
|
AnalysisResourceConstraintError,
|
|
] as const,
|
|
]),
|
|
)(
|
|
"preserves %s read failure after successful close observes cancellation",
|
|
async (_name, makeProvider, primary, expected) => {
|
|
const path = await selectedImage();
|
|
const provider = makeProvider();
|
|
fault.blockNew = false;
|
|
fault.readFailure = { cause: primary };
|
|
const controller = new AbortController();
|
|
fault.onClose = () => controller.abort(new Error("later cancellation"));
|
|
try {
|
|
const result =
|
|
provider instanceof PeResourcesProvider
|
|
? await provider.inspect(
|
|
inspectPeResourcesInputSchema.parse({ path }),
|
|
{ signal: controller.signal },
|
|
)
|
|
: await provider.inspect({ path }, { signal: controller.signal });
|
|
if (result.ok) throw new Error("Expected primary read failure");
|
|
expect(result.error).toBeInstanceOf(expected);
|
|
expect(result.error.cause).toBe(primary);
|
|
if (provider instanceof Il2cppMetadataProvider)
|
|
expect(result.error.partialObservation).toBeUndefined();
|
|
expect(fault.handles.every((handle) => handle.fd === -1)).toBe(true);
|
|
} finally {
|
|
await provider.close();
|
|
}
|
|
},
|
|
);
|
|
|
|
it.each(staticProviders)(
|
|
"keeps the primary %s read failure when cancellation arrives during failed close",
|
|
async (_name, makeProvider) => {
|
|
const path = await selectedImage();
|
|
const provider = makeProvider();
|
|
const primary = new ArtifactReaderFailure(
|
|
"integrity",
|
|
"controlled source replacement",
|
|
);
|
|
fault.readFailure = { cause: primary };
|
|
const controller = new AbortController();
|
|
fault.onClose = () => controller.abort(new Error("later cancellation"));
|
|
try {
|
|
const result =
|
|
provider instanceof PeResourcesProvider
|
|
? await provider.inspect(
|
|
inspectPeResourcesInputSchema.parse({ path }),
|
|
{ signal: controller.signal },
|
|
)
|
|
: await provider.inspect({ path }, { signal: controller.signal });
|
|
if (result.ok)
|
|
throw new Error("Expected primary failure and retained descriptor");
|
|
expect(result.error.cleanupIncomplete).toBe(true);
|
|
const aggregate = result.error.cause;
|
|
if (!(aggregate instanceof AggregateError))
|
|
throw new Error("Missing primary/cleanup aggregate");
|
|
const [original] = aggregate.errors;
|
|
expect(original).toBeInstanceOf(AnalysisArtifactChangedError);
|
|
expect((original as AnalysisArtifactChangedError).cause).toBe(primary);
|
|
if (provider instanceof Il2cppMetadataProvider)
|
|
expect(result.error.partialObservation).toBeUndefined();
|
|
fault.blocked.clear();
|
|
await provider.close();
|
|
expect(result.error.cleanupIncomplete).toBe(false);
|
|
} finally {
|
|
fault.blocked.clear();
|
|
await provider.close();
|
|
}
|
|
},
|
|
);
|
|
afterEach(async () => {
|
|
fault.blockNew = false;
|
|
fault.blocked.clear();
|
|
fault.onClose = undefined;
|
|
for (const handle of fault.handles) if (handle.fd >= 0) await handle.close();
|
|
});
|
|
|
|
const selectedImage = async () => {
|
|
const root = await createTestTempDirectory("rea-rust-artifact-owner-");
|
|
const path = join(root, "image.elf");
|
|
await writeFile(path, createRustImage({ format: "elf" }));
|
|
fault.path = path;
|
|
return path;
|
|
};
|
|
|
|
it.each([
|
|
"confirmed-close-cancel",
|
|
"failed-close",
|
|
"failed-close-cancel",
|
|
] as const)(
|
|
"retains complete scoped model observations through %s and retries the exact file owner",
|
|
async (mode) => {
|
|
const path = await selectedImage();
|
|
await writeFile(path, ggufFixture().bytes);
|
|
const provider = new ModelArtifactProvider();
|
|
const controller = new AbortController();
|
|
const reason = new Error("model close cancellation");
|
|
fault.blockNew = mode !== "confirmed-close-cancel";
|
|
if (mode !== "failed-close") fault.onClose = () => controller.abort(reason);
|
|
try {
|
|
const result = await provider.inspect(
|
|
{ path, hash: "header" },
|
|
{ signal: controller.signal },
|
|
);
|
|
if (result.ok) throw new Error("Expected finalization failure");
|
|
const evidence = parseEvidence(result.error.partialObservation);
|
|
expect(evidence.operation).toBe("inspect_model_artifact");
|
|
expect(evidence.subject).toBeNull();
|
|
expect(evidence.normalized_result).toMatchObject({
|
|
artifact: { path, sha256: null, read_scope: "header" },
|
|
tensors: [{ name: "weight" }, { name: "quant" }],
|
|
});
|
|
if (mode === "confirmed-close-cancel") {
|
|
expect(result.error).toBeInstanceOf(AnalysisCancelledError);
|
|
expect(result.error.cause).toBe(reason);
|
|
expect(fault.handles[0]?.fd).toBe(-1);
|
|
} else {
|
|
expect(result.error.cleanupIncomplete).toBe(true);
|
|
if (mode === "failed-close-cancel")
|
|
expect(result.error.cleanupPrimary?.cause).toBe(reason);
|
|
expect(fault.handles[0]?.fd).toBeGreaterThanOrEqual(0);
|
|
fault.blocked.clear();
|
|
await provider.close();
|
|
expect(result.error.cleanupIncomplete).toBe(false);
|
|
expect(fault.handles[0]?.fd).toBe(-1);
|
|
}
|
|
} finally {
|
|
fault.onClose = undefined;
|
|
fault.blocked.clear();
|
|
await provider.close();
|
|
}
|
|
},
|
|
);
|
|
|
|
it("preserves a model permission failure when cancellation arrives during close", async () => {
|
|
const path = await selectedImage();
|
|
const provider = new ModelArtifactProvider();
|
|
const denied = Object.assign(new Error("model read denied"), {
|
|
code: "EACCES",
|
|
});
|
|
fault.readFailure = { cause: denied };
|
|
fault.blockNew = false;
|
|
const controller = new AbortController();
|
|
fault.onClose = () => controller.abort(new Error("later stop"));
|
|
try {
|
|
const result = await provider.inspect(
|
|
{ path, hash: "header" },
|
|
{ signal: controller.signal },
|
|
);
|
|
if (result.ok) throw new Error("Read failure disappeared");
|
|
expect(result.error).toBeInstanceOf(AnalysisAccessDeniedError);
|
|
expect(result.error.cause).toBe(denied);
|
|
expect(result.error.partialObservation).toBeUndefined();
|
|
} finally {
|
|
fault.onClose = undefined;
|
|
await provider.close();
|
|
}
|
|
});
|
|
|
|
it("retains earlier GGUF rows and physical failure while retrying failed descriptor cleanup", async () => {
|
|
const path = await selectedImage();
|
|
const seed = ggufFixture();
|
|
const count = 200_000;
|
|
const fixture = ggufFixture({
|
|
tensors: [],
|
|
metadata: [
|
|
{ key: "general.name", type: 8, value: seed.text("First row") },
|
|
{
|
|
key: "custom.bools",
|
|
type: 9,
|
|
value: Buffer.concat([
|
|
seed.u32(7),
|
|
seed.u64(BigInt(count)),
|
|
Buffer.alloc(count),
|
|
]),
|
|
},
|
|
],
|
|
});
|
|
await writeFile(path, fixture.bytes);
|
|
const arrayStart =
|
|
24 +
|
|
8 +
|
|
Buffer.byteLength("general.name") +
|
|
4 +
|
|
8 +
|
|
Buffer.byteLength("First row") +
|
|
8 +
|
|
Buffer.byteLength("custom.bools") +
|
|
4 +
|
|
4 +
|
|
8;
|
|
const denied = Object.assign(new Error("controlled later GGUF read denial"), {
|
|
code: "EACCES",
|
|
});
|
|
fault.laterReadFailure = { position: arrayStart, cause: denied };
|
|
const provider = new ModelArtifactProvider();
|
|
try {
|
|
const result = await provider.inspect({ path, hash: "header" });
|
|
if (result.ok) throw new Error("Expected physical read failure");
|
|
expect(result.error.cleanupPrimary).toBeInstanceOf(
|
|
AnalysisAccessDeniedError,
|
|
);
|
|
expect(result.error.cleanupPrimary?.cause).toBe(denied);
|
|
expect(result.error.cleanupIncomplete).toBe(true);
|
|
const partial = parseEvidence(result.error.partialObservation);
|
|
expect(partial.normalized_result).toMatchObject({
|
|
kind: "model-artifact-partial",
|
|
metadata: [{ index: 0, key: "general.name", value: "First row" }],
|
|
artifact: {
|
|
path,
|
|
header_sha256: null,
|
|
sha256: null,
|
|
file_identity: "unverified",
|
|
},
|
|
});
|
|
expect(fault.handles).toHaveLength(1);
|
|
expect(fault.handles[0]?.fd).toBeGreaterThanOrEqual(0);
|
|
fault.blocked.clear();
|
|
await provider.close();
|
|
expect(result.error.cleanupIncomplete).toBe(false);
|
|
expect(parseEvidence(result.error.partialObservation)).toEqual(partial);
|
|
} finally {
|
|
fault.blocked.clear();
|
|
await provider.close();
|
|
}
|
|
});
|
|
|
|
mcpTest(
|
|
"retains Rust descriptor cleanup and recovers in the same MCP connection",
|
|
async ({ mcp }) => {
|
|
const path = await selectedImage();
|
|
const service = new RustBinaryService(new RustBinaryProvider());
|
|
const failures: AnalysisError[] = [];
|
|
const inspect = service.inspect.bind(service);
|
|
vi.spyOn(service, "inspect").mockImplementation(async (...arguments_) => {
|
|
const result = await inspect(...arguments_);
|
|
if (!result.ok) failures.push(result.error);
|
|
return result;
|
|
});
|
|
const session = createTestBinarySession(() => {
|
|
throw new Error("Unexpected native provider");
|
|
});
|
|
const server = createServer(
|
|
{ kind: "session", session },
|
|
{ rustBinary: service },
|
|
);
|
|
const client = await mcp.connect(server);
|
|
try {
|
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
const response = await client.callTool({
|
|
name: "inspect_rust_binary",
|
|
arguments: { path },
|
|
});
|
|
expect(response.isError).toBe(true);
|
|
expect(parseMcpToolError(response).error).toMatchObject({
|
|
code: "cleanup_incomplete",
|
|
details: { resources: [path] },
|
|
});
|
|
}
|
|
expect(fault.handles).toHaveLength(1);
|
|
expect(fault.handles[0]?.fd).toBeGreaterThanOrEqual(0);
|
|
expect(failures.map((error) => error.cleanupIncomplete)).toEqual([
|
|
true,
|
|
true,
|
|
]);
|
|
fault.blockNew = false;
|
|
fault.blocked.clear();
|
|
const recovered = await client.callTool({
|
|
name: "inspect_rust_binary",
|
|
arguments: { path },
|
|
});
|
|
expect(recovered.isError).not.toBe(true);
|
|
expect(failures.map((error) => error.cleanupIncomplete)).toEqual([
|
|
false,
|
|
false,
|
|
]);
|
|
expect(fault.handles.every((handle) => handle.fd === -1)).toBe(true);
|
|
} finally {
|
|
fault.blockNew = false;
|
|
fault.blocked.clear();
|
|
await server.close();
|
|
await session.close();
|
|
}
|
|
},
|
|
);
|
|
|
|
it("keeps mixed descriptor cleanup and its aggregate bound to the exact unresolved owners", async () => {
|
|
const path = await selectedImage();
|
|
const service = new RustBinaryService(new RustBinaryProvider());
|
|
try {
|
|
const inspections = await Promise.all([
|
|
service.inspect({ path }),
|
|
service.inspect({ path }),
|
|
]);
|
|
expect(fault.handles).toHaveLength(2);
|
|
const errors = inspections.map((result) => {
|
|
if (result.ok) throw new Error("Expected retained cleanup");
|
|
return result.error;
|
|
});
|
|
const firstClose = await service.close().catch((cause: unknown) => cause);
|
|
if (!(firstClose instanceof AnalysisError))
|
|
throw new Error("Missing typed shutdown failure");
|
|
expect(firstClose.cleanupIncomplete).toBe(true);
|
|
const handlesByError = errors.map((error) => {
|
|
const aggregate = error.cause;
|
|
if (!(aggregate instanceof AggregateError))
|
|
throw new Error("Missing cleanup aggregate");
|
|
const cleanup = aggregate.errors[1];
|
|
if (
|
|
!(cleanup instanceof AnalysisError) ||
|
|
!(cleanup.cause instanceof ArtifactFileCleanupFailure)
|
|
)
|
|
throw new Error("Missing exact descriptor owner");
|
|
return cleanup.cause.fileCleanupFailure.owner.handle;
|
|
});
|
|
expect(new Set(handlesByError)).toEqual(new Set(fault.handles));
|
|
const [first, second] = handlesByError;
|
|
if (first === undefined || second === undefined)
|
|
throw new Error("Missing distinct descriptors");
|
|
fault.blocked.delete(first);
|
|
const mixedClose = await service.close().catch((cause: unknown) => cause);
|
|
if (!(mixedClose instanceof AnalysisError))
|
|
throw new Error("Missing mixed shutdown failure");
|
|
expect(first.fd).toBe(-1);
|
|
expect(second.fd).toBeGreaterThanOrEqual(0);
|
|
expect(errors.map((error) => error.cleanupIncomplete)).toEqual([
|
|
false,
|
|
true,
|
|
]);
|
|
expect(firstClose.cleanupIncomplete).toBe(true);
|
|
expect(mixedClose.cleanupIncomplete).toBe(true);
|
|
fault.blocked.clear();
|
|
await service.close();
|
|
expect(errors.map((error) => error.cleanupIncomplete)).toEqual([
|
|
false,
|
|
false,
|
|
]);
|
|
expect(firstClose.cleanupIncomplete).toBe(false);
|
|
expect(mixedClose.cleanupIncomplete).toBe(false);
|
|
} finally {
|
|
fault.blockNew = false;
|
|
fault.blocked.clear();
|
|
await service.close();
|
|
}
|
|
});
|
|
|
|
mcpTest(
|
|
"server shutdown releases Rust descriptors retained by a failed tool call",
|
|
async ({ mcp }) => {
|
|
const path = await selectedImage();
|
|
const session = createTestBinarySession(() => {
|
|
throw new Error("Unexpected native provider");
|
|
});
|
|
const server = createServer({ kind: "session", session });
|
|
const client = await mcp.connect(server);
|
|
try {
|
|
const response = await client.callTool({
|
|
name: "inspect_rust_binary",
|
|
arguments: { path },
|
|
});
|
|
expect(parseMcpToolError(response).error.code).toBe("cleanup_incomplete");
|
|
expect(fault.handles[0]?.fd).toBeGreaterThanOrEqual(0);
|
|
fault.blockNew = false;
|
|
fault.blocked.clear();
|
|
await server.close();
|
|
expect(fault.handles[0]?.fd).toBe(-1);
|
|
} finally {
|
|
fault.blockNew = false;
|
|
fault.blocked.clear();
|
|
await server.close();
|
|
await session.close();
|
|
}
|
|
},
|
|
);
|
|
|
|
it("preserves the caller's cancellation cause when descriptor close also fails", async () => {
|
|
const path = await selectedImage();
|
|
const provider = new RustBinaryProvider();
|
|
const controller = new AbortController();
|
|
const reason = new Error("cancel during Rust descriptor close");
|
|
fault.onClose = () => controller.abort(reason);
|
|
try {
|
|
const result = await provider.inspect(
|
|
{ path },
|
|
{ signal: controller.signal },
|
|
);
|
|
if (result.ok)
|
|
throw new Error("Expected cancellation with retained cleanup");
|
|
expect(result.error.cleanupIncomplete).toBe(true);
|
|
const aggregate = result.error.cause;
|
|
if (!(aggregate instanceof AggregateError))
|
|
throw new Error("Missing primary/cleanup aggregate");
|
|
const [primary] = aggregate.errors;
|
|
expect(primary).toBeInstanceOf(AnalysisCancelledError);
|
|
expect((primary as AnalysisCancelledError).cause).toBe(reason);
|
|
fault.blockNew = false;
|
|
fault.blocked.clear();
|
|
await provider.close();
|
|
expect(result.error.cleanupIncomplete).toBe(false);
|
|
} finally {
|
|
fault.blockNew = false;
|
|
fault.blocked.clear();
|
|
await provider.close();
|
|
fault.onClose = undefined;
|
|
}
|
|
});
|
|
|
|
it("preserves upstream resource-bound facts through an unconfirmed descriptor close", async () => {
|
|
const path = await selectedImage();
|
|
const scope = new ArtifactResourceScope();
|
|
const primary = new ArtifactReaderFailure(
|
|
"limit",
|
|
"Controlled input budget refusal",
|
|
{ boundary: "file-size" },
|
|
);
|
|
try {
|
|
const failure = await scope
|
|
.run(() =>
|
|
withArtifactRegularFile(path, scope, undefined, async () => {
|
|
throw primary;
|
|
}),
|
|
)
|
|
.catch((cause: unknown) => cause);
|
|
expect(failure).toBeInstanceOf(ArtifactFileCleanupFailure);
|
|
expect(failure).toMatchObject({ reason: "limit", boundary: "file-size" });
|
|
if (!(failure instanceof ArtifactFileCleanupFailure))
|
|
throw new Error("Expected exact descriptor cleanup failure");
|
|
expect(failure.fileCleanupFailure.outcome).toEqual({
|
|
kind: "failed",
|
|
cause: primary,
|
|
});
|
|
} finally {
|
|
fault.blocked.clear();
|
|
await scope.close();
|
|
}
|
|
});
|