import { z } from "zod"; import { canonicalDigest, canonicalJson } from "../comparisonSemantics.js"; import { compareCodePoints, uniqueSorted } from "../canonicalOrdering.js"; import { freezeOwnedJsonSnapshotSteps } from "../immutableJson.js"; import type { ApplicationGraphEvidence } from "./javascriptApplicationEvidenceSchemas.js"; import { JAVASCRIPT_SEMANTIC_RELATION_FAMILIES, JAVASCRIPT_SEMANTIC_RELATION_FAMILY, javaScriptSemanticFingerprintInputSchema, javaScriptSemanticFingerprintSchema, javaScriptSemanticGraphInputSchema, javaScriptSemanticGraphRecordSchema, javaScriptSemanticNodeInputSchema, javaScriptSemanticNodeSchema, javaScriptSemanticRelationInputSchema, javaScriptSemanticRelationSchema, javaScriptSemanticUnknownInputSchema, javaScriptSemanticUnknownSchema, type JavaScriptSemanticGraphInput, type JavaScriptSemanticGraphNode, type JavaScriptSemanticGraphRelation, } from "./javascriptSemanticGraphSchemas.js"; const normalizeEvidence = ( evidence: ApplicationGraphEvidence, ): ApplicationGraphEvidence => ({ ...evidence, coverage: { ...evidence.coverage, limits: [...evidence.coverage.limits].sort((left, right) => compareCodePoints( canonicalJson(left, "JavaScript semantic graph"), canonicalJson(right, "JavaScript semantic graph"), ), ), }, limitations: uniqueSorted(evidence.limitations), evidence_ids: uniqueSorted(evidence.evidence_ids), }); /** Derive a semantic node ID from its kind and exact artifact identity. */ export const javaScriptSemanticNodeId = ( node: Pick, ): string => `jsrg_node_${canonicalDigest({ kind: node.kind, identity: node.identity }, "JavaScript semantic graph")}`; /** Normalize one semantic entity and derive its artifact-version identifier. */ export const createJavaScriptSemanticGraphNode = ( input: unknown, ): JavaScriptSemanticGraphNode => { const parsed = javaScriptSemanticNodeInputSchema.parse(input); const semantic = { ...parsed, application_node_ids: uniqueSorted(parsed.application_node_ids), evidence: normalizeEvidence(parsed.evidence), identifier_strategy: { strategy: "semantic-content-sha256" as const, stability: "artifact-version" as const, }, }; return javaScriptSemanticNodeSchema.parse({ ...semantic, node_id: javaScriptSemanticNodeId(semantic), }); }; /** Normalize one semantic relationship and derive its exact identifier. */ export const createJavaScriptSemanticGraphRelation = ( input: unknown, ): JavaScriptSemanticGraphRelation => { const parsed = javaScriptSemanticRelationInputSchema.parse(input); const semantic = { ...parsed, evidence: normalizeEvidence(parsed.evidence), identifier_strategy: { strategy: "semantic-content-sha256" as const, stability: "relationship-exact" as const, }, }; return javaScriptSemanticRelationSchema.parse({ ...semantic, relation_id: `jsrg_relation_${canonicalDigest(semantic, "JavaScript semantic graph")}`, }); }; /** Normalize one unresolved semantic frontier and derive its identifier. */ export const createJavaScriptSemanticGraphUnknown = (input: unknown) => { const parsed = javaScriptSemanticUnknownInputSchema.parse(input); const semantic = { ...parsed, relation_kinds: uniqueSorted(parsed.relation_kinds), candidate_node_ids: uniqueSorted(parsed.candidate_node_ids), evidence: normalizeEvidence(parsed.evidence), }; return javaScriptSemanticUnknownSchema.parse({ ...semantic, unknown_id: `jsrg_unknown_${canonicalDigest(semantic, "JavaScript semantic graph")}`, }); }; /** Normalize one function fingerprint and derive its component commitment. */ export const createJavaScriptSemanticFingerprint = (input: unknown) => { const parsed = javaScriptSemanticFingerprintInputSchema.parse(input); const semantic = { ...parsed, components: { ...parsed.components, effects: uniqueSorted(parsed.components.effects), }, limitations: uniqueSorted(parsed.limitations), evidence: normalizeEvidence(parsed.evidence), }; const fingerprintSha256 = canonicalDigest( semantic.components, "JavaScript semantic graph", ); return javaScriptSemanticFingerprintSchema.parse({ ...semantic, fingerprint_sha256: fingerprintSha256, fingerprint_id: `jsrg_fingerprint_${canonicalDigest( { function_node_id: semantic.function_node_id, algorithm: semantic.algorithm, fingerprint_sha256: fingerprintSha256, }, "JavaScript semantic graph", )}`, }); }; type GraphRecord = z.infer; type SemanticGraphIssue = { readonly code: "custom"; readonly path: PropertyKey[]; readonly message: string; }; interface GraphIssueReporter { readonly addIssue: (issue: SemanticGraphIssue) => void; } const sortedUniqueIssue = ( values: readonly string[], path: PropertyKey[], label: string, context: GraphIssueReporter, ): void => { for (let index = 1; index < values.length; index += 1) { if (compareCodePoints(values[index - 1] ?? "", values[index] ?? "") < 0) continue; context.addIssue({ code: "custom", path: [...path, index], message: `${label} must be sorted and unique`, }); return; } }; const checkCoverage = ( graph: GraphRecord, context: GraphIssueReporter, ): void => { const families = graph.coverage.families.map(({ family }) => family); if ( canonicalJson(families, "JavaScript semantic graph") !== canonicalJson( JAVASCRIPT_SEMANTIC_RELATION_FAMILIES, "JavaScript semantic graph", ) ) context.addIssue({ code: "custom", path: ["coverage", "families"], message: "Coverage must name every semantic relation family once in canonical order", }); if ( graph.coverage.status === "complete" && (graph.coverage.truncated || graph.coverage.omitted_nodes !== 0 || graph.coverage.omitted_relations !== 0 || graph.coverage.families.some(({ status }) => status !== "complete")) ) context.addIssue({ code: "custom", path: ["coverage"], message: "Complete graph coverage cannot omit or truncate semantic facts", }); if (graph.coverage.truncated && graph.coverage.limits.length === 0) context.addIssue({ code: "custom", path: ["coverage", "limits"], message: "Truncated graph coverage must identify an effective limit", }); const relationsByFamily = new Map( JAVASCRIPT_SEMANTIC_RELATION_FAMILIES.map((family) => [family, 0]), ); for (const relation of graph.relations) { const family = JAVASCRIPT_SEMANTIC_RELATION_FAMILY[relation.relation]; relationsByFamily.set(family, (relationsByFamily.get(family) ?? 0) + 1); } const unknownsById = new Map( graph.unknowns.map((unknown) => [unknown.unknown_id, unknown]), ); for (const [index, family] of graph.coverage.families.entries()) { if (family.retained_relations !== relationsByFamily.get(family.family)) context.addIssue({ code: "custom", path: ["coverage", "families", index, "retained_relations"], message: "Family retained relation count does not match graph content", }); if ( family.unknown_ids.some( (identifier) => unknownsById.get(identifier)?.family !== family.family, ) ) context.addIssue({ code: "custom", path: ["coverage", "families", index, "unknown_ids"], message: "Family coverage references an unknown from another family", }); if ( family.status === "complete" && (family.omitted_relations !== 0 || family.unknown_ids.length > 0) ) context.addIssue({ code: "custom", path: ["coverage", "families", index], message: "Complete family coverage cannot omit or retain unknown facts", }); } }; const checkCanonicalOrder = ( graph: GraphRecord, context: GraphIssueReporter, ): void => { sortedUniqueIssue( graph.root_node_ids, ["root_node_ids"], "Root nodes", context, ); sortedUniqueIssue( graph.nodes.map(({ node_id }) => node_id), ["nodes"], "Nodes", context, ); sortedUniqueIssue( graph.relations.map(({ relation_id }) => relation_id), ["relations"], "Relations", context, ); sortedUniqueIssue( graph.fingerprints.map(({ fingerprint_id }) => fingerprint_id), ["fingerprints"], "Fingerprints", context, ); sortedUniqueIssue( graph.unknowns.map(({ unknown_id }) => unknown_id), ["unknowns"], "Unknown frontiers", context, ); sortedUniqueIssue(graph.limitations, ["limitations"], "Limitations", context); }; const checkNodes = ( graph: GraphRecord, nodes: ReadonlyMap, context: GraphIssueReporter, ): void => { for (const [index, node] of graph.nodes.entries()) { if (node.node_id !== javaScriptSemanticNodeId(node)) context.addIssue({ code: "custom", path: ["nodes", index, "node_id"], message: "Node identifier is stale", }); if ( node.function_node_id !== null && nodes.get(node.function_node_id)?.kind !== "function" ) context.addIssue({ code: "custom", path: ["nodes", index, "function_node_id"], message: "Function owner must name a function node", }); } }; const checkRoots = ( graph: GraphRecord, nodes: ReadonlyMap, context: GraphIssueReporter, ): void => { for (const root of graph.root_node_ids) if (!nodes.has(root)) context.addIssue({ code: "custom", path: ["root_node_ids"], message: "Root identifier must name a semantic node", }); }; const checkRelations = ( graph: GraphRecord, nodes: ReadonlyMap, context: GraphIssueReporter, ): void => { for (const [index, relation] of graph.relations.entries()) { const { relation_id: identifier, ...semantic } = relation; if ( identifier !== "jsrg_relation_" + canonicalDigest(semantic, "JavaScript semantic graph") ) context.addIssue({ code: "custom", path: ["relations", index, "relation_id"], message: "Relation identifier is stale", }); if ( !nodes.has(relation.source_node_id) || !nodes.has(relation.target_node_id) ) context.addIssue({ code: "custom", path: ["relations", index], message: "Relation endpoints must name semantic nodes", }); if (relation.source_node_id === relation.target_node_id) context.addIssue({ code: "custom", path: ["relations", index], message: "Semantic relations cannot be self-referential", }); if ( relation.resolution === "candidate" && relation.evidence.state === "observed" ) context.addIssue({ code: "custom", path: ["relations", index, "evidence", "state"], message: "Candidate relations cannot claim observed resolution", }); } }; const checkUnknowns = ( graph: GraphRecord, nodes: ReadonlyMap, context: GraphIssueReporter, ): void => { for (const [index, unknown] of graph.unknowns.entries()) { const { unknown_id: identifier, ...semantic } = unknown; if ( identifier !== "jsrg_unknown_" + canonicalDigest(semantic, "JavaScript semantic graph") ) context.addIssue({ code: "custom", path: ["unknowns", index, "unknown_id"], message: "Unknown frontier identifier is stale", }); if (unknown.node_id !== null && !nodes.has(unknown.node_id)) context.addIssue({ code: "custom", path: ["unknowns", index, "node_id"], message: "Unknown frontier node is absent", }); if (unknown.candidate_node_ids.some((nodeId) => !nodes.has(nodeId))) context.addIssue({ code: "custom", path: ["unknowns", index, "candidate_node_ids"], message: "Unknown frontier candidate node is absent", }); if ( unknown.relation_kinds.some( (kind) => JAVASCRIPT_SEMANTIC_RELATION_FAMILY[kind] !== unknown.family, ) ) context.addIssue({ code: "custom", path: ["unknowns", index, "relation_kinds"], message: "Unknown relation kinds must belong to their declared family", }); if (!["unknown", "unavailable"].includes(unknown.evidence.state)) context.addIssue({ code: "custom", path: ["unknowns", index, "evidence", "state"], message: "Unknown frontiers require unknown or unavailable evidence", }); } }; const checkUnknownReferences = ( graph: GraphRecord, context: GraphIssueReporter, ): void => { const unknownIds = new Set( graph.unknowns.map(({ unknown_id }) => unknown_id), ); for (const [index, family] of graph.coverage.families.entries()) if (family.unknown_ids.some((identifier) => !unknownIds.has(identifier))) context.addIssue({ code: "custom", path: ["coverage", "families", index, "unknown_ids"], message: "Family coverage references an absent unknown frontier", }); }; const checkFingerprints = ( graph: GraphRecord, nodes: ReadonlyMap, context: GraphIssueReporter, ): void => { for (const [index, fingerprint] of graph.fingerprints.entries()) { if (nodes.get(fingerprint.function_node_id)?.kind !== "function") context.addIssue({ code: "custom", path: ["fingerprints", index, "function_node_id"], message: "Fingerprint must name a function node", }); if ( fingerprint.fingerprint_sha256 !== canonicalDigest(fingerprint.components, "JavaScript semantic graph") ) context.addIssue({ code: "custom", path: ["fingerprints", index, "fingerprint_sha256"], message: "Fingerprint component commitment is stale", }); const expectedIdentifier = "jsrg_fingerprint_" + canonicalDigest( { function_node_id: fingerprint.function_node_id, algorithm: fingerprint.algorithm, fingerprint_sha256: fingerprint.fingerprint_sha256, }, "JavaScript semantic graph", ); if (fingerprint.fingerprint_id !== expectedIdentifier) context.addIssue({ code: "custom", path: ["fingerprints", index, "fingerprint_id"], message: "Fingerprint identifier is stale", }); if ( fingerprint.status !== "complete" && fingerprint.limitations.length === 0 ) context.addIssue({ code: "custom", path: ["fingerprints", index, "limitations"], message: "Incomplete fingerprints require a limitation", }); } }; const checkGraphContent = ( graph: GraphRecord, context: GraphIssueReporter, ): void => { checkCanonicalOrder(graph, context); const nodes = new Map(graph.nodes.map((node) => [node.node_id, node])); checkNodes(graph, nodes, context); checkRoots(graph, nodes, context); checkRelations(graph, nodes, context); checkUnknowns(graph, nodes, context); checkUnknownReferences(graph, context); checkFingerprints(graph, nodes, context); checkCoverage(graph, context); if (graph.coverage.status !== "complete" && graph.limitations.length === 0) context.addIssue({ code: "custom", path: ["limitations"], message: "Non-complete graph coverage requires a limitation", }); }; const checkGraph = (graph: GraphRecord, context: GraphIssueReporter): void => { checkGraphContent(graph, context); const { graph_id: identifier, ...semantic } = graph; if ( identifier !== "jsrg_" + canonicalDigest(semantic, "JavaScript semantic graph") ) context.addIssue({ code: "custom", path: ["graph_id"], message: "Graph identifier is stale", }); }; /** Strict semantic graph schema with verified canonical commitments. */ export const javaScriptSemanticGraphSchema = javaScriptSemanticGraphRecordSchema.superRefine(checkGraph); /** Fully validated JavaScript Semantic Relation Graph. */ export type JavaScriptSemanticGraph = z.infer< typeof javaScriptSemanticGraphSchema >; /** Normalize a complete companion graph and derive its graph ID. */ export const createJavaScriptSemanticGraph = ( input: unknown, ): JavaScriptSemanticGraph => { const parsed = javaScriptSemanticGraphInputSchema.parse(input); const semantic: JavaScriptSemanticGraphInput = { ...parsed, root_node_ids: uniqueSorted(parsed.root_node_ids), nodes: [...parsed.nodes].sort((left, right) => compareCodePoints(left.node_id, right.node_id), ), relations: [...parsed.relations].sort((left, right) => compareCodePoints(left.relation_id, right.relation_id), ), fingerprints: [...parsed.fingerprints].sort((left, right) => compareCodePoints(left.fingerprint_id, right.fingerprint_id), ), unknowns: [...parsed.unknowns].sort((left, right) => compareCodePoints(left.unknown_id, right.unknown_id), ), coverage: { ...parsed.coverage, limits: [...parsed.coverage.limits].sort((left, right) => compareCodePoints( canonicalJson(left, "JavaScript semantic graph"), canonicalJson(right, "JavaScript semantic graph"), ), ), families: [...parsed.coverage.families] .map((family) => ({ ...family, unknown_ids: uniqueSorted(family.unknown_ids), })) .sort((left, right) => compareCodePoints(left.family, right.family)), }, limitations: uniqueSorted(parsed.limitations), }; // The input schema already cloned and validated every field. Validate the // normalized relationships in place, then derive the only added field. The // public schema still verifies arbitrary records and their commitments. const record: GraphRecord = { ...semantic, graph_id: `jsrg_${canonicalDigest(semantic, "JavaScript semantic graph")}`, }; const issues: SemanticGraphIssue[] = []; checkGraphContent(record, { addIssue: (issue) => { issues.push(issue); }, }); if (issues.length > 0) throw new z.ZodError(issues); return record; }; const validatedImmutableSemanticGraphs = new WeakSet(); /** Clone and validate synchronously, then return steps to seal the owned graph. */ export const createImmutableJavaScriptSemanticGraphSteps = ( input: unknown, ): Generator => sealValidatedSemanticGraphSteps(createJavaScriptSemanticGraph(input)); function* sealValidatedSemanticGraphSteps( graph: JavaScriptSemanticGraph, ): Generator { yield* freezeOwnedJsonSnapshotSteps(graph); validatedImmutableSemanticGraphs.add(graph); return graph; } /** Recognize the exact completely sealed graph produced by the owned factory. */ export const isValidatedImmutableJavaScriptSemanticGraph = ( value: unknown, ): value is JavaScriptSemanticGraph => typeof value === "object" && value !== null && validatedImmutableSemanticGraphs.has(value); export type { JavaScriptSemanticGraphNode, JavaScriptSemanticGraphRelation };