Files
rea/tests/boundary/mcp/javascriptLiteralPayload.test.ts
T
Ysopwnable 9f8659e053 fix(javascript): enforce semantic byte bounds for literals
Fixes #3933. Part of #4027.

Normalize exact literals and configuration defaults through the shared primitive owner. Preserve tagged limits through member/destructuring projection and record encountered bounds in per-analysis state independently of later selected values. Remove the retained-value rescan and duplicate default reader. Preserve source Evidence, bounded/null values, aggregate wire capacity and complete endpoint lookup identities for query/trace/comparison.

Validated 66 focused and 932 semantic tests on the restored identical runtime; final public selection 7/7 adds an augmented large-endpoint acceptance (67 distinct focused cases across invocations). Full static/docs and fresh real Obsidian controls passed. Compact measurements and rejected attempts remain recorded.

Co-authored-by: Kaoru0822 <208281322+kaoru0822-kitauji@users.noreply.github.com>
2026-10-15 23:43:05 +08:00

182 lines
6.9 KiB
TypeScript

import { Client } from "@modelcontextprotocol/client";
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
import { createHash } from "node:crypto";
import { writeFile } from "node:fs/promises";
import { join, resolve } from "node:path";
import { expect } from "vitest";
import { toolContract } from "../../../src/contracts/toolContracts.js";
import { javascriptApplicationAnalysisResultSchema } from "../../../src/domain/javascript/javascriptApplicationAnalysis.js";
import { javaScriptSemanticTraceResultSchema } from "../../../src/domain/javascript/javascriptSemanticTraceSchemas.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
import { cliTest } from "../../support/cli/cliFixture.js";
const boundedLargeLiteralPayloads = (): readonly string[] => {
// Preserve the former aggregate payload size while each normalized value
// stays within its independent semantic byte budget.
const values = Array.from({ length: 16 }, (_, index) =>
`rea_literal_payload_ğ\\"\0${String(index)}_`.padEnd(24, "x").repeat(4096),
);
expect(
values.reduce((total, value) => total + value.length, 0),
).toBeGreaterThanOrEqual(24 * 65_536);
expect(values.every((value) => value.length * 6 + 2 <= 1_048_576)).toBe(true);
return values;
};
cliTest(
"delivers aggregate large literal payloads through CLI and default stdio MCP without metadata copies",
async ({ cli }) => {
const root = await createTestTempDirectory("rea-literal-payload-");
const values = boundedLargeLiteralPayloads();
const value = values[0];
if (value === undefined) throw new Error("Missing literal payload");
const source = [
...values.map(
(payload, index) =>
`export const payload${String(index)} = ${JSON.stringify(payload)};`,
),
'export const choice = flag ? 1 : "1";',
'export const empty = "";',
].join("\n");
await writeFile(join(root, "main.js"), source);
const cliResponse = await cli.run({
arguments: [
"analyze-javascript-application",
root,
"--artifact-format",
"directory",
"--json",
],
timeoutMs: 60_000,
});
expect(cliResponse.exitCode).toBe(0);
const cliEvidence = toolContract(
"analyze_javascript_application",
).outputSchema.parse(cliResponse.json);
const client = new Client({ name: "literal-payload-e2e", version: "1" });
const transport = new StdioClientTransport({
command: process.execPath,
args: [resolve("scripts/rea.mjs"), "mcp"],
cwd: process.cwd(),
env: { PATH: process.env.PATH ?? "" },
stderr: "pipe",
});
transport.stderr?.on("data", () => undefined);
try {
await client.connect(transport);
const response = await client.callTool({
name: "analyze_javascript_application",
arguments: { input_path: root, format: "directory" },
});
// Default wire delivery must retain every admitted value in the aggregate.
expect(response.isError).not.toBe(true);
const evidence = toolContract(
"analyze_javascript_application",
).outputSchema.parse(response.structuredContent);
expect(evidence.evidence_id).toBe(cliEvidence.evidence_id);
const text = response.content.find((item) => item.type === "text");
if (text?.type !== "text") throw new Error("Missing MCP text result");
const textEvidence = toolContract(
"analyze_javascript_application",
).outputSchema.parse(JSON.parse(text.text));
expect(textEvidence.evidence_id).toBe(evidence.evidence_id);
const graph = javascriptApplicationAnalysisResultSchema.parse(
evidence.normalized_result,
).semantic_graph;
const retainedPayloads = graph.nodes
.filter(
(node) =>
node.kind === "literal" &&
typeof node.properties.value === "string" &&
values.includes(node.properties.value),
)
.map((node) => node.properties.value);
expect(retainedPayloads.toSorted()).toEqual(values.toSorted());
const literal = graph.nodes.find(
(node) => node.kind === "literal" && node.properties.value === value,
);
if (literal === undefined) throw new Error("Complete literal was lost");
const sourceSha256 = createHash("sha256").update(source).digest("hex");
expect(literal.identity).toMatchObject({
module_path: "main.js",
artifact_sha256: sourceSha256,
});
expect(literal.evidence.location).toMatchObject({
available: true,
value: { kind: "source-range", source: "main.js" },
});
const choices = graph.nodes.filter(
(node) =>
node.kind === "literal" &&
(node.properties.value === 1 || node.properties.value === "1"),
);
expect(choices.length).toBe(2);
expect(new Set(choices.map((node) => node.node_id)).size).toBe(2);
expect(
graph.nodes.some(
(node) => node.kind === "literal" && node.properties.value === "",
),
).toBe(true);
const traceResponse = await client.callTool({
name: "trace_javascript_semantics",
arguments: {
application: {
kind: "retained-evidence",
evidence_id: evidence.evidence_id,
},
query: {
seed: { kind: "semantic-node", node_id: literal.node_id },
direction: "backward-provenance",
},
},
});
expect(traceResponse.isError).not.toBe(true);
const traceEvidence = toolContract(
"trace_javascript_semantics",
).outputSchema.parse(traceResponse.structuredContent);
const trace = javaScriptSemanticTraceResultSchema.parse(
traceEvidence.normalized_result,
);
expect(
trace.nodes.some(
(node) =>
node.node_id === literal.node_id && node.properties.value === value,
),
).toBe(true);
const valueTraceResponse = await client.callTool({
name: "trace_javascript_semantics",
arguments: {
application: {
kind: "retained-evidence",
evidence_id: evidence.evidence_id,
},
query: {
seed: { kind: "literal", value: "1" },
direction: "backward-provenance",
},
},
});
expect(valueTraceResponse.isError).not.toBe(true);
const valueTraceEvidence = toolContract(
"trace_javascript_semantics",
).outputSchema.parse(valueTraceResponse.structuredContent);
const valueTrace = javaScriptSemanticTraceResultSchema.parse(
valueTraceEvidence.normalized_result,
);
expect(valueTrace.seed_node_ids).toEqual(
choices
.filter((node) => node.properties.value === "1")
.map((node) => node.node_id),
);
await client.ping();
} finally {
await client.close();
await transport.close();
}
},
60_000,
);