mirror of
https://github.com/morluto/rea
synced 2026-10-25 22:08:11 -04:00
test(mcp): verify recovery before reading retained views (#7029)
This commit is contained in:
@@ -50,7 +50,7 @@ it("delivers deadline recovery guidance at initialization before any tool call",
|
||||
}
|
||||
});
|
||||
|
||||
it("retains every static finding through public module views when application expansion exceeds memory, then accepts more work", async () => {
|
||||
it("accepts more work after application expansion exceeds memory and retains every static finding", async () => {
|
||||
const root = await createTestTempDirectory("rea-js-resource-boundary-");
|
||||
const nextRoot = await createTestTempDirectory("rea-js-resource-recovery-");
|
||||
await writeFile(join(nextRoot, "main.js"), "export const recovered = 9;\n");
|
||||
@@ -92,6 +92,7 @@ it("retains every static finding through public module views when application ex
|
||||
})
|
||||
.parse(response.structuredContent).normalized_result;
|
||||
expect(summary.summary.coverage.application.status).toBe("partial");
|
||||
await verifyRecovery(client, nextRoot, diagnostics);
|
||||
const retained = {
|
||||
kind: "retained-evidence",
|
||||
evidence_id: summary.parent_evidence_id,
|
||||
@@ -177,18 +178,6 @@ it("retains every static finding through public module views when application ex
|
||||
).toEqual(
|
||||
Array.from({ length: 4000 }, (_, index) => ["./target.js", index + 1]),
|
||||
);
|
||||
await client.ping();
|
||||
const next = await client.callTool({
|
||||
name: "analyze_javascript_application",
|
||||
arguments: {
|
||||
input_path: nextRoot,
|
||||
format: "directory",
|
||||
max_heap_mb: 128,
|
||||
detail: "summary",
|
||||
},
|
||||
});
|
||||
expectRecoverySuccess(next, diagnostics);
|
||||
await client.ping();
|
||||
expect(
|
||||
createHash("sha256")
|
||||
.update(await readFile(path))
|
||||
@@ -201,14 +190,17 @@ it("retains every static finding through public module views when application ex
|
||||
}
|
||||
}, 90000);
|
||||
|
||||
const repeatedSourceCount = 1500;
|
||||
const repeatedSourceText = "export const observed = 7;\n";
|
||||
|
||||
it("returns a directly usable partial analysis reference for an oversized timeout observation", async () => {
|
||||
const root = await createTestTempDirectory("rea-js-large-timeout-");
|
||||
for (let offset = 0; offset < 1500; offset += 50)
|
||||
for (let offset = 0; offset < repeatedSourceCount; offset += 50)
|
||||
await Promise.all(
|
||||
Array.from({ length: 50 }, (_, index) =>
|
||||
writeFile(
|
||||
join(root, `file-${String(offset + index)}.js`),
|
||||
"export const observed = 7;\n",
|
||||
repeatedSourceText,
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -250,6 +242,10 @@ it("returns a directly usable partial analysis reference for an oversized timeou
|
||||
operation: "analyze_javascript_application",
|
||||
timeout_ms: 1,
|
||||
});
|
||||
// Check worker recovery before materializing retained views: those views
|
||||
// independently consume parent capacity. Then prove the old Evidence is
|
||||
// still usable after the server has completed a different analysis.
|
||||
await verifyRecovery(client, nextRoot, diagnostics);
|
||||
const summaryResponse = await client.callTool({
|
||||
name: "inspect_analysis_view",
|
||||
arguments: {
|
||||
@@ -273,24 +269,14 @@ it("returns a directly usable partial analysis reference for an oversized timeou
|
||||
})
|
||||
.parse(summaryResponse.structuredContent).normalized_result.summary;
|
||||
expect(summary.statistics).toEqual({
|
||||
relevant_files: 1500,
|
||||
relevant_files: repeatedSourceCount,
|
||||
parsed_javascript_files: 0,
|
||||
parse_failures: 0,
|
||||
});
|
||||
expect(summary.coverage.semantic_unknowns).toBeGreaterThan(1500);
|
||||
expect(summary.coverage.semantic_unknowns).toBeGreaterThan(
|
||||
repeatedSourceCount,
|
||||
);
|
||||
await verifyRepeatedSourcePaths(client, partial);
|
||||
await client.ping();
|
||||
const next = await client.callTool({
|
||||
name: "analyze_javascript_application",
|
||||
arguments: {
|
||||
input_path: nextRoot,
|
||||
format: "directory",
|
||||
max_heap_mb: 128,
|
||||
detail: "summary",
|
||||
},
|
||||
});
|
||||
expectRecoverySuccess(next, diagnostics);
|
||||
await client.ping();
|
||||
expect(diagnostics()).not.toMatch(/FATAL ERROR:.*heap/iu);
|
||||
} finally {
|
||||
await client.close();
|
||||
@@ -298,14 +284,38 @@ it("returns a directly usable partial analysis reference for an oversized timeou
|
||||
}
|
||||
}, 90000);
|
||||
|
||||
const expectRecoverySuccess = (
|
||||
next: Awaited<ReturnType<Client["callTool"]>>,
|
||||
const verifyRecovery = async (
|
||||
client: Client,
|
||||
nextRoot: string,
|
||||
diagnostics: () => string,
|
||||
): void => {
|
||||
): Promise<void> => {
|
||||
await client.ping();
|
||||
const next = await client.callTool({
|
||||
name: "analyze_javascript_application",
|
||||
arguments: {
|
||||
input_path: nextRoot,
|
||||
format: "directory",
|
||||
max_heap_mb: 128,
|
||||
detail: "summary",
|
||||
},
|
||||
});
|
||||
expect(
|
||||
next.isError,
|
||||
JSON.stringify({ next, diagnostics: diagnostics() }),
|
||||
).not.toBe(true);
|
||||
expect(next.structuredContent).toMatchObject({
|
||||
normalized_result: {
|
||||
summary: {
|
||||
input_path: nextRoot,
|
||||
statistics: {
|
||||
relevant_files: 1,
|
||||
parsed_javascript_files: 1,
|
||||
parse_failures: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
await client.ping();
|
||||
};
|
||||
|
||||
const createResourceClient = () => {
|
||||
@@ -331,52 +341,65 @@ const verifyRepeatedSourcePaths = async (
|
||||
client: Client,
|
||||
source: { readonly kind: "retained-evidence"; readonly evidence_id: string },
|
||||
): Promise<void> => {
|
||||
const page = await client.callTool({
|
||||
name: "inspect_analysis_view",
|
||||
arguments: {
|
||||
source,
|
||||
view: { kind: "page", collection: "modules", offset: 0, limit: 4 },
|
||||
},
|
||||
});
|
||||
expect(page.isError).not.toBe(true);
|
||||
const modules = z
|
||||
.object({
|
||||
normalized_result: z.object({
|
||||
items: z.array(z.object({ kind: z.string(), node_id: z.string() })),
|
||||
}),
|
||||
})
|
||||
.parse(page.structuredContent);
|
||||
const asset = modules.normalized_result.items.find(
|
||||
({ kind }) => kind === "javascript-asset",
|
||||
);
|
||||
if (asset === undefined)
|
||||
throw new Error(
|
||||
"Identical source files disappeared from the retained graph",
|
||||
);
|
||||
const item = await client.callTool({
|
||||
const response = await client.callTool({
|
||||
name: "inspect_analysis_view",
|
||||
arguments: {
|
||||
source,
|
||||
view: {
|
||||
kind: "item",
|
||||
collection: "modules",
|
||||
selector: { node_id: asset.node_id },
|
||||
kind: "javascript-facts",
|
||||
where: { kinds: ["javascript-asset"] },
|
||||
property_keys: [],
|
||||
limit: repeatedSourceCount,
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(item.isError).not.toBe(true);
|
||||
const observations = z
|
||||
expect(response.isError).not.toBe(true);
|
||||
// Every occurrence is independently identified and sourced. No module
|
||||
// properties are needed, and a full item would retain unrelated data in
|
||||
// the same deliberately heap-constrained server used for recovery.
|
||||
const digest = createHash("sha256").update(repeatedSourceText).digest("hex");
|
||||
const facts = z
|
||||
.object({
|
||||
normalized_result: z.object({
|
||||
item: z.object({
|
||||
observations: z.array(z.object({ label: z.string() })),
|
||||
rows: z.array(
|
||||
z.object({
|
||||
node_id: z.string(),
|
||||
observation_id: z.string(),
|
||||
identity: z.object({
|
||||
strategy: z.literal("content-digest"),
|
||||
sha256: z.literal(digest),
|
||||
}),
|
||||
properties: z.strictObject({}),
|
||||
evidence: z.object({
|
||||
location: z.object({
|
||||
available: z.literal(true),
|
||||
value: z.object({
|
||||
kind: z.literal("artifact-path"),
|
||||
path: z.string(),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
),
|
||||
coverage: z.object({
|
||||
examined: z.literal(repeatedSourceCount),
|
||||
total: z.literal(repeatedSourceCount),
|
||||
exhausted: z.literal(true),
|
||||
next_offset: z.null(),
|
||||
}),
|
||||
}),
|
||||
})
|
||||
.parse(item.structuredContent).normalized_result.item.observations;
|
||||
expect(observations.map(({ label }) => label).sort()).toEqual(
|
||||
.parse(response.structuredContent).normalized_result;
|
||||
expect(facts.rows).toHaveLength(repeatedSourceCount);
|
||||
expect(new Set(facts.rows.map(({ node_id }) => node_id)).size).toBe(1);
|
||||
expect(
|
||||
new Set(facts.rows.map(({ observation_id }) => observation_id)).size,
|
||||
).toBe(repeatedSourceCount);
|
||||
expect(
|
||||
facts.rows.map(({ evidence }) => evidence.location.value.path).sort(),
|
||||
).toEqual(
|
||||
Array.from(
|
||||
{ length: 1500 },
|
||||
{ length: repeatedSourceCount },
|
||||
(_, index) => `file-${String(index)}.js`,
|
||||
).sort(),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user