mirror of
https://github.com/morluto/rea
synced 2026-10-27 14:42:18 -04:00
a0214e0e0e
* fix(javascript): name heap and semantic ceilings Refs #5910. Heap exhaustion names max_heap_mb with its effective value, range, and next-analysis worker restart. Semantic ceiling coverage names the four max_semantic_* arguments. Wakaru and transfer ceilings say this release has no runtime override. Co-authored-by: morluto <morluto@users.noreply.github.com> * fix(javascript): distinguish adjustable ceilings from fixed limits The effective transfer file ceiling and the parent result budget follow the CLI/MCP process heap, so recovery names NODE_OPTIONS instead of denying an override. A provenance report over 8 MiB keeps the fixed allowance guidance, and the Wakaru deadline says an unchanged retry may hit it again. Refs #5910 Co-authored-by: morluto <morluto@users.noreply.github.com> * fix(javascript): keep Bun OOM and checkpoint heap controls distinct Bun heap-exhaustion exits no longer claim the ignored max_heap_mb V8 old-space limit. Checkpoint decode names the parent heap and Node NODE_OPTIONS, the same control as the parent result budget. Refs #5910 Co-authored-by: morluto <morluto@users.noreply.github.com> * fix(javascript): name semantic ceilings without claiming both were reached Truncated semantic retention names the total and per-file controls as effective ceilings. Callers only know that retention was truncated, so the limitation no longer says both ceilings were reached. Refs #5910 Co-authored-by: morluto <morluto@users.noreply.github.com> * fix(javascript): name only the semantic ceiling that stopped retention Semantic ceiling limitations claimed both the application total and the per-file control whenever either one truncated the graph. Record which offered budget stopped node and unknown retention, and name only those controls. Coverage limits still list both effective values. Co-authored-by: morluto <morluto@users.noreply.github.com> * fix(javascript): require a value in the NODE_OPTIONS heap example A bare --max-old-space-size is an illegal V8 flag and prevents Node from starting. Parent-heap and transfer-ceiling recovery now share --max-old-space-size=<MiB>, and the Kuna heap remediation uses the same complete form. Co-authored-by: morluto <morluto@users.noreply.github.com> * fix(javascript): limit the old-space placeholder to NODE_OPTIONS recovery The review covers the CLI/MCP NODE_OPTIONS example and the transfer-ceiling copy. The Kuna heap-headroom sentence stays unchanged. Co-authored-by: morluto <morluto@users.noreply.github.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: morluto <morluto@users.noreply.github.com>
317 lines
11 KiB
TypeScript
317 lines
11 KiB
TypeScript
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" },
|
|
{ name: "max_semantic_unknowns", value: 2, unit: "items" },
|
|
{ name: "max_semantic_unknowns_per_file", value: 2, unit: "items" },
|
|
]),
|
|
);
|
|
expect(graph.limitations.join("\n")).toContain("max_semantic_unknowns is 2");
|
|
expect(graph.limitations.join("\n")).toContain(
|
|
"max_semantic_unknowns_per_file is 2",
|
|
);
|
|
expect(graph.limitations.join("\n")).toContain(
|
|
"Omission coverage counts examined emission occurrences",
|
|
);
|
|
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);
|
|
expect(graph.limitations.join("\n")).toContain("max_semantic_unknowns is 3");
|
|
expect(graph.limitations.join("\n")).toContain(
|
|
"max_semantic_unknowns_per_file is 2",
|
|
);
|
|
expect(graph.coverage.limits).toEqual(
|
|
expect.arrayContaining([
|
|
{ name: "semantic_graph_unknown_ceiling", value: 3, unit: "items" },
|
|
{ name: "semantic_graph_file_unknown_ceiling", value: 2, unit: "items" },
|
|
{ name: "max_semantic_unknowns", value: 3, unit: "items" },
|
|
{ name: "max_semantic_unknowns_per_file", value: 2, unit: "items" },
|
|
]),
|
|
);
|
|
});
|
|
|
|
it("names only the unknown ceiling that stopped retention", () => {
|
|
const perFile = createJavaScriptSemanticGraphProjection({
|
|
totalUnknowns: 50,
|
|
fileUnknowns: 2,
|
|
});
|
|
perFile.projectFile(source(), analyzeJavaScriptSemantics(text));
|
|
const perFileGraph = perFile.finish(digest, application);
|
|
expect(perFileGraph.limitations.join("\n")).toContain(
|
|
"max_semantic_unknowns_per_file is 2",
|
|
);
|
|
expect(perFileGraph.limitations.join("\n")).not.toContain(
|
|
"max_semantic_unknowns is 50",
|
|
);
|
|
expect(perFileGraph.coverage.limits).toEqual(
|
|
expect.arrayContaining([
|
|
{ name: "semantic_graph_unknown_ceiling", value: 50, unit: "items" },
|
|
{ name: "semantic_graph_file_unknown_ceiling", value: 2, unit: "items" },
|
|
{ name: "max_semantic_unknowns", value: 50, unit: "items" },
|
|
{ name: "max_semantic_unknowns_per_file", value: 2, unit: "items" },
|
|
]),
|
|
);
|
|
|
|
const total = createJavaScriptSemanticGraphProjection({
|
|
totalUnknowns: 2,
|
|
fileUnknowns: 50,
|
|
});
|
|
total.projectFile(source(), analyzeJavaScriptSemantics(text));
|
|
const totalLimitation = total
|
|
.finish(digest, application)
|
|
.limitations.join("\n");
|
|
expect(totalLimitation).toContain("max_semantic_unknowns is 2");
|
|
expect(totalLimitation).not.toContain("max_semantic_unknowns_per_file is 50");
|
|
|
|
const unavailable = createJavaScriptSemanticGraphProjection({
|
|
totalUnknowns: 20,
|
|
fileUnknowns: 2,
|
|
});
|
|
unavailable.recordUnavailableFile(
|
|
source(),
|
|
"Source unavailable",
|
|
null,
|
|
"incomplete-module",
|
|
);
|
|
const unavailableLimitation = unavailable
|
|
.finish(digest, application)
|
|
.limitations.join("\n");
|
|
expect(unavailableLimitation).toContain(
|
|
"max_semantic_unknowns_per_file is 2",
|
|
);
|
|
expect(unavailableLimitation).not.toContain("max_semantic_unknowns is 20");
|
|
});
|
|
|
|
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),
|
|
);
|
|
});
|