diff --git a/scripts/verify-real-ghidra-dol.mjs b/scripts/verify-real-ghidra-dol.mjs index bdc7a5444d..ebfcb64b07 100644 --- a/scripts/verify-real-ghidra-dol.mjs +++ b/scripts/verify-real-ghidra-dol.mjs @@ -1,8 +1,7 @@ #!/usr/bin/env node import assert from "node:assert/strict"; import { execFile } from "node:child_process"; -import { readFile, mkdtemp, mkdir, rm } from "node:fs/promises"; -import { tmpdir } from "node:os"; +import { readFile, mkdir } from "node:fs/promises"; import { join } from "node:path"; import { fileURLToPath } from "node:url"; import { promisify } from "node:util"; @@ -34,6 +33,7 @@ import { assertVerifierCleanupComplete, } from "./lib/verifier-run.mjs"; import { withVerifierCleanup } from "./lib/verifier-cleanup.mjs"; +import { VerifierFixture } from "./lib/verifier-fixture.mjs"; // Optional cross-target lane. No tool acquisition and no target execution. if (process.argv.length !== 2) @@ -59,7 +59,17 @@ if (installation.status !== "available") const execute = promisify(execFile); const entrypoint = fileURLToPath(new URL("./rea.mjs", import.meta.url)); const run = createVerifierRun(); -const workspace = await mkdtemp(join(tmpdir(), "rea-dol-proof-")); +const workspaceFixture = await VerifierFixture.open({ + prefix: "rea-dol-proof-", + // Ghidra children use the workspace as TMPDIR; a plain root admits them on every host. + private: false, +}); +const workspace = workspaceFixture.path; +// A failed run keeps its workspace for diagnosis; the fixture records why. +const retainWorkspace = (message, options) => { + workspaceFixture.retain(message); + return new Error(message, options); +}; const runtime = join(workspace, "runtime"); await mkdir(runtime); const baseEnv = { @@ -309,11 +319,11 @@ try { }, ]); } catch (cause) { - throw new Error( + throw retainWorkspace( `GameCube DOL verification failed; retained workspace: ${workspace}`, { cause }, ); } assert.ok(completed, "Verifier completion was not observed"); -await rm(workspace, { recursive: true, force: true }); +await workspaceFixture.release(); console.log(JSON.stringify({ ...completed, fixtures: reports }, null, 2)); diff --git a/scripts/verify-real-ghidra-m68k.mjs b/scripts/verify-real-ghidra-m68k.mjs index 17d9b26b65..d3e91e6bf1 100644 --- a/scripts/verify-real-ghidra-m68k.mjs +++ b/scripts/verify-real-ghidra-m68k.mjs @@ -1,14 +1,7 @@ import assert from "node:assert/strict"; import { execFile } from "node:child_process"; import { createHash } from "node:crypto"; -import { - mkdir, - mkdtemp, - readFile, - readdir, - rm, - writeFile, -} from "node:fs/promises"; +import { mkdir, readFile, readdir, writeFile } from "node:fs/promises"; import { join } from "node:path"; import { fileURLToPath } from "node:url"; import { promisify } from "node:util"; @@ -21,6 +14,7 @@ import { requireMcpOutput, } from "./lib/mcp-verifier-results.mjs"; import { createVerifierRun, completeVerifierRun } from "./lib/verifier-run.mjs"; +import { VerifierFixture } from "./lib/verifier-fixture.mjs"; if (process.argv.length !== 2) throw new Error("Usage: node scripts/verify-real-ghidra-m68k.mjs"); @@ -57,7 +51,18 @@ assert.equal( "7679358409efee4c5c2465f9a47158c4d49e0415ba12b3170898440db7ea0bc9", ); await mkdir(join(root, "build"), { recursive: true }); -const workspace = await mkdtemp(join(root, "build", "rea-m68k-proof-")); +const workspaceFixture = await VerifierFixture.open({ + prefix: "rea-m68k-proof-", + // Ghidra children use the workspace as TMPDIR; a plain root admits them on every host. + private: false, + parent: join(root, "build"), +}); +const workspace = workspaceFixture.path; +// A failed run keeps its workspace for diagnosis; the fixture records why. +const retainWorkspace = (message, options) => { + workspaceFixture.retain(message); + return new Error(message, options); +}; const runtime = join(workspace, "runtime"); await mkdir(runtime); const path = join(workspace, "image.bin"); @@ -257,6 +262,9 @@ try { }; } catch (cause) { if (stderr) process.stderr.write(stderr); + workspaceFixture.retain( + `m68k verification failed; retained workspace: ${workspace}`, + ); throw cause; } finally { try { @@ -265,16 +273,20 @@ try { await transport.close(); } } -assert.deepEqual( - await readdir(runtime), - [], - `Runtime resources survived server shutdown; retained workspace: ${workspace}`, -); +const survivors = await readdir(runtime); +if (survivors.length > 0) + throw retainWorkspace( + `Runtime resources survived server shutdown (${survivors.join(", ")}); retained workspace: ${workspace}`, + ); const verifierRun = await completeVerifierRun(run); -assert.equal(verifierRun.process_lineage.status, "verified"); -assert.deepEqual(verifierRun.process_lineage.descendants, []); -// Keep the workspace for diagnosis unless verification and cleanup both pass. -await rm(workspace, { recursive: true, force: true }); +if ( + verifierRun.process_lineage.status !== "verified" || + verifierRun.process_lineage.descendants.length !== 0 +) + throw retainWorkspace( + `Cleanup unverified; retained diagnostic workspace: ${workspace}`, + ); +await workspaceFixture.release(); process.stdout.write( JSON.stringify({ verifier_run: verifierRun, ...report }) + "\n", ); diff --git a/scripts/verify-real-ghidra-mips-relocatable.mjs b/scripts/verify-real-ghidra-mips-relocatable.mjs index 95c3a8b8e0..4762271640 100755 --- a/scripts/verify-real-ghidra-mips-relocatable.mjs +++ b/scripts/verify-real-ghidra-mips-relocatable.mjs @@ -2,8 +2,7 @@ import assert from "node:assert/strict"; import { execFile } from "node:child_process"; import { createHash } from "node:crypto"; -import { mkdtemp, mkdir, readFile, rm } from "node:fs/promises"; -import { tmpdir } from "node:os"; +import { mkdir, readFile } from "node:fs/promises"; import { join } from "node:path"; import { fileURLToPath } from "node:url"; import { promisify } from "node:util"; @@ -21,6 +20,7 @@ import { } from "../tests/conformance/ghidra/mips-relocatable-checks.mjs"; import { requireMcpOperationResult } from "./lib/mcp-verifier-results.mjs"; import { createVerifierRun, completeVerifierRun } from "./lib/verifier-run.mjs"; +import { VerifierFixture } from "./lib/verifier-fixture.mjs"; // Optional cross-target lane for ET_REL objects. No tool acquisition, no // linking and no target execution. @@ -48,7 +48,17 @@ if (installation.status !== "available") const execute = promisify(execFile); const entrypoint = fileURLToPath(new URL("./rea.mjs", import.meta.url)); const run = createVerifierRun(); -const workspace = await mkdtemp(join(tmpdir(), "rea-mips-rel-proof-")); +const workspaceFixture = await VerifierFixture.open({ + prefix: "rea-mips-rel-proof-", + // Ghidra children use the workspace as TMPDIR; a plain root admits them on every host. + private: false, +}); +const workspace = workspaceFixture.path; +// A failed run keeps its workspace for diagnosis; the fixture records why. +const retainWorkspace = (message, options) => { + workspaceFixture.retain(message); + return new Error(message, options); +}; const runtime = join(workspace, "runtime"); await mkdir(runtime); const env = { @@ -320,14 +330,14 @@ if ( completed.process_lineage.status !== "verified" || completed.process_lineage.descendants.length !== 0 ) - throw new Error( + throw retainWorkspace( `Cleanup unverified; retained diagnostic workspace: ${workspace}`, { cause: primaryFailure }, ); if (primaryFailure !== undefined) - throw new Error( + throw retainWorkspace( `MIPS relocatable verification failed; retained workspace: ${workspace}`, { cause: primaryFailure }, ); -await rm(workspace, { recursive: true, force: true }); +await workspaceFixture.release(); console.log(JSON.stringify({ ...completed, fixtures: reports }, null, 2)); diff --git a/scripts/verify-real-ghidra-mips-shared.mjs b/scripts/verify-real-ghidra-mips-shared.mjs index 133306de32..8a60952c54 100644 --- a/scripts/verify-real-ghidra-mips-shared.mjs +++ b/scripts/verify-real-ghidra-mips-shared.mjs @@ -1,8 +1,7 @@ #!/usr/bin/env node import assert from "node:assert/strict"; import { execFile } from "node:child_process"; -import { mkdtemp, mkdir, readFile, rm, writeFile } from "node:fs/promises"; -import { tmpdir } from "node:os"; +import { mkdir, readFile, writeFile } from "node:fs/promises"; import { join } from "node:path"; import { promisify } from "node:util"; import { Client } from "@modelcontextprotocol/client"; @@ -16,6 +15,7 @@ import { requireMcpToolError, } from "./lib/mcp-verifier-results.mjs"; import { createVerifierRun, completeVerifierRun } from "./lib/verifier-run.mjs"; +import { VerifierFixture } from "./lib/verifier-fixture.mjs"; if ( process.env.GHIDRA_INSTALL_DIR === undefined || @@ -26,7 +26,17 @@ if ( ); const execute = promisify(execFile); const run = createVerifierRun(); -const workspace = await mkdtemp(join(tmpdir(), "rea-mips-shared-proof-")); +const workspaceFixture = await VerifierFixture.open({ + prefix: "rea-mips-shared-proof-", + // Ghidra children use the workspace as TMPDIR; a plain root admits them on every host. + private: false, +}); +const workspace = workspaceFixture.path; +// A failed run keeps its workspace for diagnosis; the fixture records why. +const retainWorkspace = (message, options) => { + workspaceFixture.retain(message); + return new Error(message, options); +}; const runtime = join(workspace, "runtime"); await mkdir(runtime); const env = { @@ -328,13 +338,16 @@ if ( completed.process_lineage.status !== "verified" || completed.process_lineage.descendants.length !== 0 ) - throw new Error(`Cleanup unverified; retained workspace: ${workspace}`, { - cause: failure, - }); + throw retainWorkspace( + `Cleanup unverified; retained workspace: ${workspace}`, + { + cause: failure, + }, + ); if (failure !== undefined) - throw new Error( + throw retainWorkspace( `MIPS16 shared verification failed; retained workspace: ${workspace}`, { cause: failure }, ); -await rm(workspace, { recursive: true, force: true }); +await workspaceFixture.release(); console.log(JSON.stringify({ ...completed, fixtures: reports }, null, 2)); diff --git a/scripts/verify-real-ghidra-mips.mjs b/scripts/verify-real-ghidra-mips.mjs index 4405d155b9..4208a01f1e 100644 --- a/scripts/verify-real-ghidra-mips.mjs +++ b/scripts/verify-real-ghidra-mips.mjs @@ -3,15 +3,12 @@ import assert from "node:assert/strict"; import { execFile } from "node:child_process"; import { createHash } from "node:crypto"; import { - mkdtemp, mkdir, readFile, readdir, realpath, - rm, writeFile, } from "node:fs/promises"; -import { tmpdir } from "node:os"; import { basename, join, relative } from "node:path"; import { fileURLToPath } from "node:url"; import { promisify } from "node:util"; @@ -48,6 +45,7 @@ import { } from "./lib/mcp-verifier-results.mjs"; import { createVerifierRun, completeVerifierRun } from "./lib/verifier-run.mjs"; import { runOwnedCommand } from "../dist/process/OwnedCommand.js"; +import { VerifierFixture } from "./lib/verifier-fixture.mjs"; // Optional cross-target lane. No tool acquisition and no target execution. const mode = process.argv[2]; @@ -78,7 +76,17 @@ if (installation.status !== "available") const execute = promisify(execFile); const entrypoint = fileURLToPath(new URL("./rea.mjs", import.meta.url)); const run = createVerifierRun(); -const workspace = await mkdtemp(join(tmpdir(), "rea-mips-proof-")); +const workspaceFixture = await VerifierFixture.open({ + prefix: "rea-mips-proof-", + // Ghidra children use the workspace as TMPDIR; a plain root admits them on every host. + private: false, +}); +const workspace = workspaceFixture.path; +// A failed run keeps its workspace for diagnosis; the fixture records why. +const retainWorkspace = (message, options) => { + workspaceFixture.retain(message); + return new Error(message, options); +}; const runtime = join(workspace, "runtime"); await mkdir(runtime); const env = { @@ -892,16 +900,16 @@ if ( completed.process_lineage.status !== "verified" || completed.process_lineage.descendants.length !== 0 ) - throw new Error( + throw retainWorkspace( `Cleanup unverified; retained diagnostic workspace: ${workspace}`, { cause: primaryFailure }, ); if (primaryFailure !== undefined) - throw new Error( + throw retainWorkspace( `MIPS verification failed; retained workspace: ${workspace}`, { cause: primaryFailure, }, ); -await rm(workspace, { recursive: true, force: true }); +await workspaceFixture.release(); console.log(JSON.stringify({ ...completed, fixtures: reports }, null, 2)); diff --git a/scripts/verify-real-ghidra-ppc.mjs b/scripts/verify-real-ghidra-ppc.mjs index ac131580ea..aab8b8630f 100644 --- a/scripts/verify-real-ghidra-ppc.mjs +++ b/scripts/verify-real-ghidra-ppc.mjs @@ -2,8 +2,7 @@ import assert from "node:assert/strict"; import { execFile } from "node:child_process"; import { createHash } from "node:crypto"; -import { mkdtemp, mkdir, readFile, rm } from "node:fs/promises"; -import { tmpdir } from "node:os"; +import { mkdir, readFile } from "node:fs/promises"; import { join } from "node:path"; import { fileURLToPath } from "node:url"; import { promisify } from "node:util"; @@ -23,6 +22,7 @@ import { inspectVerifierGhidraInstallation } from "./lib/ghidra-installation-ins import { buildPowerPcFixture } from "../tests/conformance/ghidra/ppc-fixture.mjs"; import { requireMcpOperationResult } from "./lib/mcp-verifier-results.mjs"; import { createVerifierRun, completeVerifierRun } from "./lib/verifier-run.mjs"; +import { VerifierFixture } from "./lib/verifier-fixture.mjs"; // Optional cross-target lane. No tool acquisition and no target execution. if (process.argv.length !== 2) @@ -47,7 +47,17 @@ if (installation.status !== "available") const execute = promisify(execFile); const entrypoint = fileURLToPath(new URL("./rea.mjs", import.meta.url)); const run = createVerifierRun(); -const workspace = await mkdtemp(join(tmpdir(), "rea-ppc-proof-")); +const workspaceFixture = await VerifierFixture.open({ + prefix: "rea-ppc-proof-", + // Ghidra children use the workspace as TMPDIR; a plain root admits them on every host. + private: false, +}); +const workspace = workspaceFixture.path; +// A failed run keeps its workspace for diagnosis; the fixture records why. +const retainWorkspace = (message, options) => { + workspaceFixture.retain(message); + return new Error(message, options); +}; const runtime = join(workspace, "runtime"); await mkdir(runtime); const env = { @@ -303,16 +313,16 @@ if ( completed.process_lineage.status !== "verified" || completed.process_lineage.descendants.length !== 0 ) - throw new Error( + throw retainWorkspace( `Cleanup unverified; retained diagnostic workspace: ${workspace}`, { cause: primaryFailure }, ); if (primaryFailure !== undefined) - throw new Error( + throw retainWorkspace( `PowerPC verification failed; retained workspace: ${workspace}`, { cause: primaryFailure, }, ); -await rm(workspace, { recursive: true, force: true }); +await workspaceFixture.release(); console.log(JSON.stringify({ ...completed, fixtures: reports }, null, 2)); diff --git a/scripts/verify-real-ghidra-riscv.mjs b/scripts/verify-real-ghidra-riscv.mjs index c33cbfb21e..5ebc58881c 100644 --- a/scripts/verify-real-ghidra-riscv.mjs +++ b/scripts/verify-real-ghidra-riscv.mjs @@ -3,15 +3,7 @@ import { schemaVerifierEnvironment } from "./lib/mcp-verification-environment.mj import assert from "node:assert/strict"; import { execFile } from "node:child_process"; import { createHash } from "node:crypto"; -import { - mkdtemp, - mkdir, - readFile, - readdir, - rm, - writeFile, -} from "node:fs/promises"; -import { tmpdir } from "node:os"; +import { mkdir, readFile, readdir, writeFile } from "node:fs/promises"; import { join } from "node:path"; import { fileURLToPath } from "node:url"; import { promisify } from "node:util"; @@ -42,6 +34,7 @@ import { requireMcpToolError, } from "./lib/mcp-verifier-results.mjs"; import { createVerifierRun, completeVerifierRun } from "./lib/verifier-run.mjs"; +import { VerifierFixture } from "./lib/verifier-fixture.mjs"; if (process.argv.length !== 2) throw new Error("Usage: node scripts/verify-real-ghidra-riscv.mjs"); @@ -67,7 +60,17 @@ assert.equal(installation.providerVersion, "12.1.4"); const execute = promisify(execFile); const entrypoint = fileURLToPath(new URL("./rea.mjs", import.meta.url)); const run = createVerifierRun(); -const workspace = await mkdtemp(join(tmpdir(), "rea-riscv-proof-")); +const workspaceFixture = await VerifierFixture.open({ + prefix: "rea-riscv-proof-", + // Ghidra children use the workspace as TMPDIR; a plain root admits them on every host. + private: false, +}); +const workspace = workspaceFixture.path; +// A failed run keeps its workspace for diagnosis; the fixture records why. +const retainWorkspace = (message, options) => { + workspaceFixture.retain(message); + return new Error(message, options); +}; const runtime = join(workspace, "runtime"); await mkdir(runtime); const env = { @@ -563,20 +566,23 @@ if ( completed.process_lineage.status !== "verified" || completed.process_lineage.descendants.length !== 0 ) - throw new Error(`Cleanup unverified; retained workspace: ${workspace}`, { - cause: failure, - }); + throw retainWorkspace( + `Cleanup unverified; retained workspace: ${workspace}`, + { + cause: failure, + }, + ); if (failure !== undefined) - throw new Error( + throw retainWorkspace( `RISC-V verification failed; retained workspace: ${workspace}; MCP stderr: ${stderr}`, { cause: failure }, ); -assert.deepEqual( - await readdir(runtime), - [], - `Runtime resources survived server shutdown; retained workspace: ${workspace}`, -); -await rm(workspace, { recursive: true, force: true }); +const survivors = await readdir(runtime); +if (survivors.length > 0) + throw retainWorkspace( + `Runtime resources survived server shutdown (${survivors.join(", ")}); retained workspace: ${workspace}`, + ); +await workspaceFixture.release(); console.log( JSON.stringify( { diff --git a/scripts/verify/architecture-guard-baselines.json b/scripts/verify/architecture-guard-baselines.json index a8cfb43a9e..764f3aa482 100644 --- a/scripts/verify/architecture-guard-baselines.json +++ b/scripts/verify/architecture-guard-baselines.json @@ -423,15 +423,8 @@ "scripts/verify-real-cutter.mjs": 1, "scripts/verify-real-ghidra-address-context.mjs": 1, "scripts/verify-real-ghidra-checkpoints.mjs": 1, - "scripts/verify-real-ghidra-dol.mjs": 1, "scripts/verify-real-ghidra-function-bundle.mjs": 1, - "scripts/verify-real-ghidra-m68k.mjs": 1, - "scripts/verify-real-ghidra-mips-relocatable.mjs": 1, - "scripts/verify-real-ghidra-mips-shared.mjs": 1, - "scripts/verify-real-ghidra-mips.mjs": 1, - "scripts/verify-real-ghidra-ppc.mjs": 1, "scripts/verify-real-ghidra-project-coverage.mjs": 1, - "scripts/verify-real-ghidra-riscv.mjs": 1, "scripts/verify-real-ghidra-swift.mjs": 1, "scripts/verify-real-ghidra-switch.mjs": 1, "scripts/verify-real-ghidra.mjs": 1,