diff --git a/scripts/lib/verifier-fixture.d.mts b/scripts/lib/verifier-fixture.d.mts index ba77339aa2..3f93624056 100644 --- a/scripts/lib/verifier-fixture.d.mts +++ b/scripts/lib/verifier-fixture.d.mts @@ -4,6 +4,12 @@ export interface VerifierFixtureRoot { close(): Promise; } +/** A directory the verifier keeps after the run; nothing removes it. */ +export interface VerifierArtifactDirectory { + readonly path: string; + readonly kept: true; +} + /** * One verifier fixture root and every cleanup that must confirm before the * root may be removed. A failed cleanup retains the root and names it. @@ -19,6 +25,15 @@ export class VerifierFixture { readonly parent?: string; readonly private?: boolean; }): Promise; + /** + * Allocate a directory a verifier keeps for its artifacts (receipts, + * transcripts, profiles, Evidence). It is never removed; allocation names + * it on stderr. + */ + static keepArtifacts(options?: { + readonly prefix?: string; + readonly parent?: string; + }): Promise; readonly path: string; readonly released: boolean; /** Register a cleanup that must confirm before the root may be removed. */ diff --git a/scripts/lib/verifier-fixture.mjs b/scripts/lib/verifier-fixture.mjs index ed8f926d04..4bc9734e65 100644 --- a/scripts/lib/verifier-fixture.mjs +++ b/scripts/lib/verifier-fixture.mjs @@ -58,6 +58,21 @@ export class VerifierFixture { ); } + /** + * Allocate a directory a verifier keeps for the artifacts a reviewer reads + * after the run: receipts, transcripts, client profiles and Evidence. It is + * never removed. Allocation names it on stderr, so a run that fails before + * writing its receipt still reports where its artifacts are. + */ + static async keepArtifacts({ prefix, parent } = {}) { + const { path } = await createCanonicalTemporaryDirectory( + prefix ?? "rea-verifier-artifacts-", + ...(parent === undefined ? [] : [parent]), + ); + process.stderr.write(`Keeping verifier artifacts at ${path}\n`); + return Object.freeze({ path, kept: true }); + } + get path() { return this.#root.path; } diff --git a/scripts/verify-claude-client.mjs b/scripts/verify-claude-client.mjs index e5ba1199b8..0ea9219b11 100644 --- a/scripts/verify-claude-client.mjs +++ b/scripts/verify-claude-client.mjs @@ -1,7 +1,6 @@ import assert from "node:assert/strict"; import { createHash } from "node:crypto"; -import { mkdir, mkdtemp, readFile, writeFile } from "node:fs/promises"; -import { tmpdir } from "node:os"; +import { mkdir, readFile, writeFile } from "node:fs/promises"; import { isAbsolute, join } from "node:path"; import { fileURLToPath } from "node:url"; import Ajv2020 from "ajv/dist/2020.js"; @@ -36,6 +35,7 @@ import { } from "./verify/clients/result-discovery.mjs"; import { createVerifierRun, completeVerifierRun } from "./lib/verifier-run.mjs"; import { createAnthropicModelFixture } from "./verify/clients/anthropic-model-fixture.mjs"; +import { VerifierFixture } from "./lib/verifier-fixture.mjs"; const repo = fileURLToPath(new URL("..", import.meta.url)); const runtimeRoot = process.env.REA_VERIFY_RUNTIME_ROOT ?? repo; @@ -46,7 +46,9 @@ assert( "Usage: verify-claude-client.mjs [chat|call]", ); const verifier = createVerifierRun(); -const lab = await mkdtemp(join(tmpdir(), "rea-claude-client-")); +const { path: lab } = await VerifierFixture.keepArtifacts({ + prefix: "rea-claude-client-", +}); const profile = join(lab, "selected profile"); const workspace = join(lab, "workspace"); const configPath = join(profile, ".claude.json"); diff --git a/scripts/verify-cline-client.mjs b/scripts/verify-cline-client.mjs index a2e830d972..712d769709 100644 --- a/scripts/verify-cline-client.mjs +++ b/scripts/verify-cline-client.mjs @@ -1,7 +1,6 @@ import assert from "node:assert/strict"; import { Client } from "@modelcontextprotocol/client"; -import { mkdir, mkdtemp, readFile } 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"; @@ -10,13 +9,16 @@ import { createVerifierCommands } from "./lib/verify-commands.mjs"; import { createVerifierMcpOwner } from "./lib/verifier-mcp-owner.mjs"; import { withVerifierCleanup } from "./lib/verifier-cleanup.mjs"; import { completeVerifierRun, createVerifierRun } from "./lib/verifier-run.mjs"; +import { VerifierFixture } from "./lib/verifier-fixture.mjs"; /** Verify default-path CLI registration, client parsing and owned MCP discovery. */ const repo = fileURLToPath(new URL("..", import.meta.url)); const command = process.env.REA_VERIFY_CLINE_COMMAND ?? "cline"; const reaEntry = join(repo, "scripts/rea.mjs"); const run = createVerifierRun(); -const lab = await mkdtemp(join(tmpdir(), "rea-cline-client-")); +const { path: lab } = await VerifierFixture.keepArtifacts({ + prefix: "rea-cline-client-", +}); const home = join(lab, "home"); await mkdir(home, { recursive: true }); const dataDirectory = join(home, ".cline", "data"); diff --git a/scripts/verify-commandcode-client.mjs b/scripts/verify-commandcode-client.mjs index 70d6f5f565..049a813f1b 100644 --- a/scripts/verify-commandcode-client.mjs +++ b/scripts/verify-commandcode-client.mjs @@ -1,6 +1,6 @@ import assert from "node:assert/strict"; import { createHash } from "node:crypto"; -import { mkdir, mkdtemp, readFile, writeFile } from "node:fs/promises"; +import { mkdir, readFile, writeFile } from "node:fs/promises"; import { homedir } from "node:os"; import { isAbsolute, join, relative } from "node:path"; import { fileURLToPath } from "node:url"; @@ -16,6 +16,7 @@ import { } from "./verify/clients/javascript-fixture.mjs"; import { createVerifierRun, completeVerifierRun } from "./lib/verifier-run.mjs"; import { createOpenAiModelFixture } from "./verify/clients/model-fixture.mjs"; +import { VerifierFixture } from "./lib/verifier-fixture.mjs"; assert( process.platform !== "win32", @@ -39,7 +40,10 @@ assert( "Usage: verify-commandcode-client.mjs [chat|call]", ); const verifier = createVerifierRun(); -const lab = await mkdtemp(join(account, "rea-commandcode-client-")); +const { path: lab } = await VerifierFixture.keepArtifacts({ + prefix: "rea-commandcode-client-", + parent: account, +}); const workspace = join(lab, "workspace"); const target = join(workspace, "fixture α测试"); const profile = join(account, ".commandcode"); diff --git a/scripts/verify-copilot-client.mjs b/scripts/verify-copilot-client.mjs index 3f61f89d2a..acdf667085 100644 --- a/scripts/verify-copilot-client.mjs +++ b/scripts/verify-copilot-client.mjs @@ -1,7 +1,6 @@ import assert from "node:assert/strict"; import { createHash } from "node:crypto"; -import { mkdir, mkdtemp, readFile, writeFile } from "node:fs/promises"; -import { tmpdir } from "node:os"; +import { mkdir, readFile, writeFile } from "node:fs/promises"; import { basename, isAbsolute, join } from "node:path"; import { fileURLToPath } from "node:url"; import Ajv2020 from "ajv/dist/2020.js"; @@ -26,6 +25,7 @@ import { } from "./verify/clients/result-discovery.mjs"; import { createVerifierRun, completeVerifierRun } from "./lib/verifier-run.mjs"; import { createOpenAiModelFixture } from "./verify/clients/model-fixture.mjs"; +import { VerifierFixture } from "./lib/verifier-fixture.mjs"; assert( process.platform !== "win32", @@ -48,7 +48,9 @@ assert( "Usage: verify-copilot-client.mjs [chat|call]", ); const verifier = createVerifierRun(); -const lab = await mkdtemp(join(tmpdir(), "rea-copilot-client-")); +const { path: lab } = await VerifierFixture.keepArtifacts({ + prefix: "rea-copilot-client-", +}); const account = join(lab, "account"); const profile = join(lab, "selected profile"); const workspace = join(lab, "workspace"); diff --git a/scripts/verify-deepseek-client.mjs b/scripts/verify-deepseek-client.mjs index 1d960a0ec0..6be7f7e053 100644 --- a/scripts/verify-deepseek-client.mjs +++ b/scripts/verify-deepseek-client.mjs @@ -1,7 +1,6 @@ import assert from "node:assert/strict"; import { createHash } from "node:crypto"; -import { mkdir, mkdtemp, readFile, writeFile } from "node:fs/promises"; -import { tmpdir } from "node:os"; +import { mkdir, readFile, writeFile } from "node:fs/promises"; import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; import { Ajv2020 } from "ajv/dist/2020.js"; @@ -22,6 +21,7 @@ import { import { createVerifierRun, completeVerifierRun } from "./lib/verifier-run.mjs"; import { importVerifierSource } from "./lib/verifier-source-loader.mjs"; import { createOpenAiModelFixture } from "./verify/clients/model-fixture.mjs"; +import { VerifierFixture } from "./lib/verifier-fixture.mjs"; const mode = process.argv[2] ?? "call"; assert(["chat", "call"].includes(mode), "Select chat or call."); @@ -34,7 +34,9 @@ const { assertClientSchemaPatterns } = await importVerifierSource( const repo = fileURLToPath(new URL("..", import.meta.url)); const runtimeRoot = process.env.REA_VERIFY_RUNTIME_ROOT ?? repo; const command = process.env.REA_VERIFY_DEEPSEEK_COMMAND ?? "dsh"; -const lab = await mkdtemp(join(tmpdir(), "rea-deepseek-client-")); +const { path: lab } = await VerifierFixture.keepArtifacts({ + prefix: "rea-deepseek-client-", +}); const account = join(lab, "account"); const profile = join(lab, "selected profile"); const workspace = join(lab, "workspace"); diff --git a/scripts/verify-gemini-client.mjs b/scripts/verify-gemini-client.mjs index eab017d6b4..b90ceabbb1 100644 --- a/scripts/verify-gemini-client.mjs +++ b/scripts/verify-gemini-client.mjs @@ -1,7 +1,6 @@ import assert from "node:assert/strict"; import { createHash } from "node:crypto"; -import { mkdir, mkdtemp, readFile, writeFile } from "node:fs/promises"; -import { tmpdir } from "node:os"; +import { mkdir, readFile, writeFile } from "node:fs/promises"; import { isAbsolute, join, relative, resolve } from "node:path"; import { fileURLToPath } from "node:url"; import Ajv2020 from "ajv/dist/2020.js"; @@ -24,6 +23,7 @@ import { import { createVerifierRun, completeVerifierRun } from "./lib/verifier-run.mjs"; import { createGeminiModelFixture } from "./verify/clients/gemini-model-fixture.mjs"; import { importVerifierSource } from "./lib/verifier-source-loader.mjs"; +import { VerifierFixture } from "./lib/verifier-fixture.mjs"; const { geminiUnsupportedLiteral } = await importVerifierSource( new URL("./lib/gemini-schema-literals.mts", import.meta.url).href, @@ -49,7 +49,9 @@ assert( "Usage: verify-gemini-client.mjs [chat|call]", ); const verifier = createVerifierRun(); -const lab = await mkdtemp(join(tmpdir(), "rea-gemini-client-")); +const { path: lab } = await VerifierFixture.keepArtifacts({ + prefix: "rea-gemini-client-", +}); const account = join(lab, "account"); const profile = join(lab, "selected profile"); const workspace = join(lab, "workspace"); diff --git a/scripts/verify-grok-client.mjs b/scripts/verify-grok-client.mjs index 0ea587e45f..75dbcc6933 100644 --- a/scripts/verify-grok-client.mjs +++ b/scripts/verify-grok-client.mjs @@ -1,7 +1,7 @@ import assert from "node:assert/strict"; import { createHash } from "node:crypto"; -import { mkdir, mkdtemp, readFile, writeFile } from "node:fs/promises"; -import { homedir, tmpdir } from "node:os"; +import { mkdir, readFile, writeFile } from "node:fs/promises"; +import { homedir } from "node:os"; import { isAbsolute, join, relative } from "node:path"; import { fileURLToPath } from "node:url"; import Ajv2020 from "ajv/dist/2020.js"; @@ -16,6 +16,7 @@ import { } from "./verify/clients/javascript-fixture.mjs"; import { createVerifierRun, completeVerifierRun } from "./lib/verifier-run.mjs"; import { createOpenAiModelFixture } from "./verify/clients/model-fixture.mjs"; +import { VerifierFixture } from "./lib/verifier-fixture.mjs"; assert( process.platform !== "win32", @@ -30,7 +31,9 @@ assert( "Usage: verify-grok-client.mjs [chat|call]", ); const verifier = createVerifierRun(); -const lab = await mkdtemp(join(tmpdir(), "rea-grok-client-")); +const { path: lab } = await VerifierFixture.keepArtifacts({ + prefix: "rea-grok-client-", +}); const account = join(lab, "account"); const profile = join(lab, "selected profile"); const workspace = join(lab, "workspace"); diff --git a/scripts/verify-native-agent-client.mjs b/scripts/verify-native-agent-client.mjs index bed1f8b41f..63dd71fa48 100644 --- a/scripts/verify-native-agent-client.mjs +++ b/scripts/verify-native-agent-client.mjs @@ -1,7 +1,6 @@ import assert from "node:assert/strict"; import { createHash } from "node:crypto"; -import { mkdir, mkdtemp, readFile, writeFile } from "node:fs/promises"; -import { tmpdir } from "node:os"; +import { mkdir, readFile, writeFile } from "node:fs/promises"; import { join } from "node:path"; import { fileURLToPath } from "node:url"; import { TOOL_CONTRACTS } from "../dist/contracts/toolContracts.js"; @@ -18,6 +17,7 @@ import { } from "./verify/clients/result-discovery.mjs"; import { createVerifierRun, completeVerifierRun } from "./lib/verifier-run.mjs"; import { createOpenAiModelFixture } from "./verify/clients/model-fixture.mjs"; +import { VerifierFixture } from "./lib/verifier-fixture.mjs"; const client = process.argv[2]; const mode = process.argv[3] ?? "call"; @@ -31,7 +31,9 @@ const repo = fileURLToPath(new URL("..", import.meta.url)); const runtimeRoot = process.env.REA_VERIFY_RUNTIME_ROOT ?? repo; const command = process.env[`REA_VERIFY_${client.toUpperCase()}_COMMAND`] ?? client; -const lab = await mkdtemp(join(tmpdir(), `rea-${client}-client-`)); +const { path: lab } = await VerifierFixture.keepArtifacts({ + prefix: `rea-${client}-client-`, +}); const account = join(lab, "account"); const profile = join(lab, "selected profile"); const stickyHermes = diff --git a/scripts/verify-omp-client.mjs b/scripts/verify-omp-client.mjs index 0cd3d0227b..19a45c693c 100644 --- a/scripts/verify-omp-client.mjs +++ b/scripts/verify-omp-client.mjs @@ -1,7 +1,7 @@ import assert from "node:assert/strict"; import { createHash } from "node:crypto"; -import { mkdir, mkdtemp, readFile, writeFile } from "node:fs/promises"; -import { homedir, tmpdir } from "node:os"; +import { mkdir, readFile, writeFile } from "node:fs/promises"; +import { homedir } from "node:os"; import { isAbsolute, join, relative } from "node:path"; import { fileURLToPath } from "node:url"; import { TOOL_CONTRACTS } from "../dist/contracts/toolContracts.js"; @@ -15,6 +15,7 @@ import { } from "./verify/clients/javascript-fixture.mjs"; import { createVerifierRun, completeVerifierRun } from "./lib/verifier-run.mjs"; import { createOpenAiModelFixture } from "./verify/clients/model-fixture.mjs"; +import { VerifierFixture } from "./lib/verifier-fixture.mjs"; assert( process.platform !== "win32", @@ -29,7 +30,9 @@ assert( "Usage: verify-omp-client.mjs [chat|call]", ); const verifier = createVerifierRun(); -const lab = await mkdtemp(join(tmpdir(), "rea-omp-client-")); +const { path: lab } = await VerifierFixture.keepArtifacts({ + prefix: "rea-omp-client-", +}); const account = join(lab, "account"); const agent = join(lab, "selected agent"); const workspace = join(lab, "workspace"); diff --git a/scripts/verify-opencode-client.mjs b/scripts/verify-opencode-client.mjs index 8877b4664d..6b87fdbe11 100644 --- a/scripts/verify-opencode-client.mjs +++ b/scripts/verify-opencode-client.mjs @@ -1,7 +1,6 @@ import assert from "node:assert/strict"; import { createHash } from "node:crypto"; -import { mkdir, mkdtemp, readFile, writeFile } from "node:fs/promises"; -import { tmpdir } from "node:os"; +import { mkdir, readFile, writeFile } from "node:fs/promises"; import { isAbsolute, join } from "node:path"; import { fileURLToPath } from "node:url"; import Ajv2020 from "ajv/dist/2020.js"; @@ -20,6 +19,7 @@ import { } from "./verify/clients/result-discovery.mjs"; import { createVerifierRun, completeVerifierRun } from "./lib/verifier-run.mjs"; import { createOpenAiModelFixture } from "./verify/clients/model-fixture.mjs"; +import { VerifierFixture } from "./lib/verifier-fixture.mjs"; assert( process.platform !== "win32", @@ -39,7 +39,9 @@ assert( "REA_VERIFY_OPENCODE_CONFIG_FORMAT must be json or jsonc", ); const verifier = createVerifierRun(); -const lab = await mkdtemp(join(tmpdir(), "rea-opencode-client-")); +const { path: lab } = await VerifierFixture.keepArtifacts({ + prefix: "rea-opencode-client-", +}); const account = join(lab, "account"); const profile = join(lab, "selected profile"); const workspace = join(lab, "workspace"); diff --git a/scripts/verify-qwen-client.mjs b/scripts/verify-qwen-client.mjs index 58fd63c3d6..c67005a024 100644 --- a/scripts/verify-qwen-client.mjs +++ b/scripts/verify-qwen-client.mjs @@ -4,11 +4,9 @@ import { mkdir, writeFile, readFile, - mkdtemp, realpath, symlink, } from "node:fs/promises"; -import { tmpdir } from "node:os"; import { isAbsolute, join, relative, resolve, sep } from "node:path"; import { fileURLToPath } from "node:url"; import { createHash } from "node:crypto"; @@ -26,10 +24,13 @@ import { } from "./verify/clients/result-discovery.mjs"; import { createVerifierRun, completeVerifierRun } from "./lib/verifier-run.mjs"; import { TOOL_CONTRACTS } from "../dist/contracts/toolContracts.js"; +import { VerifierFixture } from "./lib/verifier-fixture.mjs"; const repo = fileURLToPath(new URL("..", import.meta.url)); const runtimeRoot = process.env.REA_VERIFY_RUNTIME_ROOT ?? repo; -const lab = await mkdtemp(join(tmpdir(), "rea-qwen-client-")); +const { path: lab } = await VerifierFixture.keepArtifacts({ + prefix: "rea-qwen-client-", +}); const client = "qwen"; const mode = process.argv[2] ?? "call"; assert( diff --git a/scripts/verify/architecture-guard-baselines.json b/scripts/verify/architecture-guard-baselines.json index 2e4f7b144f..d6f2ce29da 100644 --- a/scripts/verify/architecture-guard-baselines.json +++ b/scripts/verify/architecture-guard-baselines.json @@ -412,29 +412,19 @@ "scripts/verify-binary-layout.mjs": 1, "scripts/verify-browser-dom-destinations.mjs": 1, "scripts/verify-bun-standalone.mjs": 1, - "scripts/verify-claude-client.mjs": 1, - "scripts/verify-cline-client.mjs": 1, - "scripts/verify-commandcode-client.mjs": 1, - "scripts/verify-copilot-client.mjs": 1, - "scripts/verify-deepseek-client.mjs": 1, "scripts/verify-deno-standalone.mjs": 1, "scripts/verify-elf-unwind-ranges.mjs": 1, "scripts/verify-evm-interface.mjs": 1, "scripts/verify-flutter.mjs": 1, - "scripts/verify-gemini-client.mjs": 1, "scripts/verify-ghidra-bridge-sources.mjs": 1, "scripts/verify-ghidra-loader-relocation.mjs": 1, "scripts/verify-go-binary.mjs": 1, - "scripts/verify-grok-client.mjs": 1, "scripts/verify-jeb.mjs": 1, "scripts/verify-linux-setup-e2e.mjs": 1, - "scripts/verify-native-agent-client.mjs": 1, "scripts/verify-native-calls.mjs": 1, "scripts/verify-native-pascal-calls.mjs": 1, "scripts/verify-native-ui.mjs": 1, "scripts/verify-node-sea.mjs": 1, - "scripts/verify-omp-client.mjs": 1, - "scripts/verify-opencode-client.mjs": 1, "scripts/verify-package.mjs": 1, "scripts/verify-packaged-ghidra-windows.mjs": 1, "scripts/verify-packaged-wasm-artifact.mjs": 1, @@ -442,7 +432,6 @@ "scripts/verify-pcap.mjs": 1, "scripts/verify-pe-load-config.mjs": 1, "scripts/verify-published-package.mjs": 1, - "scripts/verify-qwen-client.mjs": 1, "scripts/verify-real-cutter.mjs": 1, "scripts/verify-real-ghidra-address-context.mjs": 1, "scripts/verify-real-ghidra-checkpoints.mjs": 1, diff --git a/tests/process-global/verifierFixture.test.ts b/tests/process-global/verifierFixture.test.ts index c80dd96a87..ab2b3ff626 100644 --- a/tests/process-global/verifierFixture.test.ts +++ b/tests/process-global/verifierFixture.test.ts @@ -219,3 +219,31 @@ it("keeps a retained root across every release and names why", async () => { expect(closes).toBe(0); expect(fixture.released).toBe(false); }); + +it("keeps an artifact directory, owned by the verifier and named on stderr", async () => { + const parent = await VerifierFixture.open({ prefix: "rea-fixture-test-" }); + const written: string[] = []; + const write = process.stderr.write.bind(process.stderr); + process.stderr.write = ((chunk: string) => { + written.push(String(chunk)); + return true; + }) as typeof process.stderr.write; + try { + const artifacts = await VerifierFixture.keepArtifacts({ + prefix: "rea-client-", + parent: parent.path, + }); + expect(artifacts.kept).toBe(true); + expect(artifacts.path.startsWith(join(parent.path, "rea-client-"))).toBe( + true, + ); + expect((await stat(artifacts.path)).mode & 0o077).toBe(0); + expect(Object.isFrozen(artifacts)).toBe(true); + expect(written).toEqual([ + `Keeping verifier artifacts at ${artifacts.path}\n`, + ]); + } finally { + process.stderr.write = write; + await parent.release(); + } +});