From 6298c84eea0e36a7e3779497160e2bbb4907a0d5 Mon Sep 17 00:00:00 2001 From: morluto <76467478+morluto@users.noreply.github.com> Date: Sat, 24 Oct 2026 10:08:56 +0800 Subject: [PATCH] refactor(javascript): consolidate semantic graph boundaries Retire the unused graph serialization facade and use one typed query implementation after canonical request admission. Migrate tests to the owning schemas while preserving graph commitments, Unicode transport, provenance, uncertainty, traversal and reconstruction controls. --- ...JavaScriptSemanticGraphAssignments.test.ts | 51 ++-- .../JavaScriptSemanticGraphBuilder.test.ts | 239 +++++++++++------- .../JavaScriptSemanticGraphResources.test.ts | 34 ++- ...JavaScriptSemanticGraphSeedQueries.test.ts | 45 ++-- .../JavaScriptSemanticTraceService.ts | 7 +- .../JavaScriptSemanticUnknownCapacity.test.ts | 12 +- .../javascriptSemanticGraphQuery.test.ts | 92 ++++--- .../javascriptSemanticGraphSerialization.ts | 17 -- .../javascriptSemanticGraphTraversal.test.ts | 98 ++++--- .../javascript/javascriptSemanticQuery.ts | 13 +- .../javascriptApplicationSignedZero.test.ts | 14 +- .../javascriptArtifactReconstruction.test.ts | 11 +- 12 files changed, 368 insertions(+), 265 deletions(-) delete mode 100644 src/domain/javascript/javascriptSemanticGraphSerialization.ts 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;