Commit literal values with canonical JSON SHA-256 in semantic role keys and use a type label for strings. Preserve complete values, provenance and literal queries while avoiding two payload copies in each string node. Verify real compiled CLI/default stdio MCP, typed union seeds, empty strings and trace continuation with a large source-owned fixture. Refs #723 BREAKING CHANGE: Newly produced semantic literal node IDs use value-sha256 role keys, and string labels read "string literal". Read exact values from properties.value and obtain node IDs from the selected parent graph. Previously stored Evidence and its original IDs remain valid.
194 lines
6.6 KiB
TypeScript
194 lines
6.6 KiB
TypeScript
import {
|
|
semanticContainer,
|
|
semanticPropertyPointer,
|
|
} from "../../domain/javascript/javascriptSemanticSlots.js";
|
|
import { digestCanonicalValue } from "../../domain/canonicalDigest.js";
|
|
import type { JavaScriptSemanticBinding } from "../../domain/javascript/javascriptSemanticIr.js";
|
|
import type {
|
|
JavaScriptSemanticProperty,
|
|
JavaScriptSemanticValue,
|
|
} from "../../domain/javascript/javascriptSemanticValueTypes.js";
|
|
import { createJavaScriptSemanticGraphUnknown } from "../../domain/javascript/javascriptSemanticGraph.js";
|
|
import {
|
|
retainSemanticGraphNode,
|
|
addSemanticGraphRelation,
|
|
addSemanticGraphUnknown,
|
|
} from "./JavaScriptSemanticGraphConstruction.js";
|
|
import type { SemanticFlowProjectionContext } from "./JavaScriptSemanticGraphFlowProjection.js";
|
|
import {
|
|
observedSemanticEvidence,
|
|
unknownSemanticEvidence,
|
|
} from "./JavaScriptSemanticGraphEvidence.js";
|
|
|
|
/** Project bounded literal values and object slots for exact query seeds. */
|
|
export const projectSemanticValues = (
|
|
context: SemanticFlowProjectionContext,
|
|
): void => {
|
|
for (const binding of context.ir.bindings) {
|
|
const bindingNode = context.bindingNodes.get(binding.bindingId);
|
|
if (bindingNode === undefined) continue;
|
|
projectValue({
|
|
context,
|
|
binding,
|
|
value: binding.value,
|
|
target: bindingNode,
|
|
path: [],
|
|
});
|
|
}
|
|
};
|
|
|
|
interface ValueProjectionInput {
|
|
readonly context: SemanticFlowProjectionContext;
|
|
readonly binding: JavaScriptSemanticBinding;
|
|
readonly value: JavaScriptSemanticValue;
|
|
readonly target: ReturnType<typeof semanticPropertySlot>;
|
|
readonly path: readonly string[];
|
|
}
|
|
|
|
const projectValue = (input: ValueProjectionInput): void => {
|
|
const { context, binding, value, target, path } = input;
|
|
const role =
|
|
path.length === 0 ? "binding" : `property:${semanticPropertyPointer(path)}`;
|
|
if (target === null) return;
|
|
if (value.status === "literal") {
|
|
const literal = addLiteralNode(context, binding, value.value, role);
|
|
addSemanticGraphRelation(context.state, {
|
|
source: literal,
|
|
target,
|
|
relation: "defines",
|
|
resolution: "resolved",
|
|
});
|
|
} else if (value.status === "union")
|
|
for (const primitive of value.values) {
|
|
const literal = addLiteralNode(context, binding, primitive, role);
|
|
addSemanticGraphRelation(context.state, {
|
|
source: literal,
|
|
target,
|
|
relation: "defines",
|
|
resolution: "candidate",
|
|
});
|
|
}
|
|
else if (value.status === "object" || value.status === "array") {
|
|
const container = semanticContainer(value);
|
|
for (const property of container.slots) {
|
|
const propertyPath = [...path, property.name];
|
|
const slot = semanticPropertySlot(
|
|
context,
|
|
binding.bindingId,
|
|
propertyPath,
|
|
property,
|
|
);
|
|
if (property.presence === "absent") continue;
|
|
addSemanticGraphRelation(context.state, {
|
|
source: target,
|
|
target: slot,
|
|
relation: "writes-property",
|
|
resolution: property.presence === "present" ? "resolved" : "candidate",
|
|
});
|
|
if (property.presence === "present")
|
|
projectValue({
|
|
context,
|
|
binding,
|
|
value: property.value,
|
|
target: slot,
|
|
path: propertyPath,
|
|
});
|
|
}
|
|
if (container.coverage.status === "partial")
|
|
addSemanticGraphUnknown(
|
|
context.state,
|
|
createJavaScriptSemanticGraphUnknown(
|
|
{
|
|
node_id: target.node_id,
|
|
family: "object-flow",
|
|
relation_kinds: ["writes-property"],
|
|
reason: "ambiguous-target",
|
|
detail: `Initializer container has unknown ${
|
|
value.status === "object" ? "properties" : "items"
|
|
}${container.coverage.omitted === null ? " with an unknown omitted count" : `; ${container.coverage.omitted} omitted`}.`,
|
|
candidate_node_ids: [target.node_id],
|
|
evidence: unknownSemanticEvidence(
|
|
context.file,
|
|
binding.definitions[0]?.location ?? null,
|
|
),
|
|
},
|
|
context.state.evidenceContexts,
|
|
),
|
|
);
|
|
} else if (
|
|
value.status === "unknown" ||
|
|
value.status === "ambiguous" ||
|
|
value.status === "cycle"
|
|
) {
|
|
const location = binding.definitions[0]?.location ?? null;
|
|
const evidence = observedSemanticEvidence(context.file, location);
|
|
addSemanticGraphUnknown(
|
|
context.state,
|
|
createJavaScriptSemanticGraphUnknown(
|
|
{
|
|
node_id: target.node_id,
|
|
family: "data-flow",
|
|
relation_kinds: ["defines"],
|
|
reason:
|
|
value.resourceLimit === undefined
|
|
? "unknown-value"
|
|
: "resource-limit",
|
|
detail: `${value.reason} Unknown value at ${role}.`,
|
|
candidate_node_ids: [target.node_id],
|
|
evidence: {
|
|
...evidence,
|
|
authority: "unknown",
|
|
state: "unknown",
|
|
confidence: "unknown",
|
|
limitations: [value.reason],
|
|
},
|
|
},
|
|
context.state.evidenceContexts,
|
|
),
|
|
);
|
|
}
|
|
};
|
|
|
|
const addLiteralNode = (
|
|
context: SemanticFlowProjectionContext,
|
|
binding: JavaScriptSemanticBinding,
|
|
value: string | number | boolean | null,
|
|
role: string,
|
|
) =>
|
|
retainSemanticGraphNode(context.state, context.file, {
|
|
kind: "literal",
|
|
// Identity needs an exact value commitment, not another payload copy.
|
|
// Keep the complete literal in properties for queries and Evidence export.
|
|
roleKey: `literal:${binding.bindingId}:${role}:value-sha256:${digestCanonicalValue(value, "Semantic literal")}`,
|
|
location: binding.definitions[0]?.location ?? null,
|
|
label: typeof value === "string" ? "string literal" : JSON.stringify(value),
|
|
functionNodeId:
|
|
context.bindingNodes.get(binding.bindingId)?.function_node_id ?? null,
|
|
properties: { value },
|
|
});
|
|
|
|
/** Create one canonical slot for a root binding and exact static property path. */
|
|
export const semanticPropertySlot = (
|
|
context: SemanticFlowProjectionContext,
|
|
objectBindingId: string,
|
|
path: readonly string[],
|
|
fact: JavaScriptSemanticProperty,
|
|
) =>
|
|
retainSemanticGraphNode(context.state, context.file, {
|
|
kind: "property-slot",
|
|
roleKey: `property:${JSON.stringify([objectBindingId, path])}`,
|
|
location: null,
|
|
label: fact.name,
|
|
functionNodeId:
|
|
context.bindingNodes.get(objectBindingId)?.function_node_id ?? null,
|
|
properties: {
|
|
name: fact.name,
|
|
property_path: [...path],
|
|
property_pointer: semanticPropertyPointer(path),
|
|
object_binding_id: objectBindingId,
|
|
presence: fact.presence,
|
|
value_status: fact.value.status,
|
|
value_coverage: semanticContainer(fact.value)?.coverage ?? null,
|
|
},
|
|
});
|