Files
reverse-engineering-anything/src/domain/javascript/javascriptAnalysisResourceControls.test.ts
T
a0214e0e0e 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>
2026-10-22 02:44:14 +08:00

225 lines
8.6 KiB
TypeScript

import { expect, it } from "vitest";
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({});
expect(
javaScriptAnalysisResourceControlsSchema.parse({
max_semantic_nodes: 750_000,
max_semantic_nodes_per_file: 700_000,
max_semantic_unknowns: 500_000,
max_semantic_unknowns_per_file: 400_000,
}),
).toEqual({
max_semantic_nodes: 750_000,
max_semantic_nodes_per_file: 700_000,
max_semantic_unknowns: 500_000,
max_semantic_unknowns_per_file: 400_000,
});
for (const value of [0, -1, 1.5, 1_000_001, Infinity, NaN]) {
for (const key of [
"max_semantic_nodes",
"max_semantic_nodes_per_file",
"max_semantic_unknowns",
"max_semantic_unknowns_per_file",
])
expect(
javaScriptAnalysisResourceControlsSchema.safeParse({ [key]: value })
.success,
).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");
});