fix(javascript): accept retained empty endpoint seeds (#6462)

Application analysis preserves the complete literal request endpoint,
including the empty string, and the event seed already accepts a
retained empty name. The endpoint seed still required a non-empty
value, so trace-javascript-semantics refused to select a retained ""
endpoint with invalid_request.

The endpoint seed now accepts any string and keeps exact matching.
Boundary-field seeds keep their minimum because boundary nodes are only
produced from method names.

Found while fixing #6458; the event half of that report landed in
cf5e4f6fa.

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Lewis WangandClaude Fable 5.1 authored and GitHub committed 2026-11-03 10:10:33 +08:00
1 parent 193e01abb1
commit b912ec184c
2 files changed
+64 -1

No files matched your search

@@ -42,9 +42,11 @@ export const javaScriptSemanticQuerySeedSchema = z.discriminatedUnion("kind", [
kind: z.literal("property"),
name: z.string(),
}),
// Analysis preserves the complete literal endpoint, including the empty
// string, so the seed matches any retained value exactly, as event does.
z.strictObject({
kind: z.literal("endpoint"),
value: z.string().min(1),
value: z.string(),
}),
z.strictObject({
kind: z.literal("event"),
@@ -162,3 +162,64 @@ cliTest(
},
120_000,
);
cliTest(
"traces a retained empty literal endpoint by its preserved value",
async ({ cli }) => {
const root = await createTestTempDirectory("rea-empty-endpoint-seed-");
await writeFile(
join(root, "main.js"),
'export const load = async () => (await fetch("")).json();\n',
);
const analyzed = await cli.run({
arguments: [
"analyze-javascript-application",
root,
"--artifact-format",
"directory",
"--json",
],
timeoutMs: 60_000,
});
expect(analyzed.exitCode, analyzed.stderr).toBe(0);
const application = toolContract(
"analyze_javascript_application",
).outputSchema.parse(analyzed.json);
const graph = javascriptApplicationAnalysisResultSchema.parse(
application.normalized_result,
).semantic_graph;
// Analysis keeps the complete literal endpoint, here the empty string.
const retained = new Set(
graph.nodes
.filter(
(node) =>
(node.kind === "request" || node.kind === "response") &&
node.properties.endpoint === "",
)
.map((node) => node.node_id),
);
expect(retained.size).toBeGreaterThan(0);
const request = join(root, "endpoint-query.json");
await writeFile(
request,
JSON.stringify({
application,
query: {
seed: { kind: "endpoint", value: "" },
direction: "forward-influence",
},
}),
);
const traced = await cli.run({
arguments: ["trace-javascript-semantics", request, "--json"],
timeoutMs: 60_000,
});
expect(traced.exitCode, traced.stdout).toBe(0);
const trace = javaScriptSemanticTraceResultSchema.parse(
toolContract("trace_javascript_semantics").outputSchema.parse(traced.json)
.normalized_result,
);
expect(new Set(trace.seed_node_ids)).toEqual(retained);
},
120_000,
);