From 934ffd28d15f4fbe6687ca8986a949e1b41a07d1 Mon Sep 17 00:00:00 2001 From: keemsisi Date: Sat, 10 Oct 2026 08:09:18 +0100 Subject: [PATCH] perf(javascript): derive the application graph ID once createJavaScriptApplicationGraph hashed the whole graph to derive its graph_id, then parsed it with the public schema, whose invariant check hashed the same content again to verify that ID. Validate the content with every other invariant first, then derive graph_id once from the validated copy. The public schema still verifies graph_id for stored and untrusted graphs, and created graphs are unchanged. --- .../javascriptApplicationGraph.test.ts | 15 +++++++++++++ .../javascript/javascriptApplicationGraph.ts | 22 ++++++++++++++----- 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/src/domain/javascript/javascriptApplicationGraph.test.ts b/src/domain/javascript/javascriptApplicationGraph.test.ts index efedf78c0..58f127928 100644 --- a/src/domain/javascript/javascriptApplicationGraph.test.ts +++ b/src/domain/javascript/javascriptApplicationGraph.test.ts @@ -547,6 +547,21 @@ describe("JavaScript Application Graph: source maps and observations", () => { /Edge identifier/u, ); }); + + it("creates exactly the graph the public schema accepts", () => { + const graph = buildSyntheticJavaScriptApplicationGraph(); + expect(JSON.stringify(graph)).toBe( + JSON.stringify( + javascriptApplicationGraphSchema.parse(structuredClone(graph)), + ), + ); + + const { graph_id: _graphId, ...semantic } = structuredClone(graph); + firstOf(semantic.edges, "edge").edge_id = `jag_edge_${"f".repeat(64)}`; + expect(() => createJavaScriptApplicationGraph(semantic)).toThrow( + /Edge identifier/u, + ); + }); }); describe("JavaScript Application Graph: duplicate and orphan rejection", () => { diff --git a/src/domain/javascript/javascriptApplicationGraph.ts b/src/domain/javascript/javascriptApplicationGraph.ts index ba122e1f0..e16aded75 100644 --- a/src/domain/javascript/javascriptApplicationGraph.ts +++ b/src/domain/javascript/javascriptApplicationGraph.ts @@ -304,8 +304,8 @@ const checkNode = ( type GraphRecord = z.infer; -const checkGraphInvariants = ( - graph: GraphRecord, +const checkGraphContent = ( + graph: JavaScriptApplicationGraphInput, context: z.RefinementCtx, ): void => { sortedUniqueIssue( @@ -394,7 +394,13 @@ const checkGraphInvariants = ( context, ); } +}; +const checkGraphInvariants = ( + graph: GraphRecord, + context: z.RefinementCtx, +): void => { + checkGraphContent(graph, context); const { graph_id: identifier, ...semantic } = graph; if ( identifier !== @@ -411,6 +417,9 @@ const checkGraphInvariants = ( export const javascriptApplicationGraphSchema = javascriptApplicationGraphRecordSchema.superRefine(checkGraphInvariants); +const javascriptApplicationGraphContentSchema = + javascriptApplicationGraphInputSchema.superRefine(checkGraphContent); + /** Fully validated JavaScript Application Graph. */ export type JavaScriptApplicationGraph = z.infer< typeof javascriptApplicationGraphSchema @@ -482,10 +491,11 @@ export const createJavaScriptApplicationGraph = ( }, limitations: uniqueSorted(parsed.limitations), }; - return javascriptApplicationGraphSchema.parse({ - ...semantic, - graph_id: `jag_${digestCanonicalValue(semantic, "JavaScript Application Graph")}`, - }); + const content = javascriptApplicationGraphContentSchema.parse(semantic); + return { + ...content, + graph_id: `jag_${digestCanonicalValue(content, "JavaScript Application Graph")}`, + }; }; const validatedImmutableApplicationGraphs = new WeakSet();