fix(wasm): attribute invalid paths to their selected fields (#2796)
* fix(wasm): attribute invalid paths to their selected fields * test(wasm): assert the exact rejected selection issues --------- Co-authored-by: maxazure <405711+maxazure@users.noreply.github.com>
This commit is contained in:
parent
2895a18ca9
commit
0bc422d7be
|
|
@ -6,6 +6,7 @@ import {
|
|||
AnalysisCancelledError,
|
||||
AnalysisInputError,
|
||||
AnalysisOutputError,
|
||||
type AnalysisInputIssue,
|
||||
} from "../../domain/analysisErrorCore.js";
|
||||
import { createEvidence, type Evidence } from "../../domain/evidence.js";
|
||||
import {
|
||||
|
|
@ -41,22 +42,29 @@ export class WasmArtifactService {
|
|||
cause: input.error,
|
||||
}),
|
||||
);
|
||||
if (
|
||||
![
|
||||
input.data.path,
|
||||
...input.data.glue_paths,
|
||||
...input.data.candidate_paths,
|
||||
].every(isAbsolute)
|
||||
)
|
||||
return err(
|
||||
new AnalysisInputError(OPERATION, undefined, [
|
||||
{
|
||||
path: ["path"],
|
||||
reason: "invalid_format",
|
||||
message: "Expected an absolute filesystem path on this host.",
|
||||
},
|
||||
]),
|
||||
);
|
||||
const pathIssues = [
|
||||
{ value: input.data.path, path: ["path"] },
|
||||
...input.data.glue_paths.map((value, index) => ({
|
||||
value,
|
||||
path: ["glue_paths", index],
|
||||
})),
|
||||
...input.data.candidate_paths.map((value, index) => ({
|
||||
value,
|
||||
path: ["candidate_paths", index],
|
||||
})),
|
||||
].flatMap<AnalysisInputIssue>(({ value, path }) =>
|
||||
isAbsolute(value)
|
||||
? []
|
||||
: [
|
||||
{
|
||||
path,
|
||||
reason: "invalid_format",
|
||||
message: "Expected an absolute filesystem path on this host.",
|
||||
},
|
||||
],
|
||||
);
|
||||
if (pathIssues.length > 0)
|
||||
return err(new AnalysisInputError(OPERATION, undefined, pathIssues));
|
||||
const inspected = await this.provider.inspect(input.data, options);
|
||||
if (!inspected.ok) return inspected;
|
||||
if (options?.signal?.aborted)
|
||||
|
|
|
|||
|
|
@ -215,6 +215,97 @@ it("advertises exact valid schemas and records inline WASM interface evidence wi
|
|||
expect(close).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("attributes rejected WASM paths to their selected fields before provider work", async () => {
|
||||
const value = wasmArtifactFixture();
|
||||
value.glue = [
|
||||
{
|
||||
artifact: { path: "/glue.js", sha256: "b".repeat(64), bytes: 0 },
|
||||
parse_status: "complete",
|
||||
references: [],
|
||||
},
|
||||
];
|
||||
value.candidates.push({
|
||||
path: "/other.wasm",
|
||||
sha256: "c".repeat(64),
|
||||
bytes: 8,
|
||||
});
|
||||
const validInput = {
|
||||
path: value.artifact.path,
|
||||
glue_paths: ["/glue.js"],
|
||||
candidate_paths: ["/other.wasm"],
|
||||
};
|
||||
const inspect = vi.fn(() =>
|
||||
Promise.resolve(ok(wasmArtifactExecution(value))),
|
||||
);
|
||||
const harness = await createApplicationMcpHarness({
|
||||
serverOptions: {
|
||||
wasmArtifact: new WasmArtifactService({
|
||||
close: () => Promise.resolve(),
|
||||
inspect,
|
||||
}),
|
||||
},
|
||||
});
|
||||
onTestFinished(harness.close);
|
||||
for (const [input, paths] of [
|
||||
[{ path: "relative.wasm" }, [["path"]]],
|
||||
[
|
||||
{ path: value.artifact.path, glue_paths: ["relative.js"] },
|
||||
[["glue_paths", 0]],
|
||||
],
|
||||
[
|
||||
{ path: value.artifact.path, candidate_paths: ["relative.wasm"] },
|
||||
[["candidate_paths", 0]],
|
||||
],
|
||||
[
|
||||
{
|
||||
path: value.artifact.path,
|
||||
glue_paths: ["/glue.js", "relative.js"],
|
||||
candidate_paths: ["/other.wasm", "relative.wasm"],
|
||||
},
|
||||
[
|
||||
["glue_paths", 1],
|
||||
["candidate_paths", 1],
|
||||
],
|
||||
],
|
||||
[
|
||||
{
|
||||
path: "relative.wasm",
|
||||
glue_paths: ["relative.js"],
|
||||
candidate_paths: ["relative-candidate.wasm"],
|
||||
},
|
||||
[["path"], ["glue_paths", 0], ["candidate_paths", 0]],
|
||||
],
|
||||
] as const) {
|
||||
const response = await harness.client.callTool({
|
||||
name: "inspect_wasm_artifact",
|
||||
arguments: input,
|
||||
});
|
||||
const failure = parseMcpToolError(response).error;
|
||||
expect(failure.code).toBe("invalid_request");
|
||||
const { issues } = z
|
||||
.object({ issues: z.array(z.unknown()) })
|
||||
.parse(failure.details);
|
||||
expect(issues).toEqual(
|
||||
paths.map((path) => ({
|
||||
path,
|
||||
reason: "invalid_format",
|
||||
message: "Expected an absolute filesystem path on this host.",
|
||||
})),
|
||||
);
|
||||
}
|
||||
expect(inspect).not.toHaveBeenCalled();
|
||||
const valid = await harness.client.callTool({
|
||||
name: "inspect_wasm_artifact",
|
||||
arguments: validInput,
|
||||
});
|
||||
expect(valid.isError).not.toBe(true);
|
||||
expect(inspect).toHaveBeenCalledExactlyOnceWith(
|
||||
validInput,
|
||||
expect.objectContaining({ signal: expect.any(AbortSignal) }),
|
||||
);
|
||||
await harness.client.ping();
|
||||
});
|
||||
|
||||
it("rejects a WASM provider response with a non-file subject before recording Evidence", async () => {
|
||||
const value = wasmArtifactFixture();
|
||||
const execution = wasmArtifactExecution(value);
|
||||
|
|
|
|||
Loading…
Reference in New Issue