mirror of
https://github.com/morluto/rea.git
synced 2026-11-15 19:23:24 +00:00
refactor(verify): give the javascript verifiers' roots to VerifierFixture (#11068)
* refactor(verify): give the javascript verifiers' roots to VerifierFixture VerifierFixture.retain(reason) keeps a root that an owned command whose cleanup is incomplete may still use, replacing each verifier's local retain flag and cleanup closure. * refactor(verify): give the Ghidra COM, DOS, 6502, NativeAOT and raw verifiers' workspaces to VerifierFixture (#11070) * refactor(verify): give the Ghidra COM, DOS, 6502, NativeAOT and raw verifiers' workspaces to VerifierFixture * refactor(verify): keep the Ghidra decompiler-failure, decompile-timeout and no-return workspaces through VerifierFixture until owned cleanup is verified (#11077)
This commit is contained in:
1 parent
ecab89f3e6
commit
47d14d0fb8
17 files changed
+329
-295
No files matched your search
@@ -23,6 +23,11 @@ export class VerifierFixture {
|
||||
readonly released: boolean;
|
||||
/** Register a cleanup that must confirm before the root may be removed. */
|
||||
releaseAfter<T extends () => unknown>(cleanup: T): T;
|
||||
/**
|
||||
* Keep the root for good: something that may still use it cannot be
|
||||
* confirmed stopped. Every later release fails with `reason`.
|
||||
*/
|
||||
retain(reason: string): void;
|
||||
/**
|
||||
* Run pending cleanups in order, then remove the root if all confirmed. A
|
||||
* failure is an AggregateError whose `cleanupLeases` is `[this fixture]`.
|
||||
|
||||
@@ -77,6 +77,18 @@ export class VerifierFixture {
|
||||
return cleanup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep the root for good: something that may still use its files cannot be
|
||||
* confirmed stopped and has no cleanup this fixture could retry (an owned
|
||||
* command whose cleanup is incomplete). Every release then fails, naming
|
||||
* `reason` and the root.
|
||||
*/
|
||||
retain(reason) {
|
||||
this.releaseAfter(() => {
|
||||
throw new Error(reason);
|
||||
});
|
||||
}
|
||||
|
||||
/** Concurrent callers share one attempt; a failed attempt can be retried. */
|
||||
release() {
|
||||
if (this.#released) return Promise.resolve();
|
||||
|
||||
@@ -1,16 +1,11 @@
|
||||
import {
|
||||
closeVerifierResources,
|
||||
withVerifierCleanup,
|
||||
} from "./lib/verifier-cleanup.mjs";
|
||||
import { closeVerifierResources } from "./lib/verifier-cleanup.mjs";
|
||||
import assert from "node:assert/strict";
|
||||
import { execFile } from "node:child_process";
|
||||
import { createHash } from "node:crypto";
|
||||
import {
|
||||
mkdir,
|
||||
mkdtemp,
|
||||
readFile,
|
||||
readdir,
|
||||
rm,
|
||||
symlink,
|
||||
unlink,
|
||||
writeFile,
|
||||
@@ -27,6 +22,10 @@ import {
|
||||
requireMcpOutput,
|
||||
} from "./lib/mcp-verifier-results.mjs";
|
||||
import { createVerifierRun, completeVerifierRun } from "./lib/verifier-run.mjs";
|
||||
import {
|
||||
VerifierFixture,
|
||||
withVerifierFixture,
|
||||
} from "./lib/verifier-fixture.mjs";
|
||||
|
||||
if (process.argv.length !== 2)
|
||||
throw new Error("Usage: node scripts/verify-real-ghidra-6502.mjs");
|
||||
@@ -45,7 +44,12 @@ for (const [name, command] of Object.entries({ ca65, ld65 })) {
|
||||
toolchain[name] = (version.stdout + version.stderr).trim();
|
||||
}
|
||||
await mkdir(join(root, "build"), { recursive: true });
|
||||
const workspace = await mkdtemp(join(root, "build", "rea-6502-proof-"));
|
||||
const workspaceFixture = await VerifierFixture.open({
|
||||
prefix: "rea-6502-proof-",
|
||||
parent: join(root, "build"),
|
||||
private: false,
|
||||
});
|
||||
const workspace = workspaceFixture.path;
|
||||
const runtime = join(workspace, "runtime");
|
||||
await mkdir(runtime);
|
||||
const path = join(workspace, "image.bin");
|
||||
@@ -74,7 +78,26 @@ transport.stderr?.on("data", (chunk) => {
|
||||
let connected = false;
|
||||
let verifierRun;
|
||||
let report;
|
||||
await withVerifierCleanup(async () => {
|
||||
workspaceFixture.releaseAfter(async () => {
|
||||
await closeVerifierResources([
|
||||
async () => {
|
||||
if (connected) await call("close_binary", {});
|
||||
},
|
||||
() => client.close(),
|
||||
() => transport.close(),
|
||||
]);
|
||||
assert.deepEqual(
|
||||
(await readdir(runtime)).filter((name) => name.startsWith("rea-ghidra-")),
|
||||
[],
|
||||
"Owned Ghidra runtime survived close",
|
||||
);
|
||||
verifierRun = await completeVerifierRun(run);
|
||||
if (process.platform !== "win32") {
|
||||
assert.equal(verifierRun.process_lineage.status, "verified");
|
||||
assert.deepEqual(verifierRun.process_lineage.descendants, []);
|
||||
}
|
||||
});
|
||||
await withVerifierFixture(workspaceFixture, async () => {
|
||||
try {
|
||||
await exec(
|
||||
ca65,
|
||||
@@ -465,28 +488,7 @@ await withVerifierCleanup(async () => {
|
||||
if (stderr) process.stderr.write(stderr);
|
||||
throw cause;
|
||||
}
|
||||
}, [
|
||||
async () => {
|
||||
await closeVerifierResources([
|
||||
async () => {
|
||||
if (connected) await call("close_binary", {});
|
||||
},
|
||||
() => client.close(),
|
||||
() => transport.close(),
|
||||
]);
|
||||
assert.deepEqual(
|
||||
(await readdir(runtime)).filter((name) => name.startsWith("rea-ghidra-")),
|
||||
[],
|
||||
"Owned Ghidra runtime survived close",
|
||||
);
|
||||
verifierRun = await completeVerifierRun(run);
|
||||
if (process.platform !== "win32") {
|
||||
assert.equal(verifierRun.process_lineage.status, "verified");
|
||||
assert.deepEqual(verifierRun.process_lineage.descendants, []);
|
||||
}
|
||||
await rm(workspace, { recursive: true, force: true });
|
||||
},
|
||||
]);
|
||||
});
|
||||
verifierRun = await completeVerifierRun(run);
|
||||
if (process.platform !== "win32") {
|
||||
assert.equal(verifierRun.process_lineage.status, "verified");
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
import { schemaVerifierEnvironment } from "./lib/mcp-verification-environment.mjs";
|
||||
import {
|
||||
closeVerifierResources,
|
||||
withVerifierCleanup,
|
||||
} from "./lib/verifier-cleanup.mjs";
|
||||
import { closeVerifierResources } from "./lib/verifier-cleanup.mjs";
|
||||
import assert from "node:assert/strict";
|
||||
import {
|
||||
requireMcpLifecycleResult,
|
||||
@@ -12,15 +9,7 @@ import {
|
||||
} from "./lib/mcp-verifier-results.mjs";
|
||||
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 { isAbsolute, join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { promisify } from "node:util";
|
||||
@@ -34,6 +23,10 @@ import { inspectVerifierGhidraInstallation } from "./lib/ghidra-installation-ins
|
||||
import { buildDosComFixture } from "../tests/conformance/ghidra/dos-com-fixture.mjs";
|
||||
import { createVerifierRun, completeVerifierRun } from "./lib/verifier-run.mjs";
|
||||
import { verifyGhidraAddressBoundaries } from "./lib/real-ghidra-address-boundaries.mjs";
|
||||
import {
|
||||
VerifierFixture,
|
||||
withVerifierFixture,
|
||||
} from "./lib/verifier-fixture.mjs";
|
||||
|
||||
// This lane intentionally has no game paths, binary seeds or compiler dependency.
|
||||
if (process.argv.length !== 2)
|
||||
@@ -73,7 +66,10 @@ const entrypoint =
|
||||
? fileURLToPath(new URL("./rea.mjs", import.meta.url))
|
||||
: join(packageRoot, "scripts", "rea.mjs");
|
||||
const run = createVerifierRun();
|
||||
const workspace = await mkdtemp(join(tmpdir(), "rea-com-proof-"));
|
||||
const workspaceFixture = await VerifierFixture.open({
|
||||
prefix: "rea-com-proof-",
|
||||
});
|
||||
const workspace = workspaceFixture.path;
|
||||
const runtime = join(workspace, "runtime");
|
||||
await mkdir(runtime);
|
||||
const fixture = buildDosComFixture();
|
||||
@@ -107,7 +103,24 @@ let report;
|
||||
transport.stderr?.on("data", (chunk) => {
|
||||
stderr = (stderr + chunk.toString()).slice(-65536);
|
||||
});
|
||||
await withVerifierCleanup(async () => {
|
||||
workspaceFixture.releaseAfter(async () => {
|
||||
await closeVerifierResources([
|
||||
async () => {
|
||||
if (connected) await call("close_binary", {});
|
||||
},
|
||||
() => client.close(),
|
||||
() => transport.close(),
|
||||
]);
|
||||
assert.deepEqual(
|
||||
(await readdir(runtime)).filter((name) => name.startsWith("rea-ghidra-")),
|
||||
[],
|
||||
"Owned Ghidra runtime survived close",
|
||||
);
|
||||
verifierRun = await completeVerifierRun(run);
|
||||
assert.equal(verifierRun.process_lineage.status, "verified");
|
||||
assert.deepEqual(verifierRun.process_lineage.descendants, []);
|
||||
});
|
||||
await withVerifierFixture(workspaceFixture, async () => {
|
||||
try {
|
||||
await client.connect(transport);
|
||||
connected = true;
|
||||
@@ -476,26 +489,7 @@ await withVerifierCleanup(async () => {
|
||||
if (stderr.trim()) process.stderr.write(stderr);
|
||||
throw error;
|
||||
}
|
||||
}, [
|
||||
async () => {
|
||||
await closeVerifierResources([
|
||||
async () => {
|
||||
if (connected) await call("close_binary", {});
|
||||
},
|
||||
() => client.close(),
|
||||
() => transport.close(),
|
||||
]);
|
||||
assert.deepEqual(
|
||||
(await readdir(runtime)).filter((name) => name.startsWith("rea-ghidra-")),
|
||||
[],
|
||||
"Owned Ghidra runtime survived close",
|
||||
);
|
||||
verifierRun = await completeVerifierRun(run);
|
||||
assert.equal(verifierRun.process_lineage.status, "verified");
|
||||
assert.deepEqual(verifierRun.process_lineage.descendants, []);
|
||||
await rm(workspace, { recursive: true, force: true });
|
||||
},
|
||||
]);
|
||||
});
|
||||
assert.ok(report);
|
||||
verifierRun = await completeVerifierRun(run);
|
||||
assert.equal(verifierRun.process_lineage.status, "verified");
|
||||
|
||||
@@ -2,8 +2,7 @@ import { AnalysisSettings } from "../dist/application/AnalysisSettings.js";
|
||||
import { runtimeAnalysisConfig } from "../dist/config/runtimeAnalysisSettings.js";
|
||||
import assert from "node:assert/strict";
|
||||
import { createHash } from "node:crypto";
|
||||
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { readFile, writeFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
|
||||
import { parseBinaryTarget } from "../dist/application/BinaryTargetResolver.js";
|
||||
@@ -23,6 +22,10 @@ import {
|
||||
completeVerifierRun,
|
||||
createVerifierRun,
|
||||
} from "./lib/verifier-run.mjs";
|
||||
import {
|
||||
VerifierFixture,
|
||||
withVerifierFixture,
|
||||
} from "./lib/verifier-fixture.mjs";
|
||||
|
||||
// Volatile straight-line work produces a natural native response above 1 MiB
|
||||
// and a decompile above one second, without the old branch-heavy fixture.
|
||||
@@ -111,9 +114,10 @@ const installation = await inspectVerifierGhidraInstallation({
|
||||
assert.equal(installation.status, "available", JSON.stringify(installation));
|
||||
assert.equal(installation.providerVersion, SUPPORTED_GHIDRA_VERSION);
|
||||
|
||||
const workspace = await mkdtemp(
|
||||
join(tmpdir(), "rea-ghidra-decompile-timeout-"),
|
||||
);
|
||||
const workspaceFixture = await VerifierFixture.open({
|
||||
prefix: "rea-ghidra-decompile-timeout-",
|
||||
});
|
||||
const workspace = workspaceFixture.path;
|
||||
const began = performance.now();
|
||||
const controller = new AbortController();
|
||||
const outerDeadline = setTimeout(
|
||||
@@ -123,7 +127,16 @@ const outerDeadline = setTimeout(
|
||||
outerDeadline.unref();
|
||||
let cleaned = true;
|
||||
let report;
|
||||
try {
|
||||
// The workspace stays while an owned Ghidra process or server may still use
|
||||
// it: this cleanup confirms only once their cleanup was verified.
|
||||
workspaceFixture.releaseAfter(() => {
|
||||
clearTimeout(outerDeadline);
|
||||
if (!cleaned)
|
||||
throw new Error(
|
||||
`Owned Ghidra cleanup is unverified; retaining ${workspace}.`,
|
||||
);
|
||||
});
|
||||
await withVerifierFixture(workspaceFixture, async () => {
|
||||
const sourcePath = join(workspace, "slow-decompile.c");
|
||||
const source = fixtureSource();
|
||||
await writeFile(sourcePath, source);
|
||||
@@ -403,8 +416,5 @@ try {
|
||||
scope:
|
||||
"Real Ghidra analysis of generated Mach-O/ELF through authenticated loopback: independent value-flow budgets, lowered admission, native payload exhaustion and recovery, natural decompile timeout with listing-backed partial facts, and deadline recovery on the same Program. No target execution. Windows and public CLI/MCP transport recovery are unverified. Timings are observations, not performance comparisons; CPU, peak process-family memory and agent tokens are unmeasured.",
|
||||
};
|
||||
} finally {
|
||||
clearTimeout(outerDeadline);
|
||||
if (cleaned) await rm(workspace, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
console.log(JSON.stringify({ ...report, cleanup: "complete" }));
|
||||
@@ -1,7 +1,6 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { createHash } from "node:crypto";
|
||||
import { mkdir, mkdtemp, 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";
|
||||
|
||||
@@ -27,6 +26,10 @@ import {
|
||||
completeVerifierRun,
|
||||
createVerifierRun,
|
||||
} from "./lib/verifier-run.mjs";
|
||||
import {
|
||||
VerifierFixture,
|
||||
withVerifierFixture,
|
||||
} from "./lib/verifier-fixture.mjs";
|
||||
|
||||
const run = createVerifierRun();
|
||||
assert.ok(
|
||||
@@ -50,12 +53,23 @@ const fixtures = fileURLToPath(
|
||||
new URL("../tests/conformance/ghidra", import.meta.url),
|
||||
);
|
||||
const bridge = fileURLToPath(new URL("../bridge/ghidra", import.meta.url));
|
||||
const workspace = await mkdtemp(join(tmpdir(), "rea-ghidra-decompiler-"));
|
||||
const workspaceFixture = await VerifierFixture.open({
|
||||
prefix: "rea-ghidra-decompiler-",
|
||||
});
|
||||
const workspace = workspaceFixture.path;
|
||||
let cleaned = true;
|
||||
let report;
|
||||
const digest = (bytes) => createHash("sha256").update(bytes).digest("hex");
|
||||
const injected = "Ghidra decompilation failed: Injected backend failure";
|
||||
try {
|
||||
// The workspace stays while an owned Ghidra process or server may still use
|
||||
// it: this cleanup confirms only once their cleanup was verified.
|
||||
workspaceFixture.releaseAfter(() => {
|
||||
if (!cleaned)
|
||||
throw new Error(
|
||||
`Owned Ghidra cleanup is unverified; retaining ${workspace}.`,
|
||||
);
|
||||
});
|
||||
await withVerifierFixture(workspaceFixture, async () => {
|
||||
const targetPath = join(
|
||||
workspace,
|
||||
process.platform === "darwin" ? "cross-format.dylib" : "cross-format.so",
|
||||
@@ -295,9 +309,7 @@ try {
|
||||
scope:
|
||||
"Real Ghidra ELF or Mach-O analysis with an injected decompiler failure; output-contract parsing of the degraded dossier; real read-only TCP provider projection of the healthy path. No target execution; natural decompiler failures, degraded CLI/MCP projection and Windows are unverified.",
|
||||
};
|
||||
} finally {
|
||||
if (cleaned) await rm(workspace, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
console.log(
|
||||
JSON.stringify({
|
||||
...report,
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
#!/usr/bin/env node
|
||||
import {
|
||||
closeVerifierResources,
|
||||
withVerifierCleanup,
|
||||
} from "./lib/verifier-cleanup.mjs";
|
||||
import { closeVerifierResources } from "./lib/verifier-cleanup.mjs";
|
||||
import assert from "node:assert/strict";
|
||||
import {
|
||||
requireMcpOperationResult,
|
||||
@@ -10,15 +7,7 @@ import {
|
||||
} from "./lib/mcp-verifier-results.mjs";
|
||||
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 { isAbsolute, join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { promisify } from "node:util";
|
||||
@@ -35,6 +24,10 @@ import {
|
||||
completeVerifierRun,
|
||||
} from "./lib/verifier-run.mjs";
|
||||
import { verifyGhidraAddressBoundaries } from "./lib/real-ghidra-address-boundaries.mjs";
|
||||
import {
|
||||
VerifierFixture,
|
||||
withVerifierFixture,
|
||||
} from "./lib/verifier-fixture.mjs";
|
||||
|
||||
// This lane intentionally has no game paths, binary seeds or compiler dependency.
|
||||
if (process.argv.length !== 2)
|
||||
@@ -67,7 +60,11 @@ if (captureDirectory !== undefined) {
|
||||
await mkdir(captureDirectory, { recursive: true, mode: 0o700 });
|
||||
}
|
||||
const run = createVerifierRun();
|
||||
const workspace = await mkdtemp(join(tmpdir(), "rea-dos-proof-"));
|
||||
const workspaceFixture = await VerifierFixture.open({
|
||||
prefix: "rea-dos-proof-",
|
||||
private: false,
|
||||
});
|
||||
const workspace = workspaceFixture.path;
|
||||
const runtime = join(workspace, "runtime");
|
||||
await mkdir(runtime);
|
||||
const fixture = buildDosMzFixture();
|
||||
@@ -107,7 +104,31 @@ let stderr = "";
|
||||
let connected = false;
|
||||
let verifierRun;
|
||||
let report;
|
||||
await withVerifierCleanup(async () => {
|
||||
workspaceFixture.releaseAfter(async () => {
|
||||
await closeVerifierResources([
|
||||
async () => {
|
||||
if (connected) await call("close_binary", {});
|
||||
},
|
||||
() => client.close(),
|
||||
() => transport.close(),
|
||||
]);
|
||||
assert.equal(
|
||||
digest(await readFile(targetPath)),
|
||||
sha256,
|
||||
"Original changed during cleanup",
|
||||
);
|
||||
assert.deepEqual(
|
||||
(await readdir(runtime)).filter((name) => name.startsWith("rea-ghidra-")),
|
||||
[],
|
||||
"Owned Ghidra runtime survived close",
|
||||
);
|
||||
verifierRun = await completeVerifierRun(run);
|
||||
if (process.platform !== "win32") {
|
||||
assert.equal(verifierRun.process_lineage.status, "verified");
|
||||
assert.deepEqual(verifierRun.process_lineage.descendants, []);
|
||||
}
|
||||
});
|
||||
await withVerifierFixture(workspaceFixture, async () => {
|
||||
try {
|
||||
await client.connect(transport);
|
||||
connected = true;
|
||||
@@ -508,33 +529,7 @@ await withVerifierCleanup(async () => {
|
||||
process.stderr.write(`DOS verifier MCP diagnostics:\n${stderr}\n`);
|
||||
throw error;
|
||||
}
|
||||
}, [
|
||||
async () => {
|
||||
await closeVerifierResources([
|
||||
async () => {
|
||||
if (connected) await call("close_binary", {});
|
||||
},
|
||||
() => client.close(),
|
||||
() => transport.close(),
|
||||
]);
|
||||
assert.equal(
|
||||
digest(await readFile(targetPath)),
|
||||
sha256,
|
||||
"Original changed during cleanup",
|
||||
);
|
||||
assert.deepEqual(
|
||||
(await readdir(runtime)).filter((name) => name.startsWith("rea-ghidra-")),
|
||||
[],
|
||||
"Owned Ghidra runtime survived close",
|
||||
);
|
||||
verifierRun = await completeVerifierRun(run);
|
||||
if (process.platform !== "win32") {
|
||||
assert.equal(verifierRun.process_lineage.status, "verified");
|
||||
assert.deepEqual(verifierRun.process_lineage.descendants, []);
|
||||
}
|
||||
await rm(workspace, { recursive: true, force: true });
|
||||
},
|
||||
]);
|
||||
});
|
||||
assert.ok(report, "No DOS proof report was produced");
|
||||
verifierRun = await completeVerifierRun(run);
|
||||
// Windows provider jobs are verified by their native owners, as in the 6502 lane.
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
#!/usr/bin/env node
|
||||
import {
|
||||
closeVerifierResources,
|
||||
withVerifierCleanup,
|
||||
} from "./lib/verifier-cleanup.mjs";
|
||||
import { closeVerifierResources } from "./lib/verifier-cleanup.mjs";
|
||||
import assert from "node:assert/strict";
|
||||
import {
|
||||
requireMcpLifecycleResult,
|
||||
@@ -10,15 +7,7 @@ import {
|
||||
} from "./lib/mcp-verifier-results.mjs";
|
||||
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 { basename, isAbsolute, join, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { promisify } from "node:util";
|
||||
@@ -29,6 +18,10 @@ import { nativeDataTypeSchema } from "../dist/domain/native/nativeDataType.js";
|
||||
import { nativeLoadImageSchema } from "../dist/domain/native/nativeLoadImage.js";
|
||||
|
||||
import { createVerifierRun, completeVerifierRun } from "./lib/verifier-run.mjs";
|
||||
import {
|
||||
VerifierFixture,
|
||||
withVerifierFixture,
|
||||
} from "./lib/verifier-fixture.mjs";
|
||||
const run = createVerifierRun();
|
||||
const mode = process.argv[2] ?? "symbols";
|
||||
if (
|
||||
@@ -90,7 +83,10 @@ const target =
|
||||
: symbolTarget;
|
||||
const bytes = await readFile(target);
|
||||
const sha256 = hash(bytes);
|
||||
const workspace = await mkdtemp(join(tmpdir(), "rea-nativeaot-proof-"));
|
||||
const workspaceFixture = await VerifierFixture.open({
|
||||
prefix: "rea-nativeaot-proof-",
|
||||
});
|
||||
const workspace = workspaceFixture.path;
|
||||
const runtime = join(workspace, "runtime");
|
||||
await mkdir(runtime);
|
||||
const packageRoot = process.env.REA_NATIVEAOT_PROOF_PACKAGE_ROOT;
|
||||
@@ -161,7 +157,24 @@ let opened = false;
|
||||
let connected = false;
|
||||
let verifierRun;
|
||||
let report;
|
||||
await withVerifierCleanup(async () => {
|
||||
workspaceFixture.releaseAfter(async () => {
|
||||
await closeVerifierResources([
|
||||
async () => {
|
||||
if (connected) await call("close_binary", {});
|
||||
},
|
||||
() => client.close(),
|
||||
() => transport.close(),
|
||||
]);
|
||||
assert.deepEqual(
|
||||
await readdir(runtime),
|
||||
[],
|
||||
"Owned runtime/project/extension files leaked after close",
|
||||
);
|
||||
verifierRun = await completeVerifierRun(run);
|
||||
assert.equal(verifierRun.process_lineage.status, "verified");
|
||||
assert.deepEqual(verifierRun.process_lineage.descendants, []);
|
||||
});
|
||||
await withVerifierFixture(workspaceFixture, async () => {
|
||||
try {
|
||||
if (mode === "loader-failure") await prepareLoaderFailure();
|
||||
await client.connect(transport);
|
||||
@@ -382,26 +395,7 @@ await withVerifierCleanup(async () => {
|
||||
console.error(stderr);
|
||||
throw error;
|
||||
}
|
||||
}, [
|
||||
async () => {
|
||||
await closeVerifierResources([
|
||||
async () => {
|
||||
if (connected) await call("close_binary", {});
|
||||
},
|
||||
() => client.close(),
|
||||
() => transport.close(),
|
||||
]);
|
||||
assert.deepEqual(
|
||||
await readdir(runtime),
|
||||
[],
|
||||
"Owned runtime/project/extension files leaked after close",
|
||||
);
|
||||
verifierRun = await completeVerifierRun(run);
|
||||
assert.equal(verifierRun.process_lineage.status, "verified");
|
||||
assert.deepEqual(verifierRun.process_lineage.descendants, []);
|
||||
await rm(workspace, { recursive: true, force: true });
|
||||
},
|
||||
]);
|
||||
});
|
||||
const capture = process.env.REA_NATIVEAOT_PROOF_CAPTURE_DIR;
|
||||
if (capture !== undefined) {
|
||||
assert.ok(isAbsolute(capture));
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { execFile } from "node:child_process";
|
||||
import { createHash } from "node:crypto";
|
||||
import { mkdir, mkdtemp, 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";
|
||||
@@ -20,6 +19,10 @@ import {
|
||||
spawnOwnedProviderProcess,
|
||||
} from "../dist/process/ProviderProcess.js";
|
||||
import { completeVerifierRun, createVerifierRun } from "./lib/verifier-run.mjs";
|
||||
import {
|
||||
VerifierFixture,
|
||||
withVerifierFixture,
|
||||
} from "./lib/verifier-fixture.mjs";
|
||||
|
||||
const exec = promisify(execFile);
|
||||
const run = createVerifierRun();
|
||||
@@ -46,11 +49,22 @@ const fixtures = fileURLToPath(
|
||||
new URL("../tests/conformance/ghidra", import.meta.url),
|
||||
);
|
||||
const bridge = fileURLToPath(new URL("../bridge/ghidra", import.meta.url));
|
||||
const workspace = await mkdtemp(join(tmpdir(), "rea-ghidra-noreturn-"));
|
||||
const workspaceFixture = await VerifierFixture.open({
|
||||
prefix: "rea-ghidra-noreturn-",
|
||||
});
|
||||
const workspace = workspaceFixture.path;
|
||||
let cleaned = true;
|
||||
let report;
|
||||
const digest = (bytes) => createHash("sha256").update(bytes).digest("hex");
|
||||
try {
|
||||
// The workspace stays while an owned Ghidra process or server may still use
|
||||
// it: this cleanup confirms only once their cleanup was verified.
|
||||
workspaceFixture.releaseAfter(() => {
|
||||
if (!cleaned)
|
||||
throw new Error(
|
||||
`Owned Ghidra cleanup is unverified; retaining ${workspace}.`,
|
||||
);
|
||||
});
|
||||
await withVerifierFixture(workspaceFixture, async () => {
|
||||
const targetPath = join(
|
||||
workspace,
|
||||
process.platform === "darwin" ? "noreturn.dylib" : "noreturn.so",
|
||||
@@ -215,9 +229,7 @@ try {
|
||||
scope:
|
||||
"Real Ghidra ELF or Mach-O analysis with injected flags; real read-only TCP Evidence projection. No target execution; exact Amethyst artifact and Windows are unverified.",
|
||||
};
|
||||
} finally {
|
||||
if (cleaned) await rm(workspace, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
console.log(
|
||||
JSON.stringify({
|
||||
...report,
|
||||
|
||||
@@ -2,15 +2,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";
|
||||
@@ -34,6 +26,10 @@ import {
|
||||
withVerifierCleanup,
|
||||
closeVerifierResources,
|
||||
} from "./lib/verifier-cleanup.mjs";
|
||||
import {
|
||||
VerifierFixture,
|
||||
withVerifierFixture,
|
||||
} from "./lib/verifier-fixture.mjs";
|
||||
|
||||
if (process.argv.length !== 2)
|
||||
throw new Error("Usage: node scripts/verify-real-ghidra-raw.mjs");
|
||||
@@ -55,7 +51,10 @@ const installation = await inspectVerifierGhidraInstallation({
|
||||
assert.equal(installation.status, "available", JSON.stringify(installation));
|
||||
|
||||
const run = createVerifierRun();
|
||||
const workspace = await mkdtemp(join(tmpdir(), "rea-raw-proof-"));
|
||||
const workspaceFixture = await VerifierFixture.open({
|
||||
prefix: "rea-raw-proof-",
|
||||
});
|
||||
const workspace = workspaceFixture.path;
|
||||
let fixtureCleaned = true;
|
||||
const runtime = join(workspace, "runtime");
|
||||
await mkdir(runtime);
|
||||
@@ -110,7 +109,29 @@ let connected = false;
|
||||
let verifierRun;
|
||||
let report;
|
||||
let searchDefinition;
|
||||
await withVerifierCleanup(async () => {
|
||||
workspaceFixture.releaseAfter(async () => {
|
||||
await closeVerifierResources([
|
||||
async () => {
|
||||
if (connected) await call("close_binary", {});
|
||||
},
|
||||
() => client.close(),
|
||||
() => transport.close(),
|
||||
]);
|
||||
assert.deepEqual(
|
||||
(await readdir(runtime)).filter((name) => name.startsWith("rea-ghidra-")),
|
||||
[],
|
||||
"Owned Ghidra runtime survived close",
|
||||
);
|
||||
assert.equal(
|
||||
fixtureCleaned,
|
||||
true,
|
||||
"Owned Java fixture cleanup remains pending",
|
||||
);
|
||||
verifierRun = await completeVerifierRun(run);
|
||||
assert.equal(verifierRun.process_lineage.status, "verified");
|
||||
assert.deepEqual(verifierRun.process_lineage.descendants, []);
|
||||
});
|
||||
await withVerifierFixture(workspaceFixture, async () => {
|
||||
try {
|
||||
await client.connect(transport);
|
||||
connected = true;
|
||||
@@ -595,31 +616,7 @@ await withVerifierCleanup(async () => {
|
||||
if (stderr.trim()) process.stderr.write(stderr);
|
||||
throw error;
|
||||
}
|
||||
}, [
|
||||
async () => {
|
||||
await closeVerifierResources([
|
||||
async () => {
|
||||
if (connected) await call("close_binary", {});
|
||||
},
|
||||
() => client.close(),
|
||||
() => transport.close(),
|
||||
]);
|
||||
assert.deepEqual(
|
||||
(await readdir(runtime)).filter((name) => name.startsWith("rea-ghidra-")),
|
||||
[],
|
||||
"Owned Ghidra runtime survived close",
|
||||
);
|
||||
assert.equal(
|
||||
fixtureCleaned,
|
||||
true,
|
||||
"Owned Java fixture cleanup remains pending",
|
||||
);
|
||||
verifierRun = await completeVerifierRun(run);
|
||||
assert.equal(verifierRun.process_lineage.status, "verified");
|
||||
assert.deepEqual(verifierRun.process_lineage.descendants, []);
|
||||
await rm(workspace, { recursive: true, force: true });
|
||||
},
|
||||
]);
|
||||
});
|
||||
verifierRun = await completeVerifierRun(run);
|
||||
assert.equal(verifierRun.process_lineage.status, "verified");
|
||||
assert.deepEqual(verifierRun.process_lineage.descendants, []);
|
||||
|
||||
@@ -493,24 +493,16 @@
|
||||
"scripts/verify-published-package.mjs": 1,
|
||||
"scripts/verify-qwen-client.mjs": 1,
|
||||
"scripts/verify-real-cutter.mjs": 1,
|
||||
"scripts/verify-real-ghidra-6502.mjs": 1,
|
||||
"scripts/verify-real-ghidra-address-context.mjs": 1,
|
||||
"scripts/verify-real-ghidra-checkpoints.mjs": 1,
|
||||
"scripts/verify-real-ghidra-com.mjs": 1,
|
||||
"scripts/verify-real-ghidra-decompile-timeout.mjs": 1,
|
||||
"scripts/verify-real-ghidra-decompiler-failure.mjs": 1,
|
||||
"scripts/verify-real-ghidra-dol.mjs": 1,
|
||||
"scripts/verify-real-ghidra-dos.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-nativeaot.mjs": 1,
|
||||
"scripts/verify-real-ghidra-noreturn.mjs": 1,
|
||||
"scripts/verify-real-ghidra-ppc.mjs": 1,
|
||||
"scripts/verify-real-ghidra-project-coverage.mjs": 1,
|
||||
"scripts/verify-real-ghidra-raw.mjs": 1,
|
||||
"scripts/verify-real-ghidra-riscv.mjs": 1,
|
||||
"scripts/verify-real-ghidra-swift.mjs": 1,
|
||||
"scripts/verify-real-ghidra-switch.mjs": 1,
|
||||
@@ -528,12 +520,7 @@
|
||||
"scripts/verify-web-network-captures.mjs": 1,
|
||||
"scripts/verify-windows-native-inspection.mjs": 1,
|
||||
"scripts/verify-windows-native.mjs": 2,
|
||||
"scripts/verify-windows-package.mjs": 1,
|
||||
"scripts/verify/javascript/module-references.mjs": 2,
|
||||
"scripts/verify/javascript/native-handoffs.mjs": 2,
|
||||
"scripts/verify/javascript/output.mjs": 1,
|
||||
"scripts/verify/javascript/recovery.mjs": 1,
|
||||
"scripts/verify/javascript/retained-facts.mjs": 1
|
||||
"scripts/verify-windows-package.mjs": 1
|
||||
},
|
||||
"macho-load-command-literal": {},
|
||||
"lifecycle-plain-throw": {
|
||||
|
||||
@@ -11,7 +11,7 @@ import { Client } from "@modelcontextprotocol/client";
|
||||
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
|
||||
import { artifactCliEvidence } from "../../lib/artifact-e2e.mjs";
|
||||
import { withVerifierCleanup } from "../../lib/verifier-cleanup.mjs";
|
||||
import { PrivateRuntimeRoot } from "../../../dist/process/PrivateRuntimeRoot.js";
|
||||
import { VerifierFixture } from "../../lib/verifier-fixture.mjs";
|
||||
import {
|
||||
OwnedCommandFailure,
|
||||
runOwnedCommand,
|
||||
@@ -82,7 +82,6 @@ const normalize = (rows) =>
|
||||
),
|
||||
}));
|
||||
let temporary;
|
||||
let retainTemporary = false;
|
||||
await withVerifierCleanup(async () => {
|
||||
await client.connect(transport);
|
||||
transport.stderr?.resume();
|
||||
@@ -266,7 +265,7 @@ await withVerifierCleanup(async () => {
|
||||
}
|
||||
let cliParity = null;
|
||||
if (values["cli-parity"]) {
|
||||
temporary = await PrivateRuntimeRoot.create({
|
||||
temporary = await VerifierFixture.open({
|
||||
prefix: "rea-module-reference-verifier-",
|
||||
});
|
||||
const bundlePath = join(temporary.path, "bundle.json");
|
||||
@@ -312,7 +311,7 @@ await withVerifierCleanup(async () => {
|
||||
}
|
||||
let tokenCosts = null;
|
||||
if (values["tokenizer-python"]) {
|
||||
temporary ??= await PrivateRuntimeRoot.create({
|
||||
temporary ??= await VerifierFixture.open({
|
||||
prefix: "rea-module-reference-verifier-",
|
||||
});
|
||||
const input = join(temporary.path, "token-input.json");
|
||||
@@ -357,9 +356,13 @@ print(json.dumps(dict(tokenizer='cl100k_base',tiktoken_version=tiktoken.__versio
|
||||
// An incomplete cleanup carries its supervisor and identity resources in
|
||||
// the error. Preserve its private files for that owner instead of erasing
|
||||
// recovery data while a command may still be using it.
|
||||
retainTemporary =
|
||||
if (
|
||||
cause instanceof OwnedCommandFailure &&
|
||||
cause.cleanupOwner !== undefined;
|
||||
cause.cleanupOwner !== undefined
|
||||
)
|
||||
temporary.retain(
|
||||
`Command cleanup is incomplete; retaining its private input/Evidence files at ${temporary.path}.`,
|
||||
);
|
||||
throw new Error(
|
||||
"Token accounting failed; inspect the owned-command outcome and cleanup status.",
|
||||
{ cause },
|
||||
@@ -392,15 +395,4 @@ print(json.dumps(dict(tokenizer='cl100k_base',tiktoken_version=tiktoken.__versio
|
||||
"First query is after shared artifact analysis; subsequent queries share one MCP session. Legacy item reads are serial; a concurrent legacy baseline is unmeasured. Filesystem/build warmness uncontrolled. Token costs count one post-change catalog, preparation request/summary and each request/structured reply once; client rendering, replay, reasoning, final answer and model latency are unmeasured. Preparation and parity/tokenization overhead are outside per-query elapsed times; external family-resource receipts include the complete verifier and children.",
|
||||
})}\n`,
|
||||
);
|
||||
}, [
|
||||
() => client.close(),
|
||||
() => transport.close(),
|
||||
async () => {
|
||||
if (temporary === undefined) return;
|
||||
if (retainTemporary)
|
||||
throw new Error(
|
||||
`Command cleanup is incomplete; retaining its private input/Evidence files at ${temporary.path}.`,
|
||||
);
|
||||
await temporary.close();
|
||||
},
|
||||
]);
|
||||
}, [() => client.close(), () => transport.close(), () => temporary?.release()]);
|
||||
@@ -11,7 +11,7 @@ import { parseArgs } from "node:util";
|
||||
import { Client } from "@modelcontextprotocol/client";
|
||||
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
|
||||
import { createEvidence } from "../../../dist/domain/evidence.js";
|
||||
import { PrivateRuntimeRoot } from "../../../dist/process/PrivateRuntimeRoot.js";
|
||||
import { VerifierFixture } from "../../lib/verifier-fixture.mjs";
|
||||
import {
|
||||
OwnedCommandFailure,
|
||||
runOwnedCommand,
|
||||
@@ -124,7 +124,6 @@ const simulatedNativeEvidence = (path, sha256) =>
|
||||
},
|
||||
);
|
||||
let temporary;
|
||||
let retainTemporary = false;
|
||||
let report;
|
||||
const ownedCommand = async (command, arguments_, timeoutMs = 60_000) => {
|
||||
try {
|
||||
@@ -141,8 +140,13 @@ const ownedCommand = async (command, arguments_, timeoutMs = 60_000) => {
|
||||
{ timeoutMs, diagnosticBytes: 16 * 1024 * 1024 },
|
||||
);
|
||||
} catch (cause) {
|
||||
retainTemporary ||=
|
||||
cause instanceof OwnedCommandFailure && cause.cleanupOwner !== undefined;
|
||||
if (
|
||||
cause instanceof OwnedCommandFailure &&
|
||||
cause.cleanupOwner !== undefined
|
||||
)
|
||||
temporary.retain(
|
||||
`Command cleanup is incomplete; retaining its private inputs at ${temporary.path}.`,
|
||||
);
|
||||
throw cause;
|
||||
}
|
||||
};
|
||||
@@ -314,7 +318,7 @@ await withVerifierCleanup(async () => {
|
||||
}
|
||||
let cliParity = null;
|
||||
if (values["cli-parity"]) {
|
||||
temporary = await PrivateRuntimeRoot.create({
|
||||
temporary = await VerifierFixture.open({
|
||||
prefix: "rea-native-handoff-verifier-",
|
||||
});
|
||||
const bundlePath = join(temporary.path, "bundle.json");
|
||||
@@ -381,7 +385,7 @@ await withVerifierCleanup(async () => {
|
||||
);
|
||||
let tokenCosts = null;
|
||||
if (values["tokenizer-python"]) {
|
||||
temporary ??= await PrivateRuntimeRoot.create({
|
||||
temporary ??= await VerifierFixture.open({
|
||||
prefix: "rea-native-handoff-verifier-",
|
||||
});
|
||||
const input = join(temporary.path, "token-input.json");
|
||||
@@ -461,15 +465,5 @@ print(json.dumps(dict(tokenizer='cl100k_base',tiktoken_version=tiktoken.__versio
|
||||
assert.ok(safeIdentity, "Native identity/linkage oracle failed");
|
||||
assert.ok(safeApplicability, "File analysis applicability oracle failed");
|
||||
}
|
||||
}, [
|
||||
() => client.close(),
|
||||
() => transport.close(),
|
||||
async () => {
|
||||
if (retainTemporary)
|
||||
throw new Error(
|
||||
`Command cleanup incomplete; retaining owned inputs at ${temporary?.path}.`,
|
||||
);
|
||||
await temporary?.close();
|
||||
},
|
||||
]);
|
||||
}, [() => client.close(), () => transport.close(), () => temporary?.release()]);
|
||||
console.log(serialize(report));
|
||||
@@ -5,7 +5,7 @@ import { once } from "node:events";
|
||||
import { createInterface } from "node:readline";
|
||||
import { Writable } from "node:stream";
|
||||
import { createReadStream, createWriteStream } from "node:fs";
|
||||
import { mkdtemp, rm, stat, statfs } from "node:fs/promises";
|
||||
import { stat, statfs } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { finished } from "node:stream/promises";
|
||||
@@ -16,6 +16,10 @@ import { estimateTokenCount } from "tokenx";
|
||||
import { parseCliOutputArguments } from "../../../dist/cliOutput.js";
|
||||
import { createCliResultOutput } from "../../../dist/cli/cliResultOutput.js";
|
||||
import { createEvidence } from "../../../dist/domain/evidence.js";
|
||||
import {
|
||||
VerifierFixture,
|
||||
withVerifierFixture,
|
||||
} from "../../lib/verifier-fixture.mjs";
|
||||
|
||||
// This opt-in format-boundary check writes one temporary file larger than the
|
||||
// actual engine string limit. Shared leaves keep the analysis input small.
|
||||
@@ -29,7 +33,10 @@ assert.ok(
|
||||
volume.bavail * volume.bsize > count * encodedLeaf.length + 1024 ** 3,
|
||||
"Large-output verification needs room for one output file and a 1-GiB reserve",
|
||||
);
|
||||
const workspace = await mkdtemp(join(tmpdir(), "rea-javascript-output-"));
|
||||
const fixture = await VerifierFixture.open({
|
||||
prefix: "rea-javascript-output-",
|
||||
});
|
||||
const workspace = fixture.path;
|
||||
const path = join(workspace, "result.json");
|
||||
let destination;
|
||||
let peakRss = process.memoryUsage().rss;
|
||||
@@ -40,7 +47,17 @@ const sampler = setInterval(sampleRss, 25);
|
||||
sampler.unref();
|
||||
let finalReport;
|
||||
|
||||
try {
|
||||
// The output stream writes under the root: it closes before the root is
|
||||
// removed.
|
||||
fixture.releaseAfter(async () => {
|
||||
clearInterval(sampler);
|
||||
if (destination !== undefined && !destination.closed) {
|
||||
const closed = once(destination, "close");
|
||||
destination.destroy();
|
||||
await closed;
|
||||
}
|
||||
});
|
||||
await withVerifierFixture(fixture, async () => {
|
||||
const evidence = createEvidence(
|
||||
undefined,
|
||||
{ id: "large-output-fixture", name: "Large output fixture", version: "1" },
|
||||
@@ -189,13 +206,5 @@ try {
|
||||
evidence_id: evidence.evidence_id,
|
||||
rss: { method: "node-memoryUsage-rss-sampled", peak_bytes: peakRss },
|
||||
};
|
||||
} finally {
|
||||
clearInterval(sampler);
|
||||
if (destination !== undefined && !destination.closed) {
|
||||
const closed = once(destination, "close");
|
||||
destination.destroy();
|
||||
await closed;
|
||||
}
|
||||
await rm(workspace, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
process.stdout.write(`${JSON.stringify(finalReport)}\n`);
|
||||
@@ -1,13 +1,12 @@
|
||||
#!/usr/bin/env node
|
||||
import {
|
||||
closeVerifierResources,
|
||||
withVerifierCleanup,
|
||||
} from "../../lib/verifier-cleanup.mjs";
|
||||
VerifierFixture,
|
||||
withVerifierFixture,
|
||||
} from "../../lib/verifier-fixture.mjs";
|
||||
import assert from "node:assert/strict";
|
||||
import { execFile } from "node:child_process";
|
||||
import { createHash } from "node:crypto";
|
||||
import { mkdtemp, readFile, rm } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { dirname, join, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { promisify } from "node:util";
|
||||
@@ -26,7 +25,10 @@ if (!process.env.REA_WAKARU_COMMAND)
|
||||
);
|
||||
const entrypoint =
|
||||
process.argv[2] ?? fileURLToPath(new URL("../../rea.mjs", import.meta.url));
|
||||
const root = await mkdtemp(join(tmpdir(), "rea-real-recovery-"));
|
||||
const rootFixture = await VerifierFixture.open({
|
||||
prefix: "rea-real-recovery-",
|
||||
});
|
||||
const root = rootFixture.path;
|
||||
const environment = { ...process.env, REA_LOG_LEVEL: "silent" };
|
||||
const transport = new StdioClientTransport({
|
||||
command: process.execPath,
|
||||
@@ -55,7 +57,11 @@ const call = async (name, args) => {
|
||||
const fingerprint = (bytes) => createHash("sha256").update(bytes).digest("hex");
|
||||
const counts = [];
|
||||
let finalReport;
|
||||
await withVerifierCleanup(async () => {
|
||||
// The MCP server reads the built fixtures and writes recovered output under
|
||||
// the root: the client and its transport close before the root is removed.
|
||||
rootFixture.releaseAfter(() => client.close());
|
||||
rootFixture.releaseAfter(() => transport.close());
|
||||
await withVerifierFixture(rootFixture, async () => {
|
||||
const fixtures = await buildRecoveryFixtures(
|
||||
root,
|
||||
process.env.REA_JAVASCRIPT_FIXTURE_TOOLS,
|
||||
@@ -169,15 +175,7 @@ await withVerifierCleanup(async () => {
|
||||
results: counts,
|
||||
verified: true,
|
||||
};
|
||||
}, [
|
||||
async () => {
|
||||
await closeVerifierResources([
|
||||
() => client.close(),
|
||||
() => transport.close(),
|
||||
]);
|
||||
await rm(root, { recursive: true, force: true });
|
||||
},
|
||||
]);
|
||||
});
|
||||
console.log(JSON.stringify(finalReport));
|
||||
|
||||
async function assertRecoveredArtifacts(evidence, result, fixture) {
|
||||
|
||||
@@ -10,7 +10,7 @@ import { fileURLToPath } from "node:url";
|
||||
import { parseArgs } from "node:util";
|
||||
import { Client } from "@modelcontextprotocol/client";
|
||||
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
|
||||
import { PrivateRuntimeRoot } from "../../../dist/process/PrivateRuntimeRoot.js";
|
||||
import { VerifierFixture } from "../../lib/verifier-fixture.mjs";
|
||||
import {
|
||||
OwnedCommandFailure,
|
||||
runOwnedCommand,
|
||||
@@ -56,7 +56,6 @@ const client = new Client({
|
||||
version: "1",
|
||||
});
|
||||
let temporary;
|
||||
let retainTemporary = false;
|
||||
let report;
|
||||
const serialize = (value) => JSON.stringify(value);
|
||||
const digest = (value) =>
|
||||
@@ -76,8 +75,13 @@ const ownedCommand = async (command, arguments_) => {
|
||||
{ timeoutMs: 120_000, diagnosticBytes: 16 * 1024 * 1024 },
|
||||
);
|
||||
} catch (cause) {
|
||||
retainTemporary ||=
|
||||
cause instanceof OwnedCommandFailure && cause.cleanupOwner !== undefined;
|
||||
if (
|
||||
cause instanceof OwnedCommandFailure &&
|
||||
cause.cleanupOwner !== undefined
|
||||
)
|
||||
temporary.retain(
|
||||
`Command cleanup is incomplete; retaining its private inputs at ${temporary.path}.`,
|
||||
);
|
||||
throw cause;
|
||||
}
|
||||
};
|
||||
@@ -111,7 +115,7 @@ await withVerifierCleanup(async () => {
|
||||
for await (const chunk of createReadStream(values.artifact))
|
||||
artifactHash.update(chunk);
|
||||
const artifactSha256 = artifactHash.digest("hex");
|
||||
temporary = await PrivateRuntimeRoot.create({
|
||||
temporary = await VerifierFixture.open({
|
||||
prefix: "rea-retained-facts-verifier-",
|
||||
});
|
||||
await client.connect(transport);
|
||||
@@ -343,15 +347,5 @@ await withVerifierCleanup(async () => {
|
||||
conditions:
|
||||
"Fresh MCP and input hashing (file cache warmed); build/OS caches uncontrolled. First export sample includes export plus caller projection code, later samples reuse that immutable export. View samples precede diagnostic export. Task token projections include preparation, first answer, selected/full name/description/inputSchema catalog and the exact baseline program; exec tool schema, host rendering, output schemas, reasoning and billing unmeasured. At-answer cgroup counters include observer/startup overhead, before repeats/export diagnostics/parity/tokenization; final external observer measures all validation work. No application or native provider executes.",
|
||||
};
|
||||
}, [
|
||||
() => client.close(),
|
||||
() => transport.close(),
|
||||
async () => {
|
||||
if (retainTemporary)
|
||||
throw new Error(
|
||||
`Command cleanup incomplete; retaining owned inputs at ${temporary?.path}.`,
|
||||
);
|
||||
await temporary?.close();
|
||||
},
|
||||
]);
|
||||
}, [() => client.close(), () => transport.close(), () => temporary?.release()]);
|
||||
console.log(serialize(report));
|
||||
@@ -192,3 +192,30 @@ it("allocates a plain temporary directory for child scratch space", async () =>
|
||||
await fixture.release();
|
||||
expect(await exists(fixture.path)).toBe(false);
|
||||
});
|
||||
|
||||
it("keeps a retained root across every release and names why", async () => {
|
||||
let closes = 0;
|
||||
const fixture = new VerifierFixture({
|
||||
path: "/fixture/root",
|
||||
close: async () => {
|
||||
closes += 1;
|
||||
},
|
||||
});
|
||||
fixture.retain("Command cleanup is incomplete.");
|
||||
for (const _attempt of [1, 2]) {
|
||||
const failure: unknown = await fixture
|
||||
.release()
|
||||
.catch((cause: unknown) => cause);
|
||||
expect(failure).toBeInstanceOf(AggregateError);
|
||||
expect(
|
||||
((failure as AggregateError).errors as Error[]).map(
|
||||
({ message }) => message,
|
||||
),
|
||||
).toEqual([
|
||||
"Command cleanup is incomplete.",
|
||||
"Retained verifier fixture /fixture/root because a cleanup that may still use it did not confirm.",
|
||||
]);
|
||||
}
|
||||
expect(closes).toBe(0);
|
||||
expect(fixture.released).toBe(false);
|
||||
});
|
||||
Reference in new issue
Block a user