mirror of
https://github.com/morluto/rea.git
synced 2026-11-12 09:52:27 +00:00
* fix(verifiers): retain native cleanup ownership on failure * fix(verification): load cleanup fixtures through the module resolver CodeQL's local threat model treats file contents passed to vm.SourceTextModule as user-controlled code execution. Import the real verifier modules and redirect their dependencies to static mocks instead of compiling script text read from disk. * fix(verifiers): own CLI and MCP process families * fix(verifiers): retire workspaces after cleanup retry --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com>
124 lines
4.2 KiB
TypeScript
124 lines
4.2 KiB
TypeScript
import { EventEmitter } from "node:events";
|
|
import { access, mkdtemp, rm } from "node:fs/promises";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import { PassThrough } from "node:stream";
|
|
import { expect, it } from "vitest";
|
|
import { createPackageVerifierCommands } from "../../../scripts/lib/verify-package-commands.mjs";
|
|
import { OwnedMcpStdioTransport } from "../../../src/process/OwnedMcpStdioTransport.js";
|
|
import { resolveSwiftcCompileTimeoutMs } from "../../../src/process/DarwinProcessRunTokenReader.js";
|
|
|
|
it("retains the workspace until an owned protocol cleanup can be retried", async () => {
|
|
const path = await mkdtemp(join(tmpdir(), "rea-verifier-transport-"));
|
|
const process = Object.assign(new EventEmitter(), {
|
|
pid: 1,
|
|
stdout: new PassThrough(),
|
|
stderr: new PassThrough(),
|
|
stdin: new PassThrough(),
|
|
exitCode: null,
|
|
signalCode: null,
|
|
kill: () => true,
|
|
});
|
|
let refuse = true;
|
|
const transport = new OwnedMcpStdioTransport(
|
|
{ command: "fixture", arguments: [] },
|
|
{
|
|
provider: "fixture",
|
|
label: "Fixture",
|
|
maxFrameBytes: 1024,
|
|
maxOperationBytes: 4096,
|
|
},
|
|
async ({ runId }) => ({
|
|
process,
|
|
ownership: { runId, leaderPid: 1, processGroupId: 1 },
|
|
cleanup: async () => {
|
|
if (refuse)
|
|
return { cleaned: false, reason: "injected cleanup failure" };
|
|
process.emit("close", 0, null);
|
|
process.stdout.end();
|
|
process.stderr.end();
|
|
process.stdin.end();
|
|
return { cleaned: true, signaled: false };
|
|
},
|
|
}),
|
|
);
|
|
const commands = await createPackageVerifierCommands({
|
|
workspace: {
|
|
path,
|
|
close: () => rm(path, { recursive: true, force: true }),
|
|
},
|
|
});
|
|
commands.adopt(transport, "protocol child");
|
|
try {
|
|
await transport.start();
|
|
// The group can still contain work after the leader's exit.
|
|
process.emit("exit", 0, null);
|
|
await expect(commands.close()).rejects.toMatchObject({
|
|
path,
|
|
cleanupOwner: commands,
|
|
});
|
|
await expect(access(path)).resolves.toBeUndefined();
|
|
refuse = false;
|
|
await commands.close();
|
|
await expect(access(path)).rejects.toMatchObject({ code: "ENOENT" });
|
|
} finally {
|
|
await rm(path, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it(
|
|
"joins an owned protocol process and its descendant before closing",
|
|
async () => {
|
|
const { writeFile, readFile } = await import("node:fs/promises");
|
|
const { createOwnedMcpStdioTransport } =
|
|
await import("../../../scripts/lib/owned-mcp-stdio.mjs");
|
|
const path = await mkdtemp(join(tmpdir(), "rea-verifier-real-transport-"));
|
|
const fixture = join(path, "child.cjs");
|
|
const marker = join(path, "pids.json");
|
|
await writeFile(
|
|
fixture,
|
|
`const {spawn}=require('node:child_process');
|
|
process.on('SIGTERM',()=>{});
|
|
const child=spawn(process.execPath,['-e',"process.on('SIGTERM',()=>{});setInterval(()=>{},1000)"],{stdio:'ignore'});
|
|
require('node:fs').writeFileSync(process.argv[2],JSON.stringify([process.pid,child.pid]));
|
|
setInterval(()=>{},1000);`,
|
|
);
|
|
const transport = await createOwnedMcpStdioTransport({
|
|
command: process.execPath,
|
|
arguments: [fixture, marker],
|
|
hostEnvironment: process.env,
|
|
cwd: process.cwd(),
|
|
expectedCommand: null,
|
|
sweepTokenOwnedProcesses: true,
|
|
});
|
|
try {
|
|
await transport.start();
|
|
const deadline = Date.now() + 10_000;
|
|
let pids: number[] | undefined;
|
|
while (pids === undefined) {
|
|
try {
|
|
pids = JSON.parse(await readFile(marker, "utf8")) as number[];
|
|
} catch (cause) {
|
|
if ((cause as NodeJS.ErrnoException).code !== "ENOENT") throw cause;
|
|
if (Date.now() >= deadline) throw new Error("Child did not start");
|
|
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
}
|
|
}
|
|
expect(pids).toHaveLength(2);
|
|
await transport.close();
|
|
for (const pid of pids)
|
|
expect(() => process.kill(pid, 0)).toThrowError(
|
|
expect.objectContaining({ code: "ESRCH" }),
|
|
);
|
|
} finally {
|
|
await transport.close();
|
|
await rm(path, { recursive: true, force: true });
|
|
}
|
|
},
|
|
Math.min(
|
|
2_147_483_647,
|
|
30_000 +
|
|
(process.platform === "darwin" ? resolveSwiftcCompileTimeoutMs() : 0),
|
|
),
|
|
);
|