mirror of
https://github.com/morluto/rea
synced 2026-10-27 22:45:19 -04:00
fix(javascript): bound semantic unknown retention at projection
Admit unknowns before canonical construction and provenance interning. Offer independent per-file and application capacities through CLI, Evidence and the owned analysis worker, and reject over-allocation during streamed decoding. Report examined omitted emission occurrences by family and reason without retaining discarded identities. Preserve omission-free graph identities and distinguish node truncation from overall partial coverage. Fixes #4797
This commit is contained in:
@@ -0,0 +1,243 @@
|
||||
import { createHash } from "node:crypto";
|
||||
import { expect, it } from "vitest";
|
||||
import type { JavaScriptArtifactFile } from "../../domain/javascript/javascriptArtifactFiles.js";
|
||||
import { analyzeJavaScriptSemantics } from "../../domain/javascript/javascriptSemanticAnalysis.js";
|
||||
import {
|
||||
javaScriptSemanticGraphSchema,
|
||||
createJavaScriptSemanticGraph,
|
||||
} from "../../domain/javascript/javascriptSemanticGraph.js";
|
||||
import { queryJavaScriptSemanticGraph } from "../../domain/javascript/javascriptSemanticQuery.js";
|
||||
import {
|
||||
createJavaScriptSemanticGraphProjection,
|
||||
projectJavaScriptSemanticFileSteps,
|
||||
} from "./JavaScriptSemanticGraphBuilder.js";
|
||||
import {
|
||||
createSemanticGraphProjectionState,
|
||||
addSemanticGraphUnknown,
|
||||
} from "./JavaScriptSemanticGraphConstruction.js";
|
||||
import { unknownSemanticEvidence } from "./JavaScriptSemanticGraphEvidence.js";
|
||||
|
||||
const digest = "a".repeat(64);
|
||||
const application = { graph_id: `jag_${"b".repeat(64)}`, nodes: [] };
|
||||
const text = "globalThis.u.p(globalThis.v[k]);\n".repeat(4);
|
||||
const source = (path = "app.js", value = text): JavaScriptArtifactFile => ({
|
||||
path,
|
||||
sha256: createHash("sha256").update(value).digest("hex"),
|
||||
container_sha256: digest,
|
||||
bytes: Buffer.byteLength(value),
|
||||
inventory_artifact_id: `art_${digest}`,
|
||||
kind: "javascript",
|
||||
unpacked: false,
|
||||
text: { included: true, value },
|
||||
});
|
||||
const fileProjection = (file = source(), nodes = 200, unknowns = 200) => {
|
||||
if (!file.text.included) throw new Error("Expected inert source");
|
||||
const steps = projectJavaScriptSemanticFileSteps(
|
||||
file,
|
||||
analyzeJavaScriptSemantics(file.text.value),
|
||||
{ nodes, unknowns },
|
||||
);
|
||||
let step = steps.next();
|
||||
while (!step.done) step = steps.next();
|
||||
return step.value;
|
||||
};
|
||||
|
||||
it("bounds unknowns independently of retained nodes and accounts for examined omissions", () => {
|
||||
const complete = fileProjection();
|
||||
const bounded = fileProjection(source(), 200, 2);
|
||||
expect(complete.unknowns.length).toBeGreaterThan(2);
|
||||
expect(bounded.nodes).toEqual(complete.nodes);
|
||||
expect(bounded.relations).toEqual(complete.relations);
|
||||
expect(bounded.unknowns).toHaveLength(2);
|
||||
expect(
|
||||
bounded.omittedUnknownOccurrences!.reduce(
|
||||
(sum, row) => sum + row.occurrences,
|
||||
0,
|
||||
),
|
||||
).toBe(complete.unknowns.length - 2);
|
||||
const projection = createJavaScriptSemanticGraphProjection({
|
||||
totalUnknowns: 2,
|
||||
fileUnknowns: 2,
|
||||
});
|
||||
projection.adoptFileProjection(bounded);
|
||||
const graph = projection.finish(digest, application);
|
||||
expect(graph.coverage).toMatchObject({
|
||||
truncated: true,
|
||||
status: "partial",
|
||||
omitted_nodes: 0,
|
||||
});
|
||||
expect(graph.coverage.limits).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ name: "semantic_graph_unknown_ceiling", value: 2, unit: "items" },
|
||||
{ name: "semantic_graph_file_unknown_ceiling", value: 2, unit: "items" },
|
||||
]),
|
||||
);
|
||||
expect(javaScriptSemanticGraphSchema.parse(graph)).toEqual(graph);
|
||||
expect(
|
||||
queryJavaScriptSemanticGraph(graph, {
|
||||
seed: { kind: "property", name: "missing" },
|
||||
direction: "backward-provenance",
|
||||
}),
|
||||
).toMatchObject({ status: "partial", coverage: { status: "partial" } });
|
||||
});
|
||||
|
||||
it("keeps unknowns bounded even after their semantic nodes are dropped", () => {
|
||||
const projection = fileProjection(source(), 1, 2);
|
||||
expect(projection.nodesTruncated).toBe(true);
|
||||
expect(projection.nodes).toHaveLength(1);
|
||||
expect(projection.unknowns).toHaveLength(2);
|
||||
expect(projection.omittedUnknownOccurrences!.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("enforces per-file and total unknown capacities across transferred files", () => {
|
||||
const projection = createJavaScriptSemanticGraphProjection({
|
||||
totalUnknowns: 3,
|
||||
fileUnknowns: 2,
|
||||
});
|
||||
for (const path of ["a.js", "b.js", "c.js"]) {
|
||||
const capacity = projection.remainingFileCapacity();
|
||||
const file = source(path);
|
||||
const steps = projectJavaScriptSemanticFileSteps(
|
||||
file,
|
||||
analyzeJavaScriptSemantics(text),
|
||||
capacity,
|
||||
);
|
||||
let step = steps.next();
|
||||
while (!step.done) step = steps.next();
|
||||
projection.adoptFileProjection(step.value);
|
||||
}
|
||||
expect(projection.remainingFileCapacity().unknowns).toBe(0);
|
||||
const graph = projection.finish(digest, application);
|
||||
expect(graph.unknowns).toHaveLength(3);
|
||||
expect(
|
||||
graph.unknowns.filter(
|
||||
(row) =>
|
||||
row.evidence.location.available &&
|
||||
row.evidence.location.value.kind === "source-range" &&
|
||||
row.evidence.location.value.source === "a.js",
|
||||
),
|
||||
).toHaveLength(2);
|
||||
expect(
|
||||
graph.coverage.omitted_unknown_occurrences!.reduce(
|
||||
(sum, row) => sum + row.occurrences,
|
||||
0,
|
||||
),
|
||||
).toBe(fileProjection().unknowns.length * 3 - 3);
|
||||
});
|
||||
|
||||
it("rejects excess transferred unknowns before adopting any file facts", () => {
|
||||
const projection = createJavaScriptSemanticGraphProjection({
|
||||
totalUnknowns: 1,
|
||||
fileUnknowns: 1,
|
||||
});
|
||||
const before = projection.remainingFileCapacity();
|
||||
expect(() => projection.adoptFileProjection(fileProjection())).toThrow(
|
||||
"Transferred semantic unknowns exceed",
|
||||
);
|
||||
expect(projection.remainingFileCapacity()).toEqual(before);
|
||||
});
|
||||
|
||||
it("bounds unavailable-file frontiers through the same owner", () => {
|
||||
const projection = createJavaScriptSemanticGraphProjection({
|
||||
totalUnknowns: 2,
|
||||
fileUnknowns: 2,
|
||||
});
|
||||
projection.recordUnavailableFile(
|
||||
source(),
|
||||
"Source unavailable",
|
||||
null,
|
||||
"incomplete-module",
|
||||
);
|
||||
const graph = projection.finish(digest, application);
|
||||
expect(graph.unknowns).toHaveLength(2);
|
||||
expect(
|
||||
graph.coverage.omitted_unknown_occurrences!.reduce(
|
||||
(sum, row) => sum + row.occurrences,
|
||||
0,
|
||||
),
|
||||
).toBe(10);
|
||||
});
|
||||
|
||||
it("does not intern omitted provenance and counts repeated omitted emissions as occurrences", () => {
|
||||
const state = createSemanticGraphProjectionState();
|
||||
state.unknownCapacity = 1;
|
||||
const file = source();
|
||||
for (const detail of ["retained", "omitted", "omitted"])
|
||||
addSemanticGraphUnknown(state, {
|
||||
node_id: null,
|
||||
family: "data-flow",
|
||||
relation_kinds: ["defines"],
|
||||
reason: "unknown-value",
|
||||
detail,
|
||||
candidate_node_ids: [],
|
||||
evidence: {
|
||||
...unknownSemanticEvidence(file, null),
|
||||
limitations: [detail],
|
||||
},
|
||||
});
|
||||
expect(state.unknowns.size).toBe(1);
|
||||
expect(state.evidenceContexts.contexts).toHaveLength(1);
|
||||
expect([...state.unknownOmissions.values()]).toEqual([
|
||||
{ family: "data-flow", reason: "unknown-value", occurrences: 2 },
|
||||
]);
|
||||
});
|
||||
|
||||
it("preserves omission-free graph identity and refuses contradictory omission coverage", () => {
|
||||
const projection = createJavaScriptSemanticGraphProjection();
|
||||
projection.adoptFileProjection(fileProjection());
|
||||
const graph = projection.finish(digest, application);
|
||||
expect(Object.hasOwn(graph.coverage, "omitted_unknown_occurrences")).toBe(
|
||||
false,
|
||||
);
|
||||
const { graph_id: _id, ...input } = graph;
|
||||
expect(createJavaScriptSemanticGraph(input).graph_id).toBe(graph.graph_id);
|
||||
const omission = {
|
||||
family: "data-flow",
|
||||
reason: "unknown-value",
|
||||
occurrences: 1,
|
||||
};
|
||||
expect(() =>
|
||||
createJavaScriptSemanticGraph({
|
||||
...input,
|
||||
coverage: { ...graph.coverage, omitted_unknown_occurrences: [omission] },
|
||||
}),
|
||||
).toThrow("Omitted unknown occurrences require");
|
||||
expect(() =>
|
||||
createJavaScriptSemanticGraph({
|
||||
...input,
|
||||
coverage: {
|
||||
...graph.coverage,
|
||||
status: "partial",
|
||||
truncated: true,
|
||||
limits: [{ name: "authored", value: 1, unit: "items" }],
|
||||
omitted_unknown_occurrences: [omission, omission],
|
||||
},
|
||||
}),
|
||||
).toThrow("Unknown omission coverage");
|
||||
});
|
||||
|
||||
it("applies the same unknown admission in direct and transferred construction", () => {
|
||||
const direct = createJavaScriptSemanticGraphProjection({
|
||||
totalUnknowns: 3,
|
||||
fileUnknowns: 2,
|
||||
});
|
||||
const transferred = createJavaScriptSemanticGraphProjection({
|
||||
totalUnknowns: 3,
|
||||
fileUnknowns: 2,
|
||||
});
|
||||
for (const name of ["a.js", "b.js"]) {
|
||||
const file = source(name);
|
||||
direct.projectFile(file, analyzeJavaScriptSemantics(text));
|
||||
const steps = projectJavaScriptSemanticFileSteps(
|
||||
file,
|
||||
analyzeJavaScriptSemantics(text),
|
||||
transferred.remainingFileCapacity(),
|
||||
);
|
||||
let step = steps.next();
|
||||
while (!step.done) step = steps.next();
|
||||
transferred.adoptFileProjection(step.value);
|
||||
}
|
||||
expect(direct.finish(digest, application)).toEqual(
|
||||
transferred.finish(digest, application),
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user