From bc9efc9279ea924031bcdc62e4fae936c392fd23 Mon Sep 17 00:00:00 2001 From: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com> Date: Sun, 25 Oct 2026 01:54:27 +0800 Subject: [PATCH] test(mcp): verify recovery before reading retained views (#7029) --- .../mcp/javascriptAnalysisResources.test.ts | 153 ++++++++++-------- 1 file changed, 88 insertions(+), 65 deletions(-) diff --git a/tests/boundary/mcp/javascriptAnalysisResources.test.ts b/tests/boundary/mcp/javascriptAnalysisResources.test.ts index 638ddb2628..807d712cf6 100644 --- a/tests/boundary/mcp/javascriptAnalysisResources.test.ts +++ b/tests/boundary/mcp/javascriptAnalysisResources.test.ts @@ -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>, +const verifyRecovery = async ( + client: Client, + nextRoot: string, diagnostics: () => string, -): void => { +): Promise => { + 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 => { - 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(), );