perf(javascript): count candidate relevance without a second adjacency
Scan eligible candidate relations directly for query ambiguity instead of allocating and sorting another traversal index. Preserve direction, relation filters and cycle handling. Semantic graph and builder suites plus public application workflow CLI tests passed. Full query outputs matched on a generated 20000-node graph; the single repeated-query timing run is indicative only.
This commit is contained in:
parent
bb764d26bd
commit
dce81ca400
|
|
@ -4,6 +4,7 @@ import { z } from "zod";
|
|||
import { digestCanonicalValue } from "../canonicalDigest.js";
|
||||
import type { ApplicationGraphEvidence } from "./javascriptApplicationEvidenceSchemas.js";
|
||||
import {
|
||||
JAVASCRIPT_SEMANTIC_RELATION_FAMILY,
|
||||
JAVASCRIPT_SEMANTIC_NODE_KINDS,
|
||||
JAVASCRIPT_SEMANTIC_RELATION_FAMILIES,
|
||||
JAVASCRIPT_SEMANTIC_RELATIONS,
|
||||
|
|
@ -27,6 +28,7 @@ import {
|
|||
parseJavaScriptSemanticGraph,
|
||||
serializeJavaScriptSemanticGraph,
|
||||
} from "./javascriptSemanticGraphSerialization.js";
|
||||
import { queryJavaScriptSemanticGraph } from "./javascriptSemanticQuery.js";
|
||||
|
||||
const SHA = "a".repeat(64);
|
||||
const JAG_ID = `jag_${"b".repeat(64)}`;
|
||||
|
|
@ -323,6 +325,104 @@ const registryForGraph = (
|
|||
return registry;
|
||||
};
|
||||
|
||||
const graphWithCandidateRelations = (): JavaScriptSemanticGraph => {
|
||||
const base = fixtureGraph();
|
||||
const evidenceContexts = registryForGraph(base);
|
||||
const relations = base.relations.map(
|
||||
({ relation_id: _relationId, ...relation }) =>
|
||||
createJavaScriptSemanticGraphRelation(
|
||||
{
|
||||
...relation,
|
||||
resolution: "candidate",
|
||||
evidence: resolveJavaScriptSemanticEvidence(base, relation.evidence),
|
||||
},
|
||||
evidenceContexts,
|
||||
),
|
||||
);
|
||||
const binding = base.nodes.find(({ label }) => label === "binding");
|
||||
const callable = base.nodes.find(({ label }) => label === "function");
|
||||
if (binding === undefined || callable === undefined)
|
||||
throw new Error("Expected candidate-cycle endpoints");
|
||||
for (const [source, target] of [
|
||||
[binding, callable],
|
||||
[callable, binding],
|
||||
] as const)
|
||||
relations.push(
|
||||
createJavaScriptSemanticGraphRelation(
|
||||
{
|
||||
source_node_id: source.node_id,
|
||||
target_node_id: target.node_id,
|
||||
relation: "owns",
|
||||
resolution: "candidate",
|
||||
properties: {},
|
||||
evidence: evidence("inferred"),
|
||||
},
|
||||
evidenceContexts,
|
||||
),
|
||||
);
|
||||
|
||||
const { graph_id: _graphId, ...input } = base;
|
||||
return createJavaScriptSemanticGraph({
|
||||
...input,
|
||||
evidence_contexts: evidenceContexts.contexts,
|
||||
relations,
|
||||
coverage: {
|
||||
...input.coverage,
|
||||
families: input.coverage.families.map((family) => ({
|
||||
...family,
|
||||
retained_relations: relations.filter(
|
||||
({ relation }) =>
|
||||
JAVASCRIPT_SEMANTIC_RELATION_FAMILY[relation] === family.family,
|
||||
).length,
|
||||
})),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
it("counts candidate edges by query direction and relation filter", () => {
|
||||
const graph = graphWithCandidateRelations();
|
||||
const binding = graph.nodes.find(({ label }) => label === "binding");
|
||||
if (binding === undefined) throw new Error("Expected binding seed");
|
||||
const query = (
|
||||
direction: "forward-influence" | "backward-provenance",
|
||||
allowedRelations: (typeof JAVASCRIPT_SEMANTIC_RELATIONS)[number][],
|
||||
) =>
|
||||
queryJavaScriptSemanticGraph(graph, {
|
||||
seed: { kind: "semantic-node", node_id: binding.node_id },
|
||||
direction,
|
||||
allowed_relations: allowedRelations,
|
||||
});
|
||||
|
||||
expect(query("forward-influence", ["captures"]).status).toBe("ambiguous");
|
||||
expect(query("forward-influence", ["defines"]).status).toBe("found");
|
||||
expect(query("backward-provenance", ["defines"]).status).toBe("ambiguous");
|
||||
expect(query("backward-provenance", ["captures"]).status).toBe("found");
|
||||
});
|
||||
|
||||
it("counts candidate ownership cycles once and traverses them finitely", () => {
|
||||
const graph = graphWithCandidateRelations();
|
||||
const binding = graph.nodes.find(({ label }) => label === "binding");
|
||||
if (binding === undefined) throw new Error("Expected binding seed");
|
||||
const result = queryJavaScriptSemanticGraph(graph, {
|
||||
seed: { kind: "semantic-node", node_id: binding.node_id },
|
||||
direction: "ownership",
|
||||
allowed_relations: ["owns"],
|
||||
include_ambiguous_dynamic_edges: true,
|
||||
});
|
||||
|
||||
expect(result.status).toBe("ambiguous");
|
||||
expect(result.relations).toHaveLength(2);
|
||||
expect(result.summary.traversed_nodes).toBe(2);
|
||||
expect(result.summary.traversed_relations).toBe(2);
|
||||
expect(
|
||||
queryJavaScriptSemanticGraph(graph, {
|
||||
seed: { kind: "semantic-node", node_id: binding.node_id },
|
||||
direction: "ownership",
|
||||
allowed_relations: ["constructs-request"],
|
||||
}).status,
|
||||
).toBe("found");
|
||||
});
|
||||
|
||||
it("defines every required relation family and canonicalizes records", () => {
|
||||
expect(JAVASCRIPT_SEMANTIC_RELATIONS).toContain("argument-to-parameter");
|
||||
expect(JAVASCRIPT_SEMANTIC_RELATIONS).toContain("detaches-task");
|
||||
|
|
|
|||
|
|
@ -283,15 +283,32 @@ const relevantCandidateRelationCount = (
|
|||
nodeIds: ReadonlySet<string>,
|
||||
input: JavaScriptSemanticQueryInput,
|
||||
): number => {
|
||||
const adjacency = buildAdjacency(
|
||||
graph.relations.filter(({ resolution }) => resolution === "candidate"),
|
||||
{ ...input, include_ambiguous_dynamic_edges: true },
|
||||
);
|
||||
const relevant = new Set<string>();
|
||||
for (const nodeId of nodeIds)
|
||||
for (const { relation } of adjacency.get(nodeId) ?? [])
|
||||
relevant.add(relation.relation_id);
|
||||
return relevant.size;
|
||||
const allowed =
|
||||
input.allowed_relations === undefined
|
||||
? null
|
||||
: new Set(input.allowed_relations);
|
||||
let relevant = 0;
|
||||
for (const relation of graph.relations) {
|
||||
if (
|
||||
relation.resolution !== "candidate" ||
|
||||
(allowed !== null && !allowed.has(relation.relation))
|
||||
)
|
||||
continue;
|
||||
const sourceIsRelevant = nodeIds.has(relation.source_node_id);
|
||||
const targetIsRelevant = nodeIds.has(relation.target_node_id);
|
||||
if (
|
||||
(input.direction === "callers" &&
|
||||
relation.relation === "calls" &&
|
||||
targetIsRelevant) ||
|
||||
(input.direction === "ownership" &&
|
||||
ownershipRelation(relation.relation) &&
|
||||
(sourceIsRelevant || targetIsRelevant)) ||
|
||||
(input.direction === "backward-provenance" && targetIsRelevant) ||
|
||||
(input.direction === "forward-influence" && sourceIsRelevant)
|
||||
)
|
||||
relevant += 1;
|
||||
}
|
||||
return relevant;
|
||||
};
|
||||
|
||||
const expectedMatchesFor = (
|
||||
|
|
|
|||
Loading…
Reference in New Issue