diff --git a/scripts/ci/scopes.mjs b/scripts/ci/scopes.mjs index b1573eb9be..b004649203 100644 --- a/scripts/ci/scopes.mjs +++ b/scripts/ci/scopes.mjs @@ -133,6 +133,8 @@ export const SCOPES = { "src/domain/result.ts", "scripts/lib/verifier-source-loader.mjs", "tests/boundary/process/windowsNativeArtifactVerifier.test.ts", + "scripts/lib/windows-native-inspection-workspace.*", + "tests/boundary/process/windowsNativeInspectionWorkspace.test.ts", "scripts/lib/windows-owned-command.mjs", "scripts/run-exclusive-command.mjs", "tests/process-global/runnerCancellation.test.ts", diff --git a/scripts/lib/windows-native-inspection-workspace.d.mts b/scripts/lib/windows-native-inspection-workspace.d.mts new file mode 100644 index 0000000000..027ffd3c57 --- /dev/null +++ b/scripts/lib/windows-native-inspection-workspace.d.mts @@ -0,0 +1,6 @@ +/** Copy an isolated runtime and injected addon into a caller-owned workspace. */ +export function prepareWindowsNativeInspectionWorkspace(input: { + readonly root: string; + readonly artifact: string; + readonly workspace: string; +}): Promise; diff --git a/scripts/lib/windows-native-inspection-workspace.mjs b/scripts/lib/windows-native-inspection-workspace.mjs new file mode 100644 index 0000000000..8c5aaa98c6 --- /dev/null +++ b/scripts/lib/windows-native-inspection-workspace.mjs @@ -0,0 +1,27 @@ +import { copyFile, cp, mkdir, symlink, writeFile } from "node:fs/promises"; +import { join, resolve } from "node:path"; +import { pathToFileURL } from "node:url"; + +/** Prepare an isolated loader graph; the caller owns the workspace and cleanup. */ +export const prepareWindowsNativeInspectionWorkspace = async ({ + root, + artifact, + workspace, +}) => { + // Copy the complete compiled graph so loader dependencies have one owner. + await cp(join(root, "dist"), join(workspace, "dist"), { recursive: true }); + await writeFile(join(workspace, "package.json"), '{"type":"module"}'); + await symlink( + resolve(root, "node_modules"), + join(workspace, "node_modules"), + process.platform === "win32" ? "junction" : "dir", + ); + // The injected addon must remain separate from the production installation. + const native = join(workspace, "native", "windows", "build"); + await mkdir(native, { recursive: true }); + for (const name of ["manifest.json", "rea-windows-x64.node"]) + await copyFile(join(artifact, name), join(native, name)); + return pathToFileURL( + join(workspace, "dist", "windows", "WindowsNativeLoader.js"), + ).href; +}; diff --git a/scripts/verify-windows-native-inspection.mjs b/scripts/verify-windows-native-inspection.mjs index 9c5375cdf5..04709f98a2 100644 --- a/scripts/verify-windows-native-inspection.mjs +++ b/scripts/verify-windows-native-inspection.mjs @@ -2,20 +2,18 @@ import assert from "node:assert/strict"; import { spawnSync } from "node:child_process"; import { access, - copyFile, mkdir, mkdtemp, readFile, readdir, rm, stat, - symlink, writeFile, } from "node:fs/promises"; -import { createRequire } from "node:module"; import { tmpdir } from "node:os"; -import { dirname, join, resolve } from "node:path"; -import { pathToFileURL } from "node:url"; +import { join, resolve } from "node:path"; + +import { prepareWindowsNativeInspectionWorkspace } from "./lib/windows-native-inspection-workspace.mjs"; if (process.platform !== "win32") throw new Error("Native inspection ownership requires real Windows NTFS"); @@ -25,35 +23,11 @@ const artifact = resolve( ); const workspace = await mkdtemp(join(tmpdir(), "rea-inspection-conformance-")); try { - // Isolate the fixture from the packaged production artifact and loader cache. - await mkdir(join(workspace, "dist", "windows"), { recursive: true }); - await mkdir(join(workspace, "native", "windows", "build"), { - recursive: true, + const loader = await prepareWindowsNativeInspectionWorkspace({ + root, + artifact, + workspace, }); - await writeFile(join(workspace, "package.json"), '{"type":"module"}'); - await copyFile( - join(root, "dist", "windows", "WindowsNativeLoader.js"), - join(workspace, "dist", "windows", "WindowsNativeLoader.js"), - ); - await copyFile( - join(root, "dist", "generatedPackageMetadata.js"), - join(workspace, "dist", "generatedPackageMetadata.js"), - ); - for (const name of ["manifest.json", "rea-windows-x64.node"]) - await copyFile( - join(artifact, name), - join(workspace, "native", "windows", "build", name), - ); - const require = createRequire(join(root, "package.json")); - await mkdir(join(workspace, "node_modules")); - await symlink( - dirname(require.resolve("zod/package.json")), - join(workspace, "node_modules", "zod"), - "junction", - ); - const loader = pathToFileURL( - join(workspace, "dist", "windows", "WindowsNativeLoader.js"), - ).href; const source = `import assert from 'node:assert/strict'; import {createRequire} from 'node:module'; import {existsSync,mkdirSync,readFileSync,readdirSync,renameSync,statSync,writeFileSync} from 'node:fs'; diff --git a/src/mcpStartupPolicy.test.ts b/src/mcpStartupPolicy.test.ts index 76f7f984e2..c616a1ee16 100644 --- a/src/mcpStartupPolicy.test.ts +++ b/src/mcpStartupPolicy.test.ts @@ -67,17 +67,19 @@ describe("MCP tool deadline allowance for a Ghidra decompile limit", () => { expect(recommendedMcpToolTimeoutSeconds(timeoutInputs)).toBe(1050); }); - it("forwards curated launch limits without copying transport or unrelated settings", () => { + it("forwards selected launch and response limits without copying transport credentials or unrelated settings", () => { expect( explicitSetupRegistrationEnvironment({ REA_GHIDRA_DECOMPILE_TIMEOUT_SECONDS: "600", REA_ASAR_MAX_HEADER_BYTES: "33554432", REA_MCP_MAX_RESPONSE_BYTES: "20971520", + REA_TOKEN: "transport-only", UNRELATED: "dropped", }), ).toEqual({ REA_GHIDRA_DECOMPILE_TIMEOUT_SECONDS: "600", REA_ASAR_MAX_HEADER_BYTES: "33554432", + REA_MCP_MAX_RESPONSE_BYTES: "20971520", }); }); }); diff --git a/tests/boundary/filesystem/ciChangeScope.test.ts b/tests/boundary/filesystem/ciChangeScope.test.ts index 4df72101d3..73d9995c03 100644 --- a/tests/boundary/filesystem/ciChangeScope.test.ts +++ b/tests/boundary/filesystem/ciChangeScope.test.ts @@ -752,6 +752,14 @@ it.each([ "tests/boundary/process/windowsNativeArtifactVerifier.test.ts", { code: true, package: true }, ], + [ + "scripts/lib/windows-native-inspection-workspace.mjs", + { code: true, package: true }, + ], + [ + "tests/boundary/process/windowsNativeInspectionWorkspace.test.ts", + { code: true, package: true }, + ], [ "src/domain/javascript/javascriptRuntimeStaticCandidates.ts", { code: true, runtime: true, package: true, javascript_recovery: true }, diff --git a/tests/boundary/process/windowsNativeInspectionWorkspace.test.ts b/tests/boundary/process/windowsNativeInspectionWorkspace.test.ts new file mode 100644 index 0000000000..8d70ecf2cd --- /dev/null +++ b/tests/boundary/process/windowsNativeInspectionWorkspace.test.ts @@ -0,0 +1,58 @@ +import { mkdir, readFile, realpath, writeFile } from "node:fs/promises"; +import { join } from "node:path"; +import { fileURLToPath } from "node:url"; +import { expect } from "vitest"; + +import { prepareWindowsNativeInspectionWorkspace } from "../../../scripts/lib/windows-native-inspection-workspace.mjs"; +import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; +import { processTest } from "../../support/process/processFixture.js"; + +const repository = fileURLToPath(new URL("../../..", import.meta.url)); + +processTest( + "imports the isolated compiled Windows loader with its dependencies while keeping the injected addon separate", + async ({ processes }) => { + const scratch = await createTestTempDirectory( + "rea-native-inspection-layout-", + ); + const workspace = join(scratch, "runtime"); + const artifact = join(scratch, "injected"); + await mkdir(workspace); + await mkdir(artifact); + // These inert bytes establish layout and import coverage, never native authority. + const addon = "inert inspection fixture"; + await writeFile(join(artifact, "rea-windows-x64.node"), addon); + await writeFile(join(artifact, "manifest.json"), "{}"); + const loader = await prepareWindowsNativeInspectionWorkspace({ + root: repository, + artifact, + workspace, + }); + const imported = await processes.run( + process.execPath, + [ + "--input-type=module", + "--eval", + [ + 'import assert from "node:assert/strict";', + "const loader = await import(" + JSON.stringify(loader) + ");", + 'assert.equal(typeof loader.systemWindowsNativeAuthority, "function");', + ].join("\n"), + ], + { cwd: workspace, timeoutMs: 10_000 }, + ); + expect(imported.exitCode, imported.stderr).toBe(0); + expect(await realpath(join(workspace, "node_modules"))).toBe( + await realpath(join(repository, "node_modules")), + ); + const isolatedAddon = join( + workspace, + "native/windows/build/rea-windows-x64.node", + ); + expect(await readFile(isolatedAddon, "utf8")).toBe(addon); + await writeFile(isolatedAddon, "changed isolated fixture"); + expect(await readFile(join(artifact, "rea-windows-x64.node"), "utf8")).toBe( + addon, + ); + }, +);