From 34aa22d0aa7f59fe950fed7058f9ec5c58acd2df Mon Sep 17 00:00:00 2001 From: morluto <76467478+morluto@users.noreply.github.com> Date: Thu, 22 Oct 2026 10:10:55 +0800 Subject: [PATCH] refactor(javascript): use shared canonical identity primitives --- .../javascriptExportShapeComparison.ts | 27 +++++----- ...javascriptExportShapeComparisonIdentity.ts | 22 -------- .../javascriptExportShapeVariants.ts | 21 ++++---- .../javascriptSemanticGraphSerialization.ts | 16 ++---- .../javascriptSemanticQueryIdentity.ts | 54 +++++++------------ src/domain/staticRuntimeCorrelation.ts | 18 +------ 6 files changed, 51 insertions(+), 107 deletions(-) delete mode 100644 src/domain/javascript/javascriptExportShapeComparisonIdentity.ts diff --git a/src/domain/javascript/javascriptExportShapeComparison.ts b/src/domain/javascript/javascriptExportShapeComparison.ts index 8fb4938397..a59798df8e 100644 --- a/src/domain/javascript/javascriptExportShapeComparison.ts +++ b/src/domain/javascript/javascriptExportShapeComparison.ts @@ -1,9 +1,7 @@ import { uniqueSorted } from "../canonicalOrdering.js"; import { compareUnicodeCodePoints } from "../unicodeCodePointOrder.js"; -import { - canonicalExportShapeValue, - digestExportShapeValue, -} from "./javascriptExportShapeComparisonIdentity.js"; +import { canonicalJson } from "../comparisonSemantics.js"; +import { digestCanonicalValue } from "../canonicalDigest.js"; import { javaScriptExportShapeComparisonResultSchema, type JavaScriptExportShapeComparisonChange, @@ -86,7 +84,7 @@ export const compareJavaScriptExportShapes = ( }; return javaScriptExportShapeComparisonResultSchema.parse({ ...semantic, - comparison_id: `jesc_${digestExportShapeValue(semantic)}`, + comparison_id: `jesc_${digestCanonicalValue(semantic, "JavaScript export shape comparison")}`, }); }; @@ -97,14 +95,17 @@ const sortChanges = ( .map((change, index) => ({ change, index, - sortKey: canonicalExportShapeValue({ - discriminant: change.discriminant, - path: change.path, - status: change.status, - left_source_range: change.left_source_range, - right_source_range: change.right_source_range, - change_id: change.change_id, - }), + sortKey: canonicalJson( + { + discriminant: change.discriminant, + path: change.path, + status: change.status, + left_source_range: change.left_source_range, + right_source_range: change.right_source_range, + change_id: change.change_id, + }, + "JavaScript export shape comparison", + ), })) .sort( (left, right) => diff --git a/src/domain/javascript/javascriptExportShapeComparisonIdentity.ts b/src/domain/javascript/javascriptExportShapeComparisonIdentity.ts deleted file mode 100644 index 256fa42e56..0000000000 --- a/src/domain/javascript/javascriptExportShapeComparisonIdentity.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { createHash } from "node:crypto"; - -import canonicalize from "canonicalize"; - -/** - * Canonically encode one export-shape comparison value. - * - * Comparison identifiers hash the full result semantics, including each - * change's presence object and the per-variant property inventories. - */ -export const canonicalExportShapeValue = (value: unknown): string => { - const encoded = canonicalize(value); - if (encoded === undefined) - throw new TypeError( - "JavaScript export shape comparison could not canonicalize data", - ); - return encoded; -}; - -/** Derive one deterministic SHA-256 comparison identifier component. */ -export const digestExportShapeValue = (value: unknown): string => - createHash("sha256").update(canonicalExportShapeValue(value)).digest("hex"); diff --git a/src/domain/javascript/javascriptExportShapeVariants.ts b/src/domain/javascript/javascriptExportShapeVariants.ts index c4f0839acf..2ba7a0c37b 100644 --- a/src/domain/javascript/javascriptExportShapeVariants.ts +++ b/src/domain/javascript/javascriptExportShapeVariants.ts @@ -1,7 +1,5 @@ -import { - canonicalExportShapeValue, - digestExportShapeValue, -} from "./javascriptExportShapeComparisonIdentity.js"; +import { canonicalJson } from "../comparisonSemantics.js"; +import { digestCanonicalValue } from "../canonicalDigest.js"; import type { JavaScriptExportShapeComparisonChange, JavaScriptExportShapeComparisonResult, @@ -195,7 +193,10 @@ const discriminantOccurrences = ( const value = literalValue(field); if (!value.found) continue; const discriminant = { path: field.path, value: value.value }; - const key = canonicalExportShapeValue(discriminant); + const key = canonicalJson( + discriminant, + "JavaScript export shape comparison", + ); const current = output.get(key); output.set(key, { discriminant, @@ -254,8 +255,8 @@ const compareDiscriminants = ( right: Discriminant, ): number => compareUnicodeCodePoints( - canonicalExportShapeValue(left), - canonicalExportShapeValue(right), + canonicalJson(left, "JavaScript export shape comparison"), + canonicalJson(right, "JavaScript export shape comparison"), ); const selectionUnknownChange = ( @@ -385,8 +386,8 @@ const fieldChangeStatus = ({ if ( leftField !== undefined && rightField !== undefined && - canonicalExportShapeValue(leftField) === - canonicalExportShapeValue(rightField) + canonicalJson(leftField, "JavaScript export shape comparison") === + canonicalJson(rightField, "JavaScript export shape comparison") ) return null; if (leftPresence === "absent" && rightPresence === "absent") return null; @@ -502,7 +503,7 @@ const changeWithId = ( semantic: Omit, ): JavaScriptExportShapeComparisonChange => ({ ...semantic, - change_id: `jesc_change_${digestExportShapeValue(semantic)}`, + change_id: `jesc_change_${digestCanonicalValue(semantic, "JavaScript export shape comparison")}`, }); const literalValue = ( diff --git a/src/domain/javascript/javascriptSemanticGraphSerialization.ts b/src/domain/javascript/javascriptSemanticGraphSerialization.ts index 7a3a8b2eff..7162a5a9d3 100644 --- a/src/domain/javascript/javascriptSemanticGraphSerialization.ts +++ b/src/domain/javascript/javascriptSemanticGraphSerialization.ts @@ -1,18 +1,9 @@ -import canonicalize from "canonicalize"; +import { canonicalJson } from "../comparisonSemantics.js"; import { javaScriptSemanticGraphSchema, type JavaScriptSemanticGraph, } from "./javascriptSemanticGraph.js"; -const canonicalJson = (value: unknown): string => { - const encoded = canonicalize(value); - if (encoded === undefined) - throw new TypeError( - "JavaScript semantic graph could not canonicalize data", - ); - return encoded; -}; - /** Parse a stored semantic graph and reject stale IDs. */ export const parseJavaScriptSemanticGraph = ( input: unknown, @@ -20,4 +11,7 @@ export const parseJavaScriptSemanticGraph = ( /** Serialize a verified semantic graph as canonical JSON. */ export const serializeJavaScriptSemanticGraph = (input: unknown): string => - canonicalJson(parseJavaScriptSemanticGraph(input)); + canonicalJson( + parseJavaScriptSemanticGraph(input), + "JavaScript semantic graph", + ); diff --git a/src/domain/javascript/javascriptSemanticQueryIdentity.ts b/src/domain/javascript/javascriptSemanticQueryIdentity.ts index c6d8373b95..0bb738bad0 100644 --- a/src/domain/javascript/javascriptSemanticQueryIdentity.ts +++ b/src/domain/javascript/javascriptSemanticQueryIdentity.ts @@ -1,6 +1,4 @@ -import { createHash } from "node:crypto"; - -import canonicalize from "canonicalize"; +import { digestCanonicalValue } from "../canonicalDigest.js"; import type { JavaScriptSemanticGraph } from "./javascriptSemanticGraph.js"; import type { JavaScriptSemanticGraphNode } from "./javascriptSemanticGraphSchemas.js"; @@ -8,44 +6,30 @@ import { uniqueSorted } from "../canonicalOrdering.js"; import { compareUnicodeCodePoints } from "../unicodeCodePointOrder.js"; import type { JavaScriptSemanticQueryInput } from "./javascriptSemanticQuerySchemas.js"; -/** Canonical JSON used by semantic query commitments and ordering. */ -export const canonicalJavaScriptSemanticQueryJson = ( - value: unknown, -): string => { - const encoded = canonicalize(value); - if (encoded === undefined) - throw new TypeError( - "JavaScript semantic query could not canonicalize data", - ); - return encoded; -}; - -const digest = (value: unknown): string => - createHash("sha256") - .update(canonicalJavaScriptSemanticQueryJson(value)) - .digest("hex"); - /** Derive the stable identity for one graph-bound query. */ export const javaScriptSemanticQueryIdentifier = ( graph: JavaScriptSemanticGraph, input: JavaScriptSemanticQueryInput, ): string => { const semantic = input; - return `jsrq_${digest({ - source_graph_id: graph.graph_id, - ...semantic, - allowed_relations: - semantic.allowed_relations === undefined - ? undefined - : uniqueSorted(semantic.allowed_relations), - expected: - semantic.expected === null - ? null - : { - ...semantic.expected, - classes: uniqueSorted(semantic.expected.classes), - }, - })}`; + return `jsrq_${digestCanonicalValue( + { + source_graph_id: graph.graph_id, + ...semantic, + allowed_relations: + semantic.allowed_relations === undefined + ? undefined + : uniqueSorted(semantic.allowed_relations), + expected: + semantic.expected === null + ? null + : { + ...semantic.expected, + classes: uniqueSorted(semantic.expected.classes), + }, + }, + "JavaScript semantic query", + )}`; }; /** Resolve deterministic seed candidates without applying traversal limits. */ diff --git a/src/domain/staticRuntimeCorrelation.ts b/src/domain/staticRuntimeCorrelation.ts index 94a8f08311..64cc080d72 100644 --- a/src/domain/staticRuntimeCorrelation.ts +++ b/src/domain/staticRuntimeCorrelation.ts @@ -1,6 +1,4 @@ -import { createHash } from "node:crypto"; - -import canonicalize from "canonicalize"; +import { digestCanonicalValue } from "./canonicalDigest.js"; import { z } from "zod"; import { artifactComparisonResultSchema } from "./artifactComparison.js"; @@ -238,7 +236,7 @@ const correlate = ( hypothesis: mapping.hypothesis, } satisfies JsonValue; return correlationItemSchema.parse({ - correlation_id: `cor_${sha256(canonicalJson(projection))}`, + correlation_id: `cor_${digestCanonicalValue(projection, "Static/runtime correlation")}`, static: { ...mapping.static, ...selectedStatic }, runtime: { ...mapping.runtime, status: runtimeStatus }, side_alignment: mapping.side_alignment, @@ -411,15 +409,3 @@ const assertUnique = (evidence: readonly Evidence[], label: string): void => { `Static/runtime correlation rejects duplicate ${label}`, ); }; - -const sha256 = (value: string): string => - createHash("sha256").update(value).digest("hex"); - -const canonicalJson = (value: JsonValue): string => { - const serialized = canonicalize(value); - if (serialized === undefined) - throw new TypeError( - "RFC 8785 canonicalization rejected correlation mapping", - ); - return serialized; -};