feat(javascript): discover semantic call literals in feature traces (#7840)

* feat(javascript): discover semantic call literals in feature traces

* fix(javascript): bound retained call literals by transfer capacity

* test(javascript): align feature no-match diagnostic
This commit is contained in:
morluto authored and GitHub committed 2026-10-29 01:25:23 +08:00
1 parent 685f6db960
commit f9e7571a3d
20 files changed
+681 -15

No files matched your search

@@ -2,6 +2,8 @@ import { z } from "zod";
/** Complete static checkpoint / per-script result transport ceiling. */
export const JAVASCRIPT_STATIC_FACT_BYTES = 8 * 1024 * 1024;
/** Maximum encoded worker transfer record, including the JSON envelope and newline. */
export const JAVASCRIPT_ANALYSIS_TRANSFER_RECORD_BYTES = 8 * 1024 * 1024;
import type { JsonValue } from "../jsonValue.js";
import type { ApplicationCoverage } from "./javascriptApplicationEvidenceSchemas.js";
@@ -71,6 +71,17 @@ export const readExactJavaScriptLiteral = (
return { found: true, value: node.value };
if (t.isBooleanLiteral(node)) return { found: true, value: node.value };
if (t.isNullLiteral(node)) return { found: true, value: null };
if (
t.isUnaryExpression(node) &&
(node.operator === "+" || node.operator === "-")
) {
const operand = unwrapJavaScriptExpression(node.argument).node;
if (t.isNumericLiteral(operand)) {
const value = node.operator === "-" ? -operand.value : operand.value;
if (Number.isFinite(value) && !Object.is(value, -0))
return { found: true, value };
}
}
if (
t.isTemplateLiteral(node) &&
node.expressions.length === 0 &&
@@ -0,0 +1,45 @@
import * as t from "@babel/types";
import { expect, it } from "vitest";
import { graphForJavaScript } from "../../../tests/fixtures/javascriptSemanticGraph.js";
import { readExactJavaScriptLiteral } from "./javascriptAstValues.js";
import { analyzeParsedJavaScriptSemantics } from "./javascriptSemanticAnalysis.js";
import { parseJavaScriptSource } from "./javascriptSourceParser.js";
import { queryJavaScriptSemanticGraph } from "./javascriptSemanticQuery.js";
import { javaScriptSemanticQueryInputSchema } from "./javascriptSemanticQuerySchemas.js";
it("recognizes wrapped unary numeric literals without collapsing negative zero", () => {
const wrapped = t.tsAsExpression(t.numericLiteral(7), t.tsNumberKeyword());
expect(readExactJavaScriptLiteral(t.unaryExpression("-", wrapped))).toEqual({
found: true,
value: -7,
});
expect(
readExactJavaScriptLiteral(t.unaryExpression("-", t.numericLiteral(0))),
).toEqual({
found: false,
});
});
it("retains wrapped TypeScript call literals for semantic queries", () => {
const source = "rpc(-(7 as number), +(8 as number), -0, variable);";
const parsed = parseJavaScriptSource(source, "example.ts");
if (parsed === null) throw new Error("Expected TypeScript source to parse");
const graph = graphForJavaScript(
source,
analyzeParsedJavaScriptSemantics(parsed),
);
const call = graph.nodes.find(({ kind }) => kind === "call-site");
expect(call?.properties.arguments).toEqual([
expect.objectContaining({ index: 0, value: -7 }),
expect.objectContaining({ index: 1, value: 8 }),
]);
if (call === undefined) throw new Error("Expected call site");
const result = queryJavaScriptSemanticGraph(
graph,
javaScriptSemanticQueryInputSchema.parse({
seed: { kind: "literal", value: -7 },
direction: "forward-influence",
}),
);
expect(result.seed_node_ids).toContain(call.node_id);
});
@@ -365,7 +365,7 @@ describe("feature trace projection from contextBridge source members", () => {
});
expect(result.evidence_links).toEqual([sourceEvidenceId]);
expect(result.limitations).toContain(
"No graph entity matched the literal seed; this is not evidence that the feature is absent.",
"No application graph entity matched the literal seed; this is not evidence that the feature is absent.",
);
});
@@ -42,7 +42,7 @@ const seedMatch = (
): ApplicationFeatureSeedMatch[] => {
const mode = featureSeedMatchMode(seed);
const caseSensitive = seed.case_sensitive;
const query = normalized(seed.value, caseSensitive);
const query = normalizeFeatureSeedText(seed.value, caseSensitive);
if (!kindMatches(node, seed.kind)) return [];
const requestedEntryPath =
seed.kind === "api" && "entry_path" in seed ? seed.entry_path : undefined;
@@ -62,7 +62,7 @@ const seedMatch = (
)
return [];
const match = candidateFields(node, seed.kind).find((candidate) => {
const value = normalized(candidate.value, caseSensitive);
const value = normalizeFeatureSeedText(candidate.value, caseSensitive);
return mode === "exact" ? value === query : value.includes(query);
});
return match === undefined
@@ -272,7 +272,11 @@ const propertyFields = (
return output;
};
const normalized = (value: string, caseSensitive: boolean): string => {
/** Normalize literal matching identically across application and semantic labels. */
export const normalizeFeatureSeedText = (
value: string,
caseSensitive: boolean,
): string => {
const normalizedValue = value.normalize(caseSensitive ? "NFC" : "NFKC");
return caseSensitive
? normalizedValue
@@ -12,14 +12,18 @@ import type {
} from "./javascriptApplicationGraphSchemas.js";
import {
applicationFeatureTraceResultSchema,
featureSeedMatchMode,
type ApplicationFeatureSeed,
type ApplicationFeatureTraceResult,
type TraceApplicationFeatureInput,
} from "./javascriptFeatureTraceSchemas.js";
import {
findApplicationFeatureSeeds,
normalizeFeatureSeedText,
type ApplicationFeatureSeedMatch,
} from "./javascriptFeatureSeed.js";
import type { JavaScriptSemanticGraph } from "./javascriptSemanticGraph.js";
import { semanticCallLiteralValues } from "./javascriptSemanticCallLiterals.js";
import {
traverseApplicationFeature,
type ApplicationFeatureTraversal,
@@ -30,6 +34,8 @@ import { buildJavaScriptNativeHandoffs } from "./javascriptNativeHandoff.js";
export interface ApplicationFeatureTraceProjectionInput {
readonly sourceEvidenceId: string;
readonly graph: JavaScriptApplicationGraph;
readonly semanticGraph?: JavaScriptSemanticGraph | null;
readonly semanticPage?: { readonly offset: number; readonly limit: number };
readonly nativeEvidence: readonly Evidence[];
readonly seed: ApplicationFeatureSeed;
readonly direction: TraceApplicationFeatureInput["direction"];
@@ -98,12 +104,14 @@ export const traceApplicationFeature = (
const noMatchResult = (
input: ApplicationFeatureTraceProjectionInput,
): ApplicationFeatureTraceResult => {
const discovery = discoverSemanticLabels(input);
const semantic = {
source_evidence_id: input.sourceEvidenceId,
source_graph_id: input.graph.graph_id,
seed: input.seed,
direction: input.direction,
seed_matches: [],
...(discovery === undefined ? {} : { semantic_discovery: discovery }),
graph: null,
paths: [],
native_handoffs: [],
@@ -125,7 +133,12 @@ const noMatchResult = (
},
evidence_links: [input.sourceEvidenceId],
limitations: uniqueSorted([
"No graph entity matched the literal seed; this is not evidence that the feature is absent.",
"No application graph entity matched the literal seed; this is not evidence that the feature is absent.",
...(discovery === undefined
? []
: [
"Semantic discovery matches retained node labels and direct string call arguments; matches are starting points, not application-graph feature traces. Use trace_javascript_semantics with a returned trace_seed and the same application Evidence.",
]),
...sourceCoverageLimitations(input.graph),
]),
};
@@ -135,6 +148,56 @@ const noMatchResult = (
});
};
/** Semantic discovery is independent of the application graph traversal. */
const discoverSemanticLabels = (
input: ApplicationFeatureTraceProjectionInput,
): ApplicationFeatureTraceResult["semantic_discovery"] => {
const graph = input.semanticGraph;
const seed = input.seed;
if (graph === undefined || graph === null || seed.kind !== "string")
return undefined;
const query = normalizeFeatureSeedText(seed.value, seed.case_sensitive);
const mode = featureSeedMatchMode(seed);
const { offset, limit } = input.semanticPage ?? { offset: 0, limit: 64 };
const matches: NonNullable<
ApplicationFeatureTraceResult["semantic_discovery"]
>["matches"] = [];
let total = 0;
for (const node of graph.nodes) {
const { label, node_id, kind } = node;
const values = [
...(label === null ? [] : [label]),
...semanticCallLiteralValues(node).filter(
(value): value is string => typeof value === "string",
),
];
if (
!values.some((entry) => {
const value = normalizeFeatureSeedText(entry, seed.case_sensitive);
return mode === "exact" ? value === query : value.includes(query);
})
)
continue;
if (total >= offset && matches.length < limit)
matches.push({
node_id,
kind,
trace_seed: { kind: "semantic-node", node_id },
});
total += 1;
}
return {
source_graph_id: graph.graph_id,
source_coverage: graph.coverage.status,
total,
offset,
limit,
next_offset:
offset + matches.length < total ? offset + matches.length : null,
matches,
};
};
const traceGraph = (
input: ApplicationFeatureTraceProjectionInput,
seedMatches: readonly ApplicationFeatureSeedMatch[],
@@ -106,6 +106,15 @@ export const traceApplicationFeatureInputSchema = z
application: evidenceSchema,
native_observations: z.array(evidenceSchema).default([]),
seed: applicationFeatureSeedSchema,
semantic_page: z
.strictObject({
offset: z.int().nonnegative().default(0),
limit: z.int().positive().default(64),
})
.optional()
.describe(
"Page retained semantic label matches when a string seed has no application match. Defaults to offset 0 and limit 64.",
),
direction: z.enum(["outgoing", "incoming", "both"]).default("both"),
})
.superRefine(validateApplicationTraceIdentities);
@@ -156,6 +165,34 @@ export const applicationFeatureTraceResultSchema = z.strictObject({
seed: applicationFeatureSeedSchema,
direction: z.enum(["outgoing", "incoming", "both"]),
seed_matches: z.array(seedMatchSchema),
semantic_discovery: z
.strictObject({
source_graph_id: prefixedDigestSchema("jsrg"),
source_coverage: z.enum([
"complete",
"partial",
"unknown",
"unavailable",
]),
total: z.int().nonnegative(),
offset: z.int().nonnegative(),
limit: z.int().positive(),
next_offset: z.int().nonnegative().nullable(),
matches: z.array(
z.strictObject({
node_id: prefixedDigestSchema("jsrg_node"),
kind: z.string().min(1),
trace_seed: z.strictObject({
kind: z.literal("semantic-node"),
node_id: prefixedDigestSchema("jsrg_node"),
}),
}),
),
})
.optional()
.describe(
"On an unmatched string seed, retained semantic labels and direct string call arguments use the requested match mode and case sensitivity. Use a trace_seed with trace_javascript_semantics; source coverage limits absence claims.",
),
graph: javascriptApplicationGraphSchema.nullable(),
paths: z.array(tracePathSchema),
native_handoffs: z.array(nativeHandoffSchema),
@@ -0,0 +1,25 @@
import type { JavaScriptSemanticGraphNode } from "./javascriptSemanticGraphSchemas.js";
/** Read retained direct call literals without interpreting other node properties. */
export const semanticCallLiteralValues = (
node: JavaScriptSemanticGraphNode,
): (string | number | boolean | null)[] => {
if (node.kind !== "call-site" || !Array.isArray(node.properties.arguments))
return [];
return node.properties.arguments.flatMap((argument) => {
if (
argument === null ||
typeof argument !== "object" ||
Array.isArray(argument) ||
!Object.hasOwn(argument, "value")
)
return [];
const value = argument.value;
return value === null ||
typeof value === "string" ||
typeof value === "number" ||
typeof value === "boolean"
? [value]
: [];
});
};
@@ -12,12 +12,12 @@ import type {
import { semanticIdentifierRole } from "./javascriptSemanticProjection.js";
import { createSemanticCallableStack } from "./javascriptSemanticCallableStack.js";
import {
readExactJavaScriptLiteral,
semanticStaticPropertyKey,
unwrapJavaScriptExpression,
} from "./javascriptAstValues.js";
import {
resolveSemanticBindingState,
isUnshadowedGlobal,
type JavaScriptSemanticAnalysisState,
type JavaScriptSemanticBindingState,
} from "./javascriptSemanticState.js";
@@ -30,6 +30,7 @@ import { traverseJavaScriptValueAst } from "./javascriptSemanticTraversal.js";
import { range } from "./javascriptStaticAnalysisHelpers.js";
import { semanticCallableResultKind } from "./javascriptSemanticCallableResults.js";
import { assignedSemanticResultBindings } from "./javascriptSemanticDataEffectHelpers.js";
import { semanticPrimitiveSet } from "./javascriptSemanticPrimitives.js";
/** Local call, flow, capture, and unsupported-frontier facts. */
export interface JavaScriptSemanticCallAnalysis {
@@ -138,7 +139,7 @@ const collectCallSite = (
bindingCache: context.bindingResolutionCache,
});
const callSiteId = semanticCallSiteId(node);
const argumentsValue = retainedArguments(node.arguments);
const argumentsValue = retainedArguments(node.arguments, state);
const site: JavaScriptSemanticCallSite = {
callSiteId,
kind: t.isNewExpression(node) ? "construct" : "call",
@@ -209,6 +210,7 @@ const retainedArguments = (
| t.ArgumentPlaceholder
| null
)[],
state: JavaScriptSemanticAnalysisState,
): JavaScriptSemanticCallSite["arguments"] => {
const retained: JavaScriptSemanticCallSite["arguments"][number][] = [];
for (const [index, node] of nodes.entries()) {
@@ -223,11 +225,34 @@ const retainedArguments = (
location: range(node),
valueExpressionLocation: valueNode === null ? null : range(valueNode),
spread: t.isSpreadElement(node),
...(valueNode === null ? {} : directLiteral(valueNode, state)),
});
}
return retained;
};
/** Retain direct scalar syntax only; do not infer values through bindings. */
const directLiteral = (
node: t.Node,
state: JavaScriptSemanticAnalysisState,
): {
readonly literal?: string | number | boolean | null;
readonly literalResourceLimit?: "primitive-bytes";
} => {
const literal = readExactJavaScriptLiteral(node);
if (!literal.found) return {};
const parsed = semanticPrimitiveSet([literal.value]);
if (parsed.status === "literal") return { literal: parsed.value };
if (
parsed.status === "unknown" &&
parsed.resourceLimit === "primitive-bytes"
) {
state.valueResourceLimits.add(parsed.resourceLimit);
return { literalResourceLimit: parsed.resourceLimit };
}
return {};
};
const collectArgumentFlows = (
site: JavaScriptSemanticCallSite,
node: t.CallExpression | t.OptionalCallExpression | t.NewExpression,
@@ -138,6 +138,10 @@ export interface JavaScriptSemanticCallArgument {
/** Unwrapped value expression; null for a spread or non-expression slot. */
readonly valueExpressionLocation: JavaScriptSourceRange | null;
readonly spread: boolean;
/** Direct scalar syntax, before runtime evaluation; omitted for other expressions. */
readonly literal?: string | number | boolean | null;
/** A direct literal was omitted under a semantic resource bound. */
readonly literalResourceLimit?: "primitive-bytes";
}
/** One positional argument that may initialize one local parameter binding. */
@@ -5,6 +5,7 @@ import type { JavaScriptSemanticGraphNode } from "./javascriptSemanticGraphSchem
import { uniqueSorted } from "../canonicalOrdering.js";
import { compareUnicodeCodePoints } from "../unicodeCodePointOrder.js";
import type { JavaScriptSemanticQueryInput } from "./javascriptSemanticQuerySchemas.js";
import { semanticCallLiteralValues } from "./javascriptSemanticCallLiterals.js";
/** Derive the stable identity for one graph-bound query. */
export const javaScriptSemanticQueryIdentifier = (
@@ -74,8 +75,12 @@ const seedMatches = (
if (seed.kind === "application-node")
return node.application_node_ids.includes(seed.node_id);
if (seed.kind === "function") return fingerprintFunctions.has(node.node_id);
if (seed.kind === "literal")
return node.kind === "literal" && node.properties.value === seed.value;
if (seed.kind === "literal") {
if (node.kind === "literal") return node.properties.value === seed.value;
return semanticCallLiteralValues(node).some(
(value) => value === seed.value,
);
}
if (seed.kind === "property") {
if (node.kind === "property-slot")
return node.properties.name === seed.name;