refactor(javascript): use shared canonical identity primitives

This commit is contained in:
morluto committed 2026-10-22 10:10:55 +08:00
1 parent 719d56ebfe
commit 34aa22d0aa
6 files changed
+51 -107

No files matched your search

@@ -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) =>
@@ -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");
@@ -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, "change_id">,
): JavaScriptExportShapeComparisonChange => ({
...semantic,
change_id: `jesc_change_${digestExportShapeValue(semantic)}`,
change_id: `jesc_change_${digestCanonicalValue(semantic, "JavaScript export shape comparison")}`,
});
const literalValue = (
@@ -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",
);
@@ -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. */
+2 -16
View File
@@ -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;
};