mirror of
https://github.com/morluto/rea
synced 2026-10-26 22:28:27 -04:00
96a46ada0a
* fix(javascript): bound semantic node projection for resource safety analyze_javascript_application exhausted the heap or failed serialization on ordinary inputs. A small extracted tree crashed outright, and a larger application tree failed at both the default heap and an 8 GB heap. Cause: the semantic projection emits one node per AST expression, call site, binding and property slot, with no ceiling. A five-file tree already yields on the order of 170,000 nodes, and the whole result is serialized after the object graph is built and validated. Result ceilings were deliberately removed upstream, so nothing bounded this growth. Apply two resource-safety ceilings and report them through the graph's own coverage contract rather than dropping data silently: - SEMANTIC_GRAPH_NODE_CEILING bounds retained nodes per tree (100,000, roughly 300 MB serialized, below V8's 512 MB string limit). - SEMANTIC_GRAPH_FILE_NODE_CEILING bounds each source file (20,000) so one bundled library cannot starve the application's own modules. Exhausting either marks coverage truncated with a partial status and records the ceiling in coverage.limits. A small application stays complete and untruncated. Verified: a previously-crashing application tree now returns Evidence in under a minute, while a small tree stays complete. Existing tests and check:fast pass. * fix(javascript): report semantic graph truncation accurately --------- Co-authored-by: REA Contributor <contributor@users.noreply.github.com> Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
158 lines
5.6 KiB
TypeScript
158 lines
5.6 KiB
TypeScript
import { writeFile } from "node:fs/promises";
|
|
import { join } from "node:path";
|
|
|
|
import { describe, expect, it } from "vitest";
|
|
import { z } from "zod";
|
|
|
|
import { createTestTempDirectory } from "../../../tests/fixtures/temporaryDirectory.js";
|
|
import { projectAnalysisError } from "../../domain/analysisErrorProjection.js";
|
|
import { analyzeJavaScriptApplication } from "./JavaScriptApplicationService.js";
|
|
|
|
describe("JavaScript application failure diagnostics", () => {
|
|
it("identifies the rejected result field in caller-visible diagnostics", async () => {
|
|
const inputPath = await createTestTempDirectory("rea-js-schema-failure-");
|
|
await writeFile(join(inputPath, "main.js"), "export const value = 1;\n");
|
|
const cause = new z.ZodError([
|
|
{
|
|
code: "custom",
|
|
path: ["semantic_graph", "nodes", 42, "application_node_ids", 0],
|
|
message: "Semantic node references an absent application node",
|
|
},
|
|
]);
|
|
const result = await analyzeJavaScriptApplication(
|
|
{ input_path: inputPath, format: "directory" },
|
|
{
|
|
progress: {
|
|
report: async (event) => {
|
|
if (event.terminal) throw cause;
|
|
},
|
|
},
|
|
},
|
|
);
|
|
|
|
expect(result.ok).toBe(false);
|
|
if (result.ok) throw new Error("Expected analysis failure");
|
|
expect(result.error.cause).toBe(cause);
|
|
expect(projectAnalysisError(result.error)).toMatchObject({
|
|
code: "unreadable_output",
|
|
details: {
|
|
operation: "analyze_javascript_application",
|
|
reason:
|
|
"Result schema rejected 1 issue at /semantic_graph/nodes/42/application_node_ids/0 (custom)",
|
|
},
|
|
});
|
|
});
|
|
|
|
it("retains unexpected failures and their input identity in caller-visible diagnostics", async () => {
|
|
const inputPath = await createTestTempDirectory("rea-js-failure-");
|
|
await writeFile(join(inputPath, "main.js"), "export const value = 1;\n");
|
|
const cause = new RangeError("Invalid string length");
|
|
// Retain the original failure locally, but never serialize its object graph.
|
|
cause.cause = cause;
|
|
const result = await analyzeJavaScriptApplication(
|
|
{ input_path: inputPath, format: "directory" },
|
|
{
|
|
progress: {
|
|
report: async (event) => {
|
|
if (event.terminal) throw cause;
|
|
},
|
|
},
|
|
},
|
|
);
|
|
expect(result.ok).toBe(false);
|
|
if (result.ok) throw new Error("Expected analysis failure");
|
|
expect(result.error.cause).toBe(cause);
|
|
expect(projectAnalysisError(result.error)).toMatchObject({
|
|
code: "execution_failure",
|
|
details: {
|
|
provider_id: "rea-javascript-application",
|
|
operation: "analyze_javascript_application",
|
|
diagnostics: {
|
|
input_path: inputPath,
|
|
error_name: "RangeError",
|
|
error_message: "Invalid string length",
|
|
},
|
|
},
|
|
});
|
|
const projection = projectAnalysisError(result.error);
|
|
expect(JSON.parse(JSON.stringify(projection))).toEqual(projection);
|
|
});
|
|
|
|
it("keeps filesystem failures distinct from engine failures", async () => {
|
|
const root = await createTestTempDirectory("rea-js-missing-");
|
|
const result = await analyzeJavaScriptApplication({
|
|
input_path: join(root, "missing"),
|
|
format: "directory",
|
|
});
|
|
expect(result.ok).toBe(false);
|
|
if (result.ok) throw new Error("Expected missing artifact failure");
|
|
expect(projectAnalysisError(result.error)).toMatchObject({
|
|
code: "artifact_operation_failed",
|
|
details: { operation: "analyze_javascript_application", reason: "io" },
|
|
});
|
|
});
|
|
|
|
it.each(["reported failure", null])(
|
|
"retains non-Error failures (%s)",
|
|
async (cause) => {
|
|
const inputPath = await createTestTempDirectory("rea-js-non-error-");
|
|
await writeFile(join(inputPath, "main.js"), "export const value = 1;\n");
|
|
const result = await analyzeJavaScriptApplication(
|
|
{ input_path: inputPath, format: "directory" },
|
|
{
|
|
progress: {
|
|
report: async (event) => {
|
|
if (event.terminal) throw cause;
|
|
},
|
|
},
|
|
},
|
|
);
|
|
expect(result.ok).toBe(false);
|
|
if (result.ok) throw new Error("Expected analysis failure");
|
|
expect(projectAnalysisError(result.error)).toMatchObject({
|
|
code: "execution_failure",
|
|
details: {
|
|
diagnostics: {
|
|
error_name: "UnknownError",
|
|
error_message:
|
|
typeof cause === "string"
|
|
? cause
|
|
: "JavaScript analysis failed with a non-Error value",
|
|
},
|
|
},
|
|
});
|
|
},
|
|
);
|
|
|
|
it.each([new Error("x".repeat(10_000)), "y".repeat(10_000)])(
|
|
"preserves long failure messages through JSON projection (%#)",
|
|
async (cause) => {
|
|
const inputPath = await createTestTempDirectory("rea-js-oversized-");
|
|
await writeFile(join(inputPath, "main.js"), "export const value = 1;\n");
|
|
const message = cause instanceof Error ? cause.message : cause;
|
|
const result = await analyzeJavaScriptApplication(
|
|
{ input_path: inputPath, format: "directory" },
|
|
{
|
|
progress: {
|
|
report: async (event) => {
|
|
if (event.terminal) throw cause;
|
|
},
|
|
},
|
|
},
|
|
);
|
|
expect(result.ok).toBe(false);
|
|
if (result.ok) throw new Error("Expected analysis failure");
|
|
expect(projectAnalysisError(result.error)).toMatchObject({
|
|
code: "execution_failure",
|
|
details: {
|
|
diagnostics: {
|
|
error_message: message,
|
|
},
|
|
},
|
|
});
|
|
const projection = projectAnalysisError(result.error);
|
|
expect(JSON.parse(JSON.stringify(projection))).toEqual(projection);
|
|
},
|
|
);
|
|
});
|