diff --git a/src/application/javascript/JavaScriptSemanticGraphAssignments.test.ts b/src/application/javascript/JavaScriptSemanticGraphAssignments.test.ts index ccf33cdff7..b63ccae7fc 100644 --- a/src/application/javascript/JavaScriptSemanticGraphAssignments.test.ts +++ b/src/application/javascript/JavaScriptSemanticGraphAssignments.test.ts @@ -1,3 +1,4 @@ +import { javaScriptSemanticQueryInputSchema } from "../../domain/javascript/javascriptSemanticQuerySchemas.js"; import { expect, it } from "vitest"; import { graphForJavaScript } from "../../../tests/fixtures/javascriptSemanticGraph.js"; import { queryJavaScriptSemanticGraph } from "../../domain/javascript/javascriptSemanticQuery.js"; @@ -17,11 +18,14 @@ it.each([ const state = { speed: 6 }; export function update() { ${body} } `); - const trace = queryJavaScriptSemanticGraph(graph, { - seed: { kind: "literal", value: 0.001 }, - direction: "forward-influence", - include_ambiguous_dynamic_edges: true, - }); + const trace = queryJavaScriptSemanticGraph( + graph, + javaScriptSemanticQueryInputSchema.parse({ + seed: { kind: "literal", value: 0.001 }, + direction: "forward-influence", + include_ambiguous_dynamic_edges: true, + }), + ); expect(trace.nodes.map((node) => node.label)).toContain(reached); expect(trace.nodes.map((node) => node.label)).toContain("return"); expect( @@ -37,11 +41,14 @@ it("does not attribute deferred callback reads to an enclosing assignment", () = let callback; callback = () => accel; `); - const trace = queryJavaScriptSemanticGraph(graph, { - seed: { kind: "literal", value: 0.001 }, - direction: "forward-influence", - include_ambiguous_dynamic_edges: true, - }); + const trace = queryJavaScriptSemanticGraph( + graph, + javaScriptSemanticQueryInputSchema.parse({ + seed: { kind: "literal", value: 0.001 }, + direction: "forward-influence", + include_ambiguous_dynamic_edges: true, + }), + ); expect( graph.relations.filter( (edge) => edge.properties.assignment_operator !== undefined, @@ -88,10 +95,13 @@ it("reaches a property write by default and keeps uncertain later reads opt-in", const graph = graphForJavaScript( "const accel = 0.001; const state = {speed: 6}; function update() { state.speed += accel; return state.speed; }", ); - const trace = queryJavaScriptSemanticGraph(graph, { - seed: { kind: "literal", value: 0.001 }, - direction: "forward-influence", - }); + const trace = queryJavaScriptSemanticGraph( + graph, + javaScriptSemanticQueryInputSchema.parse({ + seed: { kind: "literal", value: 0.001 }, + direction: "forward-influence", + }), + ); expect( trace.nodes.some( (node) => node.kind === "property-slot" && node.label === "speed", @@ -128,11 +138,14 @@ it.each([ nodes.get(edge.target_node_id)?.label === "speed", ); expect(assignment).toBeDefined(); - const trace = queryJavaScriptSemanticGraph(graph, { - seed: { kind: "literal", value: 0.001 }, - direction: "forward-influence", - include_ambiguous_dynamic_edges: true, - }); + const trace = queryJavaScriptSemanticGraph( + graph, + javaScriptSemanticQueryInputSchema.parse({ + seed: { kind: "literal", value: 0.001 }, + direction: "forward-influence", + include_ambiguous_dynamic_edges: true, + }), + ); expect(trace.nodes.map((node) => node.label)).toContain("return"); }, ); diff --git a/src/application/javascript/JavaScriptSemanticGraphBuilder.test.ts b/src/application/javascript/JavaScriptSemanticGraphBuilder.test.ts index 7dc372c211..b1d1d688a2 100644 --- a/src/application/javascript/JavaScriptSemanticGraphBuilder.test.ts +++ b/src/application/javascript/JavaScriptSemanticGraphBuilder.test.ts @@ -1,3 +1,4 @@ +import { javaScriptSemanticQueryInputSchema } from "../../domain/javascript/javascriptSemanticQuerySchemas.js"; import { primitiveByteExpansionSource, primitiveCandidateExpansionExpression, @@ -83,11 +84,14 @@ it.each([ ); if (source.includes("export {")) expect(exportReference).toBeDefined(); - const trace = queryJavaScriptSemanticGraph(graph, { - seed: { kind: "semantic-node", node_id: binding.node_id }, - direction: "backward-provenance", - allowed_relations: ["writes"], - }); + const trace = queryJavaScriptSemanticGraph( + graph, + javaScriptSemanticQueryInputSchema.parse({ + seed: { kind: "semantic-node", node_id: binding.node_id }, + direction: "backward-provenance", + allowed_relations: ["writes"], + }), + ); expect(trace.status).toBe("found"); expect(trace.summary).toMatchObject({ total_seed_matches: 1, @@ -122,11 +126,14 @@ it("projects closure and direct interprocedural flow without execution", () => { ({ kind, label }) => kind === "parameter" && label === "value", ); if (parameter === undefined) throw new Error("Expected parameter node"); - const provenance = queryJavaScriptSemanticGraph(graph, { - seed: { kind: "semantic-node", node_id: parameter.node_id }, - direction: "backward-provenance", - include_ambiguous_dynamic_edges: true, - }); + const provenance = queryJavaScriptSemanticGraph( + graph, + javaScriptSemanticQueryInputSchema.parse({ + seed: { kind: "semantic-node", node_id: parameter.node_id }, + direction: "backward-provenance", + include_ambiguous_dynamic_edges: true, + }), + ); expect(provenance.relations.map(({ relation }) => relation)).toEqual( expect.arrayContaining([ "aliases", @@ -208,11 +215,14 @@ it("keeps a directly returned or passed property in backward provenance", () => (item) => item.kind === "binding" && item.label === binding, ); if (node === undefined) throw new Error(`missing ${binding}`); - const trace = queryJavaScriptSemanticGraph(graph, { - seed: { kind: "semantic-node", node_id: node.node_id }, - direction: "backward-provenance", - include_ambiguous_dynamic_edges: candidates, - }); + const trace = queryJavaScriptSemanticGraph( + graph, + javaScriptSemanticQueryInputSchema.parse({ + seed: { kind: "semantic-node", node_id: node.node_id }, + direction: "backward-provenance", + include_ambiguous_dynamic_edges: candidates, + }), + ); return trace.nodes.some( (item) => item.kind === "literal" && item.properties.value === "products", ); @@ -239,16 +249,22 @@ it("keeps dynamic calls explicit and returns complete deterministic results", () )?.source_node_id; if (seedNodeId === undefined) throw new Error("Expected a definition seed"); - const first = queryJavaScriptSemanticGraph(graph, { - seed: { kind: "semantic-node", node_id: seedNodeId }, - direction: "forward-influence", - include_ambiguous_dynamic_edges: true, - }); - const repeated = queryJavaScriptSemanticGraph(graph, { - seed: { kind: "semantic-node", node_id: seedNodeId }, - direction: "forward-influence", - include_ambiguous_dynamic_edges: true, - }); + const first = queryJavaScriptSemanticGraph( + graph, + javaScriptSemanticQueryInputSchema.parse({ + seed: { kind: "semantic-node", node_id: seedNodeId }, + direction: "forward-influence", + include_ambiguous_dynamic_edges: true, + }), + ); + const repeated = queryJavaScriptSemanticGraph( + graph, + javaScriptSemanticQueryInputSchema.parse({ + seed: { kind: "semantic-node", node_id: seedNodeId }, + direction: "forward-influence", + include_ambiguous_dynamic_edges: true, + }), + ); expect(repeated).toEqual(first); }); @@ -358,17 +374,23 @@ it("keeps ambiguous interprocedural flow out of the default traversal", () => { "argument-to-parameter" as const, ], }; - const strict = queryJavaScriptSemanticGraph(graph, query); + const strict = queryJavaScriptSemanticGraph( + graph, + javaScriptSemanticQueryInputSchema.parse(query), + ); expect( strict.relations.some( ({ relation }) => relation === "argument-to-parameter", ), ).toBe(false); expect(strict.status).toBe("ambiguous"); - const admitted = queryJavaScriptSemanticGraph(graph, { - ...query, - include_ambiguous_dynamic_edges: true, - }); + const admitted = queryJavaScriptSemanticGraph( + graph, + javaScriptSemanticQueryInputSchema.parse({ + ...query, + include_ambiguous_dynamic_edges: true, + }), + ); expect( admitted.relations.filter( ({ relation }) => relation === "argument-to-parameter", @@ -403,18 +425,24 @@ it("connects caller results, direct returns, parameters, and captures", () => { readOuter === undefined ) throw new Error("Expected semantic flow nodes"); - const provenance = queryJavaScriptSemanticGraph(graph, { - seed: { kind: "semantic-node", node_id: output.node_id }, - direction: "backward-provenance", - }); + const provenance = queryJavaScriptSemanticGraph( + graph, + javaScriptSemanticQueryInputSchema.parse({ + seed: { kind: "semantic-node", node_id: output.node_id }, + direction: "backward-provenance", + }), + ); expect(provenance.nodes.map(({ node_id }) => node_id)).toContain( input.node_id, ); - const influence = queryJavaScriptSemanticGraph(graph, { - seed: { kind: "semantic-node", node_id: outer.node_id }, - direction: "forward-influence", - allowed_relations: ["captures"], - }); + const influence = queryJavaScriptSemanticGraph( + graph, + javaScriptSemanticQueryInputSchema.parse({ + seed: { kind: "semantic-node", node_id: outer.node_id }, + direction: "forward-influence", + allowed_relations: ["captures"], + }), + ); expect(influence.nodes.map(({ node_id }) => node_id)).toContain( readOuter.node_id, ); @@ -500,10 +528,13 @@ it("keeps duplicate function fingerprints ambiguous", () => { expect(digest).toBeDefined(); expect(graph.fingerprints[1]?.fingerprint_sha256).toBe(digest); if (digest === undefined) throw new Error("Expected fingerprint"); - const query = queryJavaScriptSemanticGraph(graph, { - seed: { kind: "function", fingerprint_sha256: digest }, - direction: "forward-influence", - }); + const query = queryJavaScriptSemanticGraph( + graph, + javaScriptSemanticQueryInputSchema.parse({ + seed: { kind: "function", fingerprint_sha256: digest }, + direction: "forward-influence", + }), + ); expect(query.status).toBe("ambiguous"); expect(query.summary.total_seed_matches).toBe(2); }); @@ -519,19 +550,25 @@ it("selects analyzer-produced boundaries by their method field", () => { method: "Number", }); expect( - queryJavaScriptSemanticGraph(graph, { - seed: { kind: "boundary-field", field: "Number" }, - direction: "forward-influence", - }), + queryJavaScriptSemanticGraph( + graph, + javaScriptSemanticQueryInputSchema.parse({ + seed: { kind: "boundary-field", field: "Number" }, + direction: "forward-influence", + }), + ), ).toMatchObject({ seed_node_ids: [boundary.node_id], summary: { total_seed_matches: 1 }, }); expect( - queryJavaScriptSemanticGraph(graph, { - seed: { kind: "boundary-field", field: "missing-field" }, - direction: "forward-influence", - }).summary.total_seed_matches, + queryJavaScriptSemanticGraph( + graph, + javaScriptSemanticQueryInputSchema.parse({ + seed: { kind: "boundary-field", field: "missing-field" }, + direction: "forward-influence", + }), + ).summary.total_seed_matches, ).toBe(0); }); @@ -713,11 +750,14 @@ it.each([ expect(returnNode?.kind).toBe("return-site"); if (returnNode === undefined) throw new Error("Expected the linked return site"); - const query = queryJavaScriptSemanticGraph(graph, { - seed: { kind: "semantic-node", node_id: returnNode.node_id }, - direction: "forward-influence", - allowed_relations: ["returns-task"], - }); + const query = queryJavaScriptSemanticGraph( + graph, + javaScriptSemanticQueryInputSchema.parse({ + seed: { kind: "semantic-node", node_id: returnNode.node_id }, + direction: "forward-influence", + allowed_relations: ["returns-task"], + }), + ); expect(query.relations).toContainEqual(relation); expect(query.nodes.map(({ node_id }) => node_id)).toContain(promise?.node_id); }); @@ -954,11 +994,14 @@ it("keeps full static property identities distinct through backward provenance", expect(slot.properties.presence).toBe( literal === null ? "absent" : "present", ); - const trace = queryJavaScriptSemanticGraph(graph, { - seed: { kind: "semantic-node", node_id: slot.node_id }, - direction: "backward-provenance", - allowed_relations: ["defines"], - }); + const trace = queryJavaScriptSemanticGraph( + graph, + javaScriptSemanticQueryInputSchema.parse({ + seed: { kind: "semantic-node", node_id: slot.node_id }, + direction: "backward-provenance", + allowed_relations: ["defines"], + }), + ); expect( trace.nodes .filter(({ kind }) => kind === "literal") @@ -1039,11 +1082,14 @@ it("keeps dynamic intermediate receivers unresolved instead of selecting a stati ({ kind, label }) => kind === "binding" && label === "selected", ); if (selected === undefined) throw new Error("Missing selected binding"); - const trace = queryJavaScriptSemanticGraph(graph, { - seed: { kind: "semantic-node", node_id: selected.node_id }, - direction: "backward-provenance", - allowed_relations: ["defines"], - }); + const trace = queryJavaScriptSemanticGraph( + graph, + javaScriptSemanticQueryInputSchema.parse({ + seed: { kind: "semantic-node", node_id: selected.node_id }, + direction: "backward-provenance", + allowed_relations: ["defines"], + }), + ); expect(trace.nodes.filter(({ kind }) => kind === "literal")).toEqual([]); }); @@ -1087,11 +1133,14 @@ it.each([ resolution: resolved ? "resolved" : "candidate", }), ); - const trace = queryJavaScriptSemanticGraph(graph, { - seed: { kind: "semantic-node", node_id: write.node_id }, - direction: "forward-influence", - allowed_relations: ["writes-property"], - }); + const trace = queryJavaScriptSemanticGraph( + graph, + javaScriptSemanticQueryInputSchema.parse({ + seed: { kind: "semantic-node", node_id: write.node_id }, + direction: "forward-influence", + allowed_relations: ["writes-property"], + }), + ); expect(trace.nodes.some(({ node_id }) => node_id === slot.node_id)).toBe( resolved, ); @@ -1123,11 +1172,14 @@ it.each(["", "root.length = 0;", "root.length = query(); root[0] = 9;"])( presence: "present", value_status: "unknown", }); - const trace = queryJavaScriptSemanticGraph(graph, { - seed: { kind: "property", name: "length" }, - direction: "forward-influence", - allowed_relations: ["reads-property"], - }); + const trace = queryJavaScriptSemanticGraph( + graph, + javaScriptSemanticQueryInputSchema.parse({ + seed: { kind: "property", name: "length" }, + direction: "forward-influence", + allowed_relations: ["reads-property"], + }), + ); expect(trace.seed_node_ids).toEqual([slot?.node_id]); expect(trace.relations).toContainEqual( expect.objectContaining({ @@ -1156,10 +1208,13 @@ it.each([ ({ kind, label }) => kind === "binding" && label === "root", ); if (root === undefined) throw new Error("Expected root binding"); - const result = queryJavaScriptSemanticGraph(graph, { - seed: { kind: "semantic-node", node_id: root.node_id }, - direction: "forward-influence", - }); + const result = queryJavaScriptSemanticGraph( + graph, + javaScriptSemanticQueryInputSchema.parse({ + seed: { kind: "semantic-node", node_id: root.node_id }, + direction: "forward-influence", + }), + ); expect(result.coverage.status).toBe("partial"); expect(result.unknowns).toContainEqual( @@ -1211,10 +1266,13 @@ it.each([ identity.role_key.includes("binding:shared"), ); if (sharedMode === undefined) throw new Error("Expected shared mode slot"); - const result = queryJavaScriptSemanticGraph(graph, { - seed: { kind: "semantic-node", node_id: sharedMode.node_id }, - direction: "backward-provenance", - }); + const result = queryJavaScriptSemanticGraph( + graph, + javaScriptSemanticQueryInputSchema.parse({ + seed: { kind: "semantic-node", node_id: sharedMode.node_id }, + direction: "backward-provenance", + }), + ); expect(sharedMode.properties.presence).toBe("unknown-coverage"); expect(result.nodes).toContainEqual( @@ -1243,11 +1301,14 @@ it.each(["{}", "unknownRoot", "{ a: null }"])( value_status: "unknown", }, }); - const trace = queryJavaScriptSemanticGraph(graph, { - seed: { kind: "property", name: "c" }, - direction: "backward-provenance", - allowed_relations: ["defines"], - }); + const trace = queryJavaScriptSemanticGraph( + graph, + javaScriptSemanticQueryInputSchema.parse({ + seed: { kind: "property", name: "c" }, + direction: "backward-provenance", + allowed_relations: ["defines"], + }), + ); expect(trace.seed_node_ids).toEqual([slot?.node_id]); expect(trace.nodes.filter(({ kind }) => kind === "literal")).toEqual([]); }, diff --git a/src/application/javascript/JavaScriptSemanticGraphResources.test.ts b/src/application/javascript/JavaScriptSemanticGraphResources.test.ts index 8e69279072..a6d2b964d0 100644 --- a/src/application/javascript/JavaScriptSemanticGraphResources.test.ts +++ b/src/application/javascript/JavaScriptSemanticGraphResources.test.ts @@ -1,3 +1,4 @@ +import { javaScriptSemanticQueryInputSchema } from "../../domain/javascript/javascriptSemanticQuerySchemas.js"; import { expect, it } from "vitest"; import { graphForJavaScript as graphFor } from "../../../tests/fixtures/javascriptSemanticGraph.js"; @@ -287,10 +288,13 @@ it("projects literal seeds and static object flow", () => { kind === "property-slot" && properties.name === "token", ), ).toBeDefined(); - const literal = queryJavaScriptSemanticGraph(graph, { - seed: { kind: "literal", value: "TOKEN" }, - direction: "forward-influence", - }); + const literal = queryJavaScriptSemanticGraph( + graph, + javaScriptSemanticQueryInputSchema.parse({ + seed: { kind: "literal", value: "TOKEN" }, + direction: "forward-influence", + }), + ); expect(literal.status).toBe("ambiguous"); const targetName = (targetId: string): string => { const target = graph.nodes.find(({ node_id }) => node_id === targetId); @@ -320,10 +324,13 @@ it("projects literal seeds and static object flow", () => { "token", ]); expect(literal.summary.total_seed_matches).toBe(literalTargets.length); - const property = queryJavaScriptSemanticGraph(graph, { - seed: { kind: "property", name: "token" }, - direction: "forward-influence", - }); + const property = queryJavaScriptSemanticGraph( + graph, + javaScriptSemanticQueryInputSchema.parse({ + seed: { kind: "property", name: "token" }, + direction: "forward-influence", + }), + ); expect(property.status).toBe("ambiguous"); expect(property.seed_node_ids.map(targetName).sort()).toEqual([ "copy.token", @@ -366,10 +373,13 @@ it("does not promote transformed return expressions to resolved aliases", () => ); if (output === undefined || input === undefined) throw new Error("Expected caller bindings"); - const strict = queryJavaScriptSemanticGraph(graph, { - seed: { kind: "semantic-node", node_id: output.node_id }, - direction: "backward-provenance", - }); + const strict = queryJavaScriptSemanticGraph( + graph, + javaScriptSemanticQueryInputSchema.parse({ + seed: { kind: "semantic-node", node_id: output.node_id }, + direction: "backward-provenance", + }), + ); expect(strict.nodes.map(({ node_id }) => node_id)).not.toContain( input.node_id, ); diff --git a/src/application/javascript/JavaScriptSemanticGraphSeedQueries.test.ts b/src/application/javascript/JavaScriptSemanticGraphSeedQueries.test.ts index dd10e2b279..598bae4251 100644 --- a/src/application/javascript/JavaScriptSemanticGraphSeedQueries.test.ts +++ b/src/application/javascript/JavaScriptSemanticGraphSeedQueries.test.ts @@ -1,3 +1,4 @@ +import { javaScriptSemanticQueryInputSchema } from "../../domain/javascript/javascriptSemanticQuerySchemas.js"; import { expect, it } from "vitest"; import { graphForJavaScript } from "../../../tests/fixtures/javascriptSemanticGraph.js"; @@ -18,10 +19,13 @@ it.each([ properties.property_path.at(-1) === "speed", ); expect(accesses.length).toBeGreaterThan(0); - const trace = queryJavaScriptSemanticGraph(graph, { - seed: { kind: "property", name: "speed" }, - direction: "forward-influence", - }); + const trace = queryJavaScriptSemanticGraph( + graph, + javaScriptSemanticQueryInputSchema.parse({ + seed: { kind: "property", name: "speed" }, + direction: "forward-influence", + }), + ); expect(trace.seed_node_ids).toEqual( accesses.map(({ node_id }) => node_id).sort(), ); @@ -33,10 +37,13 @@ it.each([ expect.arrayContaining(relevant.map(({ unknown_id }) => unknown_id)), ); expect( - queryJavaScriptSemanticGraph(graph, { - seed: { kind: "property", name: "absent" }, - direction: "forward-influence", - }).seed_node_ids, + queryJavaScriptSemanticGraph( + graph, + javaScriptSemanticQueryInputSchema.parse({ + seed: { kind: "property", name: "absent" }, + direction: "forward-influence", + }), + ).seed_node_ids, ).toEqual([]); }, ); @@ -45,10 +52,13 @@ it("keeps resolved property seeds on their slot without duplicating access occur const graph = graphForJavaScript( "const state = { speed: 1 }; const result = state.speed;", ); - const trace = queryJavaScriptSemanticGraph(graph, { - seed: { kind: "property", name: "speed" }, - direction: "backward-provenance", - }); + const trace = queryJavaScriptSemanticGraph( + graph, + javaScriptSemanticQueryInputSchema.parse({ + seed: { kind: "property", name: "speed" }, + direction: "backward-provenance", + }), + ); expect(trace.seed_node_ids).toHaveLength(1); expect( graph.nodes.find(({ node_id }) => node_id === trace.seed_node_ids[0])?.kind, @@ -59,10 +69,13 @@ it("queries an empty event name as an exact retained event identity", () => { const graph = graphForJavaScript( 'const { EventEmitter } = require("node:events"); const bus = new EventEmitter(); bus.on("", function emptyListener() {}); bus.on("data", function dataListener() {});', ); - const trace = queryJavaScriptSemanticGraph(graph, { - seed: { kind: "event", name: "" }, - direction: "forward-influence", - }); + const trace = queryJavaScriptSemanticGraph( + graph, + javaScriptSemanticQueryInputSchema.parse({ + seed: { kind: "event", name: "" }, + direction: "forward-influence", + }), + ); expect(trace.seed_node_ids.length).toBeGreaterThan(0); expect( trace.nodes diff --git a/src/application/javascript/JavaScriptSemanticTraceService.ts b/src/application/javascript/JavaScriptSemanticTraceService.ts index 8aa831599e..1b5f6f698e 100644 --- a/src/application/javascript/JavaScriptSemanticTraceService.ts +++ b/src/application/javascript/JavaScriptSemanticTraceService.ts @@ -7,7 +7,7 @@ import { parseApplicationGraphEvidence, } from "./JavaScriptApplicationEvidenceGraph.js"; import type { Evidence } from "../../domain/evidence.js"; -import { queryJavaScriptSemanticGraphValidated } from "../../domain/javascript/javascriptSemanticQuery.js"; +import { queryJavaScriptSemanticGraph } from "../../domain/javascript/javascriptSemanticQuery.js"; import { traceJavaScriptSemanticsInputSchema, type JavaScriptSemanticTraceResult, @@ -40,10 +40,7 @@ export const traceJavaScriptSemanticsEvidenceValidated = ( }, ]), ); - const query = queryJavaScriptSemanticGraphValidated( - source.semanticGraph, - input.query, - ); + const query = queryJavaScriptSemanticGraph(source.semanticGraph, input.query); const result: JavaScriptSemanticTraceResult = { ...query, source_evidence_id: source.evidence.evidence_id, diff --git a/src/application/javascript/JavaScriptSemanticUnknownCapacity.test.ts b/src/application/javascript/JavaScriptSemanticUnknownCapacity.test.ts index 40fd99fde4..6dec214d3b 100644 --- a/src/application/javascript/JavaScriptSemanticUnknownCapacity.test.ts +++ b/src/application/javascript/JavaScriptSemanticUnknownCapacity.test.ts @@ -1,3 +1,4 @@ +import { javaScriptSemanticQueryInputSchema } from "../../domain/javascript/javascriptSemanticQuerySchemas.js"; import { createHash } from "node:crypto"; import { expect, it } from "vitest"; import type { JavaScriptArtifactFile } from "../../domain/javascript/javascriptArtifactFiles.js"; @@ -83,10 +84,13 @@ it("bounds unknowns independently of retained nodes and accounts for examined om ); expect(javaScriptSemanticGraphSchema.parse(graph)).toEqual(graph); expect( - queryJavaScriptSemanticGraph(graph, { - seed: { kind: "property", name: "missing" }, - direction: "backward-provenance", - }), + queryJavaScriptSemanticGraph( + graph, + javaScriptSemanticQueryInputSchema.parse({ + seed: { kind: "property", name: "missing" }, + direction: "backward-provenance", + }), + ), ).toMatchObject({ status: "partial", coverage: { status: "partial" } }); }); diff --git a/src/domain/javascript/javascriptSemanticGraphQuery.test.ts b/src/domain/javascript/javascriptSemanticGraphQuery.test.ts index 562a78ca55..71ca2d7f18 100644 --- a/src/domain/javascript/javascriptSemanticGraphQuery.test.ts +++ b/src/domain/javascript/javascriptSemanticGraphQuery.test.ts @@ -1,3 +1,4 @@ +import { javaScriptSemanticQueryInputSchema } from "./javascriptSemanticQuerySchemas.js"; import { semanticFixtureGraph, semanticFixtureInput, @@ -30,10 +31,6 @@ import { type JavaScriptSemanticGraph, } from "./javascriptSemanticGraph.js"; import type { JavaScriptSemanticGraphNode } from "./javascriptSemanticGraphSchemas.js"; -import { - parseJavaScriptSemanticGraph, - serializeJavaScriptSemanticGraph, -} from "./javascriptSemanticGraphSerialization.js"; import { queryJavaScriptSemanticGraph } from "./javascriptSemanticQuery.js"; const SHA = "a".repeat(64); @@ -262,11 +259,14 @@ it("counts candidate edges by query direction and relation filter", () => { direction: "forward-influence" | "backward-provenance", allowedRelations: (typeof JAVASCRIPT_SEMANTIC_RELATIONS)[number][], ) => - queryJavaScriptSemanticGraph(graph, { - seed: { kind: "semantic-node", node_id: binding.node_id }, - direction, - allowed_relations: allowedRelations, - }); + queryJavaScriptSemanticGraph( + graph, + javaScriptSemanticQueryInputSchema.parse({ + seed: { kind: "semantic-node", node_id: binding.node_id }, + direction, + allowed_relations: allowedRelations, + }), + ); expect(query("forward-influence", ["captures"]).status).toBe("ambiguous"); expect(query("forward-influence", ["defines"]).status).toBe("found"); @@ -296,10 +296,13 @@ it("matches scalar literal seeds and skips non-scalar literal metadata", () => { input.nodes.push(...literals); const graph = createJavaScriptSemanticGraph(input); const query = (value: string | number | boolean | null) => - queryJavaScriptSemanticGraph(graph, { - seed: { kind: "literal", value }, - direction: "forward-influence", - }).seed_node_ids; + queryJavaScriptSemanticGraph( + graph, + javaScriptSemanticQueryInputSchema.parse({ + seed: { kind: "literal", value }, + direction: "forward-influence", + }), + ).seed_node_ids; const nodeId = (role: string) => { const literal = literals.find(({ identity }) => identity.role_key === role); if (literal === undefined) throw new Error(`Missing literal ${role}`); @@ -341,10 +344,13 @@ it("matches exact endpoint properties on request and response carriers only", () const request = input.nodes.find(({ kind }) => kind === "request"); if (request === undefined) throw new Error("Expected request carrier"); const query = (value: string) => - queryJavaScriptSemanticGraph(graph, { - seed: { kind: "endpoint", value }, - direction: "forward-influence", - }); + queryJavaScriptSemanticGraph( + graph, + javaScriptSemanticQueryInputSchema.parse({ + seed: { kind: "endpoint", value }, + direction: "forward-influence", + }), + ); expect(query(endpoint).seed_node_ids).toEqual( [request.node_id, response.node_id].toSorted(), ); @@ -412,11 +418,14 @@ it("scopes callers unknowns to call relations while preserving graph uncertainty direction: "callers" | "backward-provenance", allowed_relations?: ["calls"], ) => - queryJavaScriptSemanticGraph(graph, { - seed: { kind: "semantic-node", node_id: seed.node_id }, - direction, - ...(allowed_relations === undefined ? {} : { allowed_relations }), - }); + queryJavaScriptSemanticGraph( + graph, + javaScriptSemanticQueryInputSchema.parse({ + seed: { kind: "semantic-node", node_id: seed.node_id }, + direction, + ...(allowed_relations === undefined ? {} : { allowed_relations }), + }), + ); const callers = query("callers"); const filtered = query("callers", ["calls"]); const backward = query("backward-provenance"); @@ -437,23 +446,29 @@ it("counts candidate ownership cycles once and traverses them finitely", () => { const graph = graphWithCandidateRelations(); const binding = graph.nodes.find(({ label }) => label === "binding"); if (binding === undefined) throw new Error("Expected binding seed"); - const result = queryJavaScriptSemanticGraph(graph, { - seed: { kind: "semantic-node", node_id: binding.node_id }, - direction: "ownership", - allowed_relations: ["owns"], - include_ambiguous_dynamic_edges: true, - }); + const result = queryJavaScriptSemanticGraph( + graph, + javaScriptSemanticQueryInputSchema.parse({ + seed: { kind: "semantic-node", node_id: binding.node_id }, + direction: "ownership", + allowed_relations: ["owns"], + include_ambiguous_dynamic_edges: true, + }), + ); expect(result.status).toBe("ambiguous"); expect(result.relations).toHaveLength(2); expect(result.summary.traversed_nodes).toBe(2); expect(result.summary.traversed_relations).toBe(2); expect( - queryJavaScriptSemanticGraph(graph, { - seed: { kind: "semantic-node", node_id: binding.node_id }, - direction: "ownership", - allowed_relations: ["constructs-request"], - }).status, + queryJavaScriptSemanticGraph( + graph, + javaScriptSemanticQueryInputSchema.parse({ + seed: { kind: "semantic-node", node_id: binding.node_id }, + direction: "ownership", + allowed_relations: ["constructs-request"], + }), + ).status, ).toBe("found"); }); @@ -463,11 +478,6 @@ it("defines every required relation family and canonicalizes records", () => { expect(JAVASCRIPT_SEMANTIC_RELATIONS).toContain("forwards-signal"); expect(JAVASCRIPT_SEMANTIC_RELATIONS).toContain("validates"); const graph = semanticFixtureGraph(); - expect( - parseJavaScriptSemanticGraph( - JSON.parse(serializeJavaScriptSemanticGraph(graph)), - ), - ).toEqual(graph); expect(graph.nodes.map(({ node_id }) => node_id)).toEqual( graph.nodes.map(({ node_id }) => node_id).toSorted(), ); @@ -476,7 +486,7 @@ it("defines every required relation family and canonicalizes records", () => { it("rejects stale identities, dangling endpoints, and incomplete coverage claims", () => { const graph = semanticFixtureGraph(); expect(() => - parseJavaScriptSemanticGraph({ + javaScriptSemanticGraphSchema.parse({ ...graph, graph_id: `jsrg_${"0".repeat(64)}`, }), @@ -753,9 +763,7 @@ it("captures nested input before yielding and preserves the synchronous graph co const graph = complete(steps); expect(graph).toEqual(expected); expect( - parseJavaScriptSemanticGraph( - JSON.parse(serializeJavaScriptSemanticGraph(graph)), - ), + javaScriptSemanticGraphSchema.parse(JSON.parse(JSON.stringify(graph))), ).toEqual(expected); expect(isValidatedImmutableJavaScriptSemanticGraph(graph)).toBe(true); expect(isValidatedImmutableJavaScriptSemanticGraph(expected)).toBe(false); diff --git a/src/domain/javascript/javascriptSemanticGraphSerialization.ts b/src/domain/javascript/javascriptSemanticGraphSerialization.ts deleted file mode 100644 index 7162a5a9d3..0000000000 --- a/src/domain/javascript/javascriptSemanticGraphSerialization.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { canonicalJson } from "../comparisonSemantics.js"; -import { - javaScriptSemanticGraphSchema, - type JavaScriptSemanticGraph, -} from "./javascriptSemanticGraph.js"; - -/** Parse a stored semantic graph and reject stale IDs. */ -export const parseJavaScriptSemanticGraph = ( - input: unknown, -): JavaScriptSemanticGraph => javaScriptSemanticGraphSchema.parse(input); - -/** Serialize a verified semantic graph as canonical JSON. */ -export const serializeJavaScriptSemanticGraph = (input: unknown): string => - canonicalJson( - parseJavaScriptSemanticGraph(input), - "JavaScript semantic graph", - ); diff --git a/src/domain/javascript/javascriptSemanticGraphTraversal.test.ts b/src/domain/javascript/javascriptSemanticGraphTraversal.test.ts index 785538f3c2..5277cf2527 100644 --- a/src/domain/javascript/javascriptSemanticGraphTraversal.test.ts +++ b/src/domain/javascript/javascriptSemanticGraphTraversal.test.ts @@ -25,10 +25,7 @@ import type { JavaScriptSemanticGraphNode, JavaScriptSemanticGraphRelation, } from "./javascriptSemanticGraphSchemas.js"; -import { - queryJavaScriptSemanticGraph, - queryJavaScriptSemanticGraphValidated, -} from "./javascriptSemanticQuery.js"; +import { queryJavaScriptSemanticGraph } from "./javascriptSemanticQuery.js"; import { javaScriptSemanticQueryInputSchema } from "./javascriptSemanticQuerySchemas.js"; import type { JsonValue } from "../jsonValue.js"; @@ -124,10 +121,13 @@ it("returns every relevant unknown inline", () => { const graph = semanticFixtureGraph(true); const callable = graph.nodes.find(({ kind }) => kind === "function"); if (callable === undefined) throw new Error("Expected callable node"); - const result = queryJavaScriptSemanticGraph(graph, { - seed: { kind: "semantic-node", node_id: callable.node_id }, - direction: "forward-influence", - }); + const result = queryJavaScriptSemanticGraph( + graph, + javaScriptSemanticQueryInputSchema.parse({ + seed: { kind: "semantic-node", node_id: callable.node_id }, + direction: "forward-influence", + }), + ); expect(result).toMatchObject({ summary: { relevant_unknowns: 1 }, }); @@ -135,10 +135,13 @@ it("returns every relevant unknown inline", () => { }); it("does not admit a semantic-node seed absent from the graph", () => { - const result = queryJavaScriptSemanticGraph(semanticFixtureGraph(), { - seed: { kind: "semantic-node", node_id: `jsrg_node_${"f".repeat(64)}` }, - direction: "forward-influence", - }); + const result = queryJavaScriptSemanticGraph( + semanticFixtureGraph(), + javaScriptSemanticQueryInputSchema.parse({ + seed: { kind: "semantic-node", node_id: `jsrg_node_${"f".repeat(64)}` }, + direction: "forward-influence", + }), + ); expect(result).toMatchObject({ status: "no-match", @@ -155,7 +158,10 @@ it("returns the complete deterministic forward influence result inline", () => { direction: "forward-influence" as const, expected: { role: "sink" as const, classes: ["request" as const] }, }; - const first = queryJavaScriptSemanticGraph(graph, input); + const first = queryJavaScriptSemanticGraph( + graph, + javaScriptSemanticQueryInputSchema.parse(input), + ); expect(first).toMatchObject({ status: "found", summary: { traversed_nodes: 4, traversed_relations: 3 }, @@ -167,10 +173,13 @@ it("returns the complete deterministic forward influence result inline", () => { it("returns only the referenced evidence contexts with resolvable trace facts", () => { const graph = semanticFixtureGraph(); - const result = queryJavaScriptSemanticGraph(graph, { - seed: { kind: "literal", value: "TOKEN" }, - direction: "forward-influence", - }); + const result = queryJavaScriptSemanticGraph( + graph, + javaScriptSemanticQueryInputSchema.parse({ + seed: { kind: "literal", value: "TOKEN" }, + direction: "forward-influence", + }), + ); const expectedContextIds = [ ...new Set([ ...result.nodes.map(({ evidence }) => evidence.context_id), @@ -228,10 +237,13 @@ it("traverses paths beyond the former fixed depth ceiling", () => { }, }); - const result = queryJavaScriptSemanticGraph(graph, { - seed: { kind: "literal", value: "TOKEN" }, - direction: "forward-influence", - }); + const result = queryJavaScriptSemanticGraph( + graph, + javaScriptSemanticQueryInputSchema.parse({ + seed: { kind: "literal", value: "TOKEN" }, + direction: "forward-influence", + }), + ); expect(result.summary.traversed_nodes).toBe(74); expect(result.nodes).toHaveLength(74); @@ -253,10 +265,13 @@ it("accepts deeply nested semantic properties", () => { }); it("keeps relevant dynamic frontiers unknown", () => { - const result = queryJavaScriptSemanticGraph(semanticFixtureGraph(true), { - seed: { kind: "literal", value: "TOKEN" }, - direction: "forward-influence", - }); + const result = queryJavaScriptSemanticGraph( + semanticFixtureGraph(true), + javaScriptSemanticQueryInputSchema.parse({ + seed: { kind: "literal", value: "TOKEN" }, + direction: "forward-influence", + }), + ); expect(result.status).toBe("found"); expect(result.coverage.status).toBe("partial"); expect(result.unknowns).toMatchObject([ @@ -271,14 +286,17 @@ it("excludes candidate edges unless the caller explicitly opts in", () => { direction: "forward-influence" as const, allowed_relations: ["supplies-request-field" as const], }; - const conservative = queryJavaScriptSemanticGraphValidated( + const conservative = queryJavaScriptSemanticGraph( graph, javaScriptSemanticQueryInputSchema.parse(input), ); - const optedIn = queryJavaScriptSemanticGraph(graph, { - ...input, - include_ambiguous_dynamic_edges: true, - }); + const optedIn = queryJavaScriptSemanticGraph( + graph, + javaScriptSemanticQueryInputSchema.parse({ + ...input, + include_ambiguous_dynamic_edges: true, + }), + ); expect(conservative.summary.traversed_relations).toBe(0); expect(optedIn.summary.traversed_relations).toBe(1); expect(conservative.status).toBe("ambiguous"); @@ -289,9 +307,9 @@ it("excludes candidate edges unless the caller explicitly opts in", () => { ]); }); -it("rejects an empty relation selection at the raw query boundary", () => { +it("rejects an empty relation selection at the query schema boundary", () => { expect(() => - queryJavaScriptSemanticGraph(graphWithCandidateEdge(), { + javaScriptSemanticQueryInputSchema.parse({ seed: { kind: "literal", value: "TOKEN" }, direction: "forward-influence", allowed_relations: [], @@ -456,10 +474,10 @@ it.each([ const nodes = [seed, reached, other]; const control = queryJavaScriptSemanticGraph( directionalGraph(nodes, [resolved], evidenceContexts), - { + javaScriptSemanticQueryInputSchema.parse({ seed: { kind: "semantic-node", node_id: seed.node_id }, direction, - }, + }), ); expect(control).toMatchObject({ status: "found", @@ -468,11 +486,11 @@ it.each([ for (const include of [false, true]) { const result = queryJavaScriptSemanticGraph( directionalGraph(nodes, [resolved, candidate], evidenceContexts), - { + javaScriptSemanticQueryInputSchema.parse({ seed: { kind: "semantic-node", node_id: seed.node_id }, direction, include_ambiguous_dynamic_edges: include, - }, + }), ); expect(result).toMatchObject({ status: relevant ? "ambiguous" : "found", @@ -510,11 +528,11 @@ it("excludes explicitly filtered candidates without hiding partial graph coverag for (const partial of [false, true]) { const result = queryJavaScriptSemanticGraph( directionalGraph([seed, other], [candidate], evidenceContexts, partial), - { + javaScriptSemanticQueryInputSchema.parse({ seed: { kind: "semantic-node", node_id: seed.node_id }, direction: "forward-influence", allowed_relations: ["calls"], - }, + }), ); expect(result).toMatchObject({ status: "found", @@ -545,11 +563,11 @@ it("keeps candidate uncertainty at the end of a long reachable path", () => { for (const include of [false, true]) { const result = queryJavaScriptSemanticGraph( directionalGraph(chain, relations, evidenceContexts), - { + javaScriptSemanticQueryInputSchema.parse({ seed: { kind: "semantic-node", node_id: seed.node_id }, direction: "forward-influence", include_ambiguous_dynamic_edges: include, - }, + }), ); expect(result).toMatchObject({ status: "ambiguous", diff --git a/src/domain/javascript/javascriptSemanticQuery.ts b/src/domain/javascript/javascriptSemanticQuery.ts index 7db6689e19..4fd87aa40e 100644 --- a/src/domain/javascript/javascriptSemanticQuery.ts +++ b/src/domain/javascript/javascriptSemanticQuery.ts @@ -6,7 +6,6 @@ import type { import type { JavaScriptSemanticGraphUnknown } from "./javascriptSemanticGraphSchemas.js"; import { isJavaScriptSemanticOwnershipRelation as ownershipRelation } from "./javascriptSemanticQueryRelations.js"; import { - javaScriptSemanticQueryInputSchema, javaScriptSemanticQueryResultSchema, type JavaScriptSemanticQueryInput, type JavaScriptSemanticQueryResult, @@ -34,18 +33,8 @@ interface QueryResult { readonly retainedRelations: JavaScriptSemanticGraphRelation[]; } -/** Run one deterministic traversal over a verified semantic graph. */ -export const queryJavaScriptSemanticGraph = ( - graph: JavaScriptSemanticGraph, - rawInput: unknown, -): JavaScriptSemanticQueryResult => - queryJavaScriptSemanticGraphValidated( - graph, - javaScriptSemanticQueryInputSchema.parse(rawInput), - ); - /** Run a query already parsed by its owning request boundary. */ -export const queryJavaScriptSemanticGraphValidated = ( +export const queryJavaScriptSemanticGraph = ( graph: JavaScriptSemanticGraph, input: JavaScriptSemanticQueryInput, ): JavaScriptSemanticQueryResult => { diff --git a/tests/boundary/filesystem/javascript-applications/javascriptApplicationSignedZero.test.ts b/tests/boundary/filesystem/javascript-applications/javascriptApplicationSignedZero.test.ts index 3d1f94d144..bd4a52c675 100644 --- a/tests/boundary/filesystem/javascript-applications/javascriptApplicationSignedZero.test.ts +++ b/tests/boundary/filesystem/javascript-applications/javascriptApplicationSignedZero.test.ts @@ -1,3 +1,4 @@ +import { javaScriptSemanticQueryInputSchema } from "../../../../src/domain/javascript/javascriptSemanticQuerySchemas.js"; import { writeFile } from "node:fs/promises"; import { join } from "node:path"; @@ -43,11 +44,14 @@ it.each([ ); if (node === undefined) throw new Error("Expected the selected semantic node"); - const query = queryJavaScriptSemanticGraph(analysis.semantic_graph, { - seed: { kind: "semantic-node", node_id: node.node_id }, - direction: "backward-provenance", - allowed_relations: ["defines"], - }); + const query = queryJavaScriptSemanticGraph( + analysis.semantic_graph, + javaScriptSemanticQueryInputSchema.parse({ + seed: { kind: "semantic-node", node_id: node.node_id }, + direction: "backward-provenance", + allowed_relations: ["defines"], + }), + ); const transportedQuery = javaScriptSemanticQueryResultSchema.parse( JSON.parse(JSON.stringify(query)), ); diff --git a/tests/boundary/filesystem/javascript-applications/javascriptArtifactReconstruction.test.ts b/tests/boundary/filesystem/javascript-applications/javascriptArtifactReconstruction.test.ts index 13c03208d0..1c21f9f21e 100644 --- a/tests/boundary/filesystem/javascript-applications/javascriptArtifactReconstruction.test.ts +++ b/tests/boundary/filesystem/javascript-applications/javascriptArtifactReconstruction.test.ts @@ -50,8 +50,9 @@ import { javascriptApplicationAnalysisResultSchema } from "../../../../src/domai import { createJavaScriptSemanticGraph, isValidatedImmutableJavaScriptSemanticGraph, + javaScriptSemanticGraphSchema, + type JavaScriptSemanticGraph, } from "../../../../src/domain/javascript/javascriptSemanticGraph.js"; -import { parseJavaScriptSemanticGraph } from "../../../../src/domain/javascript/javascriptSemanticGraphSerialization.js"; import { writeJavaScriptArtifactFixture } from "../../../fixtures/javascriptArtifactApplication.js"; import { writeFixtureFiles } from "../../../support/javascriptApplicationFixture.js"; @@ -625,7 +626,7 @@ it("preserves graph commitments and export shapes through worker adoption", asyn .find(({ file }) => file.path === "exports.mjs") ?.semantic?.ir.callables.map(({ name }) => name), ).toContain("unexported"); - expect(parseJavaScriptSemanticGraph(semantic)).toEqual(semantic); + expect(javaScriptSemanticGraphSchema.parse(semantic)).toEqual(semantic); const transferred = createJavaScriptSemanticGraphProjection(); const wireRoot = await createTestTempDirectory("rea-javascript-transfer-"); const decodedModules = new Map(); @@ -810,7 +811,9 @@ it("reconstructs package, Electron roles, Webpack/Rspack modules, and cross-laye input_path: root, }); const graph = parseJavaScriptApplicationGraph(result.graph); - const semanticGraph = parseJavaScriptSemanticGraph(result.semantic_graph); + const semanticGraph = javaScriptSemanticGraphSchema.parse( + result.semantic_graph, + ); expect(Reflect.get(globalThis, "__rea_bundle_executed")).toBeUndefined(); expect(result.input_path).toBe(root); @@ -1119,7 +1122,7 @@ it("recurses into filesystem-backed ASAR containers without losing container-rel const assertSemanticLinks = ( result: Awaited>, graph: ReturnType, - semanticGraph: ReturnType, + semanticGraph: JavaScriptSemanticGraph, ) => { expect(semanticGraph.application_graph_id).toBe(graph.graph_id); const { graph_id: _semanticGraphId, ...semanticGraphInput } = semanticGraph;