feat(verify): allocate kept verifier artifact directories through VerifierFixture (#11091)

The agent-client verifiers keep their lab directory as the run's artifact record (receipt, transcripts, profiles, Evidence) and name it as artifacts in the receipt, so it is not a fixture root that release should remove. VerifierFixture.keepArtifacts allocates such a directory canonically, never removes it, and names it on stderr at allocation so a run that fails before its receipt still reports where its artifacts are. The 11 client verifiers use it, and the verifier-fixture-root baseline shrinks by 11 sites.
This commit is contained in:
morluto authored and GitHub committed 2026-11-15 20:19:27 +08:00
1 parent c813adb331
commit 3f88f69746
15 files changed
+115 -43

No files matched your search

+15
View File
@@ -4,6 +4,12 @@ export interface VerifierFixtureRoot {
close(): Promise<void>;
}
/** 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<VerifierFixture>;
/**
* 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<VerifierArtifactDirectory>;
readonly path: string;
readonly released: boolean;
/** Register a cleanup that must confirm before the root may be removed. */
+15
View File
@@ -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;
}
+5 -3
View File
@@ -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");
+5 -3
View File
@@ -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");
+6 -2
View File
@@ -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");
+5 -3
View File
@@ -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");
+5 -3
View File
@@ -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");
+5 -3
View File
@@ -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");
+6 -3
View File
@@ -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");
+5 -3
View File
@@ -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 =
+6 -3
View File
@@ -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");
+5 -3
View File
@@ -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");
+4 -3
View File
@@ -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(
@@ -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,
@@ -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();
}
});