mirror of
https://github.com/morluto/rea.git
synced 2026-10-29 10:55:57 +00:00
fix(javascript): name heap and semantic ceilings (#6022)
* 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>
This commit is contained in:
22 files changed
+1166
-87
No files matched your search
@@ -1,5 +1,18 @@
|
||||
import { expect, it } from "vitest";
|
||||
import { javaScriptAnalysisResourceControlsSchema } from "./javascriptAnalysisResourceControls.js";
|
||||
import { AnalysisResourceConstraintError } from "../analysisErrorCore.js";
|
||||
import { projectAnalysisError } from "../analysisErrorProjection.js";
|
||||
import {
|
||||
JAVASCRIPT_ANALYSIS_RESOURCE_DEFAULTS,
|
||||
JAVASCRIPT_PARENT_CHECKPOINT_HEAP_REMEDIATION,
|
||||
JAVASCRIPT_PARENT_RESULT_BUDGET_REMEDIATION,
|
||||
JAVASCRIPT_SEMANTIC_ITEM_MAXIMUM,
|
||||
JAVASCRIPT_WORKER_HEAP_MIB_MAXIMUM,
|
||||
JAVASCRIPT_WORKER_HEAP_MIB_MINIMUM,
|
||||
javaScriptAnalysisResourceControlsSchema,
|
||||
javaScriptWorkerHeapExhaustionRecovery,
|
||||
javaScriptSemanticCeilingCoverage,
|
||||
javaScriptWorkerHeapRemediation,
|
||||
} from "./javascriptAnalysisResourceControls.js";
|
||||
|
||||
it("keeps omitted operational choices absent and validates explicit semantic budgets", () => {
|
||||
expect(javaScriptAnalysisResourceControlsSchema.parse({})).toEqual({});
|
||||
@@ -29,3 +42,183 @@ it("keeps omitted operational choices absent and validates explicit semantic bud
|
||||
).toBe(false);
|
||||
}
|
||||
});
|
||||
|
||||
it("names max_heap_mb, its range, and the budgets it does not raise", () => {
|
||||
const heapMb = JAVASCRIPT_ANALYSIS_RESOURCE_DEFAULTS.heapMb;
|
||||
const error = new AnalysisResourceConstraintError(
|
||||
"analyze_javascript_application",
|
||||
"memory",
|
||||
`Owned JavaScript worker exhausted its ${String(heapMb)} MiB V8 old-space heap (max_heap_mb) while analyzing app.js during static`,
|
||||
{ worker_old_space_mb: heapMb },
|
||||
{ remediationAction: javaScriptWorkerHeapRemediation(heapMb) },
|
||||
);
|
||||
const projected = projectAnalysisError(error);
|
||||
expect(projected.message).toContain(`max_heap_mb`);
|
||||
expect(projected.message).toContain(`${String(heapMb)} MiB`);
|
||||
expect(projected.remediation.action).toContain("max_heap_mb");
|
||||
expect(projected.remediation.action).toContain(
|
||||
String(JAVASCRIPT_WORKER_HEAP_MIB_MINIMUM),
|
||||
);
|
||||
expect(projected.remediation.action).toContain(
|
||||
String(JAVASCRIPT_WORKER_HEAP_MIB_MAXIMUM),
|
||||
);
|
||||
expect(projected.remediation.action).toContain("--max-heap-mb");
|
||||
expect(projected.remediation.action).toContain("next analysis");
|
||||
expect(projected.remediation.action).toContain("new worker");
|
||||
expect(projected.remediation.action).toContain("parent result budget");
|
||||
expect(projected.remediation.action).toContain("transfer");
|
||||
expect(projected.remediation.action).not.toMatch(/available host memory/u);
|
||||
const maximum = projectAnalysisError(
|
||||
new AnalysisResourceConstraintError(
|
||||
"analyze_javascript_application",
|
||||
"memory",
|
||||
"heap",
|
||||
null,
|
||||
{
|
||||
remediationAction: javaScriptWorkerHeapRemediation(
|
||||
JAVASCRIPT_WORKER_HEAP_MIB_MAXIMUM,
|
||||
),
|
||||
},
|
||||
),
|
||||
);
|
||||
expect(maximum.remediation.action).toContain("Do not increase it further");
|
||||
});
|
||||
|
||||
it("names the parent heap control for the parent result budget", () => {
|
||||
expect(JAVASCRIPT_PARENT_RESULT_BUDGET_REMEDIATION).toContain(
|
||||
"parent result budget",
|
||||
);
|
||||
expect(JAVASCRIPT_PARENT_RESULT_BUDGET_REMEDIATION).toContain("NODE_OPTIONS");
|
||||
expect(JAVASCRIPT_PARENT_RESULT_BUDGET_REMEDIATION).toContain(
|
||||
"--max-old-space-size=<MiB>",
|
||||
);
|
||||
expect(JAVASCRIPT_PARENT_RESULT_BUDGET_REMEDIATION).toContain("max_heap_mb");
|
||||
expect(JAVASCRIPT_PARENT_RESULT_BUDGET_REMEDIATION).not.toContain(
|
||||
"no runtime override",
|
||||
);
|
||||
});
|
||||
|
||||
it("names the parent heap control for checkpoint decode", () => {
|
||||
expect(JAVASCRIPT_PARENT_CHECKPOINT_HEAP_REMEDIATION).toContain(
|
||||
"parent process heap",
|
||||
);
|
||||
expect(JAVASCRIPT_PARENT_CHECKPOINT_HEAP_REMEDIATION).toContain(
|
||||
"NODE_OPTIONS",
|
||||
);
|
||||
expect(JAVASCRIPT_PARENT_CHECKPOINT_HEAP_REMEDIATION).toContain(
|
||||
"--max-old-space-size=<MiB>",
|
||||
);
|
||||
expect(JAVASCRIPT_PARENT_CHECKPOINT_HEAP_REMEDIATION).toContain(
|
||||
"admit this checkpoint",
|
||||
);
|
||||
expect(JAVASCRIPT_PARENT_CHECKPOINT_HEAP_REMEDIATION).toContain(
|
||||
"Increasing max_heap_mb does not raise it",
|
||||
);
|
||||
});
|
||||
|
||||
it("attributes V8 heap exhaustion to max_heap_mb only when that flag is enforced", () => {
|
||||
const node = javaScriptWorkerHeapExhaustionRecovery({
|
||||
heapFlagsEnforced: true,
|
||||
heapMb: 1024,
|
||||
path: "app.js",
|
||||
phase: "static",
|
||||
});
|
||||
expect(node.reason).toContain(
|
||||
"exhausted its 1024 MiB V8 old-space heap (max_heap_mb)",
|
||||
);
|
||||
expect(node.remediationAction).toContain("max_heap_mb");
|
||||
expect(node.remediationAction).toContain("--max-heap-mb");
|
||||
const bun = javaScriptWorkerHeapExhaustionRecovery({
|
||||
heapFlagsEnforced: false,
|
||||
heapMb: 1024,
|
||||
path: "app.js",
|
||||
phase: "static",
|
||||
});
|
||||
expect(bun.reason).toContain("Bun ignores the V8 old-space flag");
|
||||
expect(bun.reason).not.toContain(
|
||||
"exhausted its 1024 MiB V8 old-space heap (max_heap_mb)",
|
||||
);
|
||||
expect(bun.remediationAction).toContain("Bun ignores V8 heap flags");
|
||||
expect(bun.remediationAction).not.toContain("Increase max_heap_mb");
|
||||
});
|
||||
|
||||
it("names the four semantic arguments on ceiling coverage", () => {
|
||||
const both = { total: true, perFile: true };
|
||||
const nodes = javaScriptSemanticCeilingCoverage("nodes", 50, 30, both);
|
||||
const unknowns = javaScriptSemanticCeilingCoverage("unknowns", 3, 2, both);
|
||||
const perFileOnly = javaScriptSemanticCeilingCoverage("nodes", 50, 30, {
|
||||
total: false,
|
||||
perFile: true,
|
||||
});
|
||||
const totalOnly = javaScriptSemanticCeilingCoverage("unknowns", 3, 2, {
|
||||
total: true,
|
||||
perFile: false,
|
||||
});
|
||||
expect(nodes.limits).toEqual(perFileOnly.limits);
|
||||
expect(nodes.limits).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ name: "semantic_graph_node_ceiling", value: 50, unit: "items" },
|
||||
{ name: "semantic_graph_file_node_ceiling", value: 30, unit: "items" },
|
||||
{ name: "max_semantic_nodes", value: 50, unit: "items" },
|
||||
{ name: "max_semantic_nodes_per_file", value: 30, unit: "items" },
|
||||
]),
|
||||
);
|
||||
expect(unknowns.limits).toEqual(totalOnly.limits);
|
||||
expect(unknowns.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" },
|
||||
]),
|
||||
);
|
||||
expect(nodes.limitation).toContain("1 to");
|
||||
expect(nodes.limitation).toContain(String(JAVASCRIPT_SEMANTIC_ITEM_MAXIMUM));
|
||||
expect(nodes.limitation).toContain(
|
||||
"CLI: --max-semantic-nodes and --max-semantic-nodes-per-file",
|
||||
);
|
||||
expect(nodes.limitation).toContain(
|
||||
"Set max_semantic_nodes and max_semantic_nodes_per_file",
|
||||
);
|
||||
expect(nodes.limitation).toContain("Raising them does not raise");
|
||||
expect(nodes.limitation).toContain("next analysis");
|
||||
expect(unknowns.limitation).toContain(
|
||||
"CLI: --max-semantic-unknowns and --max-semantic-unknowns-per-file",
|
||||
);
|
||||
expect(unknowns.limitation).toContain("max_semantic_nodes");
|
||||
expect(perFileOnly.limitation).toContain("max_semantic_nodes_per_file is 30");
|
||||
expect(perFileOnly.limitation).toContain(
|
||||
"Set max_semantic_nodes_per_file on the next",
|
||||
);
|
||||
expect(perFileOnly.limitation).toContain(
|
||||
"CLI: --max-semantic-nodes-per-file",
|
||||
);
|
||||
expect(perFileOnly.limitation).toContain("Raising it does not raise");
|
||||
expect(perFileOnly.limitation).not.toContain("max_semantic_nodes is 50");
|
||||
expect(perFileOnly.limitation).not.toContain("--max-semantic-nodes and");
|
||||
expect(totalOnly.limitation).toContain("max_semantic_unknowns is 3");
|
||||
expect(totalOnly.limitation).toContain(
|
||||
"Set max_semantic_unknowns on the next",
|
||||
);
|
||||
expect(totalOnly.limitation).toContain("CLI: --max-semantic-unknowns)");
|
||||
expect(totalOnly.limitation).not.toContain(
|
||||
"max_semantic_unknowns_per_file is 2",
|
||||
);
|
||||
expect(totalOnly.limitation).not.toContain(
|
||||
"--max-semantic-unknowns-per-file",
|
||||
);
|
||||
const capped = javaScriptSemanticCeilingCoverage(
|
||||
"nodes",
|
||||
JAVASCRIPT_SEMANTIC_ITEM_MAXIMUM,
|
||||
30,
|
||||
{ total: true, perFile: false },
|
||||
);
|
||||
expect(capped.limitation).toContain("Do not increase it further");
|
||||
expect(capped.limitation).not.toContain("max_semantic_nodes_per_file is 30");
|
||||
expect(() =>
|
||||
javaScriptSemanticCeilingCoverage("nodes", 50, 30, {
|
||||
total: false,
|
||||
perFile: false,
|
||||
}),
|
||||
).toThrow("ceiling that stopped retention");
|
||||
});
|
||||
Reference in new issue
Block a user