mirror of
https://github.com/morluto/rea
synced 2026-10-26 06:16:17 -04:00
0402012a95
Move command and retained resource lifetimes into a shared verifier owner, keeping package workspace release as a separate dependency. Migrate Pi and Hermes verification to that owner while preserving open pipes, explicit EOF, diagnostics, and retained labs. Require positive writable finalization before accepting EOF delivery. Cover premature close, opaque failures, late ownership delivery, mixed cleanup outcomes, exact retry, and real descendant cleanup with authored process boundaries.
236 lines
9.4 KiB
TypeScript
236 lines
9.4 KiB
TypeScript
import { randomUUID } from "node:crypto";
|
||
import { readFile, writeFile } from "node:fs/promises";
|
||
import { resolve } from "node:path";
|
||
import { setTimeout as delay } from "node:timers/promises";
|
||
import { pathToFileURL } from "node:url";
|
||
import { expect, it } from "vitest";
|
||
import { z } from "zod";
|
||
|
||
import { cleanupOwnedProcessGroup } from "../../../src/process/ProcessOwnership.js";
|
||
import { systemProcessOwnershipHost } from "../../../src/process/ProcessOwnershipObservation.js";
|
||
import { liveProcesses } from "../../../src/process/ProcessOwnershipProcessTree.js";
|
||
import {
|
||
ProviderProcessSupervisor,
|
||
spawnOwnedProviderProcess,
|
||
} from "../../../src/process/ProviderProcess.js";
|
||
import {
|
||
createTestWorkspace,
|
||
removeTestWorkspace,
|
||
} from "../../support/workspace/workspaceFixture.js";
|
||
|
||
const moduleUrl = (path: string) => pathToFileURL(resolve(path)).href;
|
||
|
||
const descendantSource = `
|
||
import { appendFileSync, existsSync } from "node:fs";
|
||
const finish = (reason) => {
|
||
appendFileSync(process.env.NATIVE_EVENTS, JSON.stringify({ kind: "descendant-exit", reason, labExists: existsSync(process.env.NATIVE_LAB) }) + "\\n");
|
||
process.exit(0);
|
||
};
|
||
process.on("SIGTERM", () => finish("SIGTERM"));
|
||
process.send({ pid: process.pid, runId: process.env.REA_PROCESS_RUN_ID });
|
||
setInterval(() => {
|
||
if (existsSync(process.env.NATIVE_RELEASE)) finish("fixture-release");
|
||
}, 25);
|
||
setTimeout(() => finish("fixture-deadline"), 20000);
|
||
`;
|
||
|
||
const leaderSource = `
|
||
import assert from "node:assert/strict";
|
||
import { spawn } from "node:child_process";
|
||
import { appendFileSync, existsSync, fstatSync, writeFileSync } from "node:fs";
|
||
const metadata = fstatSync(0);
|
||
const stdin = { fifo: metadata.isFIFO(), socket: metadata.isSocket(), character: metadata.isCharacterDevice(), file: metadata.isFile() };
|
||
assert.ok(stdin.fifo || stdin.socket, "Native client stdin must retain an actual pipe or socket");
|
||
assert.equal(stdin.character, false);
|
||
assert.equal(stdin.file, false);
|
||
let inputBytes = 0;
|
||
for await (const chunk of process.stdin) inputBytes += chunk.length;
|
||
assert.equal(inputBytes, 0);
|
||
process.once("SIGTERM", () => {
|
||
appendFileSync(process.env.NATIVE_EVENTS, JSON.stringify({ kind: "leader-exit", labExists: existsSync(process.env.NATIVE_LAB) }) + "\\n");
|
||
process.kill(process.pid, "SIGTERM");
|
||
});
|
||
const child = spawn(process.execPath, [process.env.NATIVE_DESCENDANT], {
|
||
stdio: ["ignore", "ignore", "ignore", "ipc"],
|
||
});
|
||
const acknowledged = await new Promise((resolve, reject) => {
|
||
child.once("message", resolve);
|
||
child.once("error", reject);
|
||
});
|
||
writeFileSync(process.env.NATIVE_READY, JSON.stringify({
|
||
pids: [process.pid, acknowledged.pid], stdin, stdinEof: true, inputBytes,
|
||
runId: process.env.REA_PROCESS_RUN_ID, descendantRunId: acknowledged.runId,
|
||
}));
|
||
process.stdout.write("native verifier stdin EOF\\n");
|
||
process.stderr.write("native verifier diagnostics\\n");
|
||
setInterval(() => {}, 1000);
|
||
setTimeout(() => process.exit(1), 20000);
|
||
`;
|
||
|
||
const driverSource = `
|
||
import { existsSync } from "node:fs";
|
||
import { readFile } from "node:fs/promises";
|
||
import { createVerifierCommands } from ${JSON.stringify(moduleUrl("scripts/lib/verify-commands.mjs"))};
|
||
import { systemProcessOwnershipHost } from ${JSON.stringify(moduleUrl("dist/process/ProcessOwnershipObservation.js"))};
|
||
import { liveProcesses } from ${JSON.stringify(moduleUrl("dist/process/ProcessOwnershipProcessTree.js"))};
|
||
let failure;
|
||
const commands = await createVerifierCommands();
|
||
try {
|
||
await commands.exec(process.execPath, [process.env.NATIVE_LEADER], {
|
||
cwd: process.env.NATIVE_LAB, env: process.env, timeout: 1500, maxBuffer: 1024 * 1024,
|
||
stdin: "closed-pipe",
|
||
});
|
||
} catch (cause) {
|
||
failure = { reason: cause.reason, signal: cause.snapshot?.signal, stdout: cause.snapshot?.stdout.text, stderr: cause.snapshot?.stderr.text };
|
||
} finally {
|
||
await commands.close();
|
||
}
|
||
const ready = JSON.parse(await readFile(process.env.NATIVE_READY, "utf8"));
|
||
const liveAtCompletion = liveProcesses(await systemProcessOwnershipHost.listProcesses()).filter(({ pid }) => ready.pids.includes(pid)).map(({ pid }) => pid);
|
||
const artifactAtCompletion = (await readFile(process.env.NATIVE_ARTIFACT)).toString("base64");
|
||
process.stdout.write(JSON.stringify({ ready, failure, liveAtCompletion, labExists: existsSync(process.env.NATIVE_LAB), artifactAtCompletion }));
|
||
`;
|
||
|
||
const reportSchema = z.object({
|
||
ready: z.object({
|
||
pids: z.array(z.number().int().positive()).length(2),
|
||
stdin: z.object({
|
||
fifo: z.boolean(),
|
||
socket: z.boolean(),
|
||
character: z.literal(false),
|
||
file: z.literal(false),
|
||
}),
|
||
stdinEof: z.literal(true),
|
||
inputBytes: z.literal(0),
|
||
runId: z.string().min(1),
|
||
descendantRunId: z.string().min(1),
|
||
}),
|
||
failure: z.object({
|
||
reason: z.literal("timeout"),
|
||
// Canonical cleanup releases verified owned groups with SIGKILL.
|
||
signal: z.literal("SIGKILL"),
|
||
stdout: z.string(),
|
||
stderr: z.string(),
|
||
}),
|
||
liveAtCompletion: z.array(z.number().int().positive()),
|
||
labExists: z.boolean(),
|
||
artifactAtCompletion: z.string(),
|
||
});
|
||
|
||
const awaitAuthoredExit = async (readyPath: string) => {
|
||
const ready = await readFile(readyPath, "utf8").then(
|
||
(text) => reportSchema.shape.ready.parse(JSON.parse(text)),
|
||
(cause: unknown) => {
|
||
if (cause instanceof Error && "code" in cause && cause.code === "ENOENT")
|
||
return undefined;
|
||
throw cause;
|
||
},
|
||
);
|
||
if (ready === undefined) return;
|
||
const deadline = Date.now() + 7_500;
|
||
while (
|
||
liveProcesses(await systemProcessOwnershipHost.listProcesses()).some(
|
||
({ pid }) => ready.pids.includes(pid),
|
||
)
|
||
) {
|
||
if (Date.now() >= deadline)
|
||
throw new Error(
|
||
"Authored native verifier processes did not finish cleanup",
|
||
);
|
||
await delay(25);
|
||
}
|
||
};
|
||
|
||
// The native client is authored; actual stdin, timeout, descendants and census
|
||
// exercise the owned verifier command boundary without onboarding or network access.
|
||
it.skipIf(process.platform === "win32")(
|
||
"joins a timed-out native verifier descendant after pipe EOF while retaining lab artifacts",
|
||
async () => {
|
||
const workspace = await createTestWorkspace("rea-native-verifier-owner-");
|
||
const release = workspace.path("release-descendant");
|
||
const readyPath = workspace.path("ready.json");
|
||
const artifact = Buffer.from("retained native verifier lab α\0\n");
|
||
let acquired:
|
||
| Awaited<ReturnType<typeof spawnOwnedProviderProcess>>
|
||
| undefined;
|
||
let supervisor: ProviderProcessSupervisor | undefined;
|
||
try {
|
||
const lab = await workspace.mkdir("retained lab");
|
||
const artifactPath = await workspace.write(
|
||
"retained lab/evidence.bin",
|
||
artifact,
|
||
);
|
||
const leader = await workspace.write("leader.mjs", leaderSource);
|
||
const descendant = await workspace.write(
|
||
"descendant.mjs",
|
||
descendantSource,
|
||
);
|
||
const driver = await workspace.write("driver.mjs", driverSource);
|
||
acquired = await spawnOwnedProviderProcess({
|
||
command: process.execPath,
|
||
arguments: [driver],
|
||
cwd: process.cwd(),
|
||
runId: `rea-native-verifier-test-${randomUUID()}`,
|
||
sweepTokenOwnedProcesses: true,
|
||
hostEnvironment: {
|
||
...process.env,
|
||
...workspace.environment,
|
||
NATIVE_LAB: lab,
|
||
NATIVE_ARTIFACT: artifactPath,
|
||
NATIVE_LEADER: leader,
|
||
NATIVE_DESCENDANT: descendant,
|
||
NATIVE_READY: readyPath,
|
||
NATIVE_RELEASE: release,
|
||
NATIVE_EVENTS: workspace.path("events.jsonl"),
|
||
},
|
||
});
|
||
const owned = acquired;
|
||
supervisor = new ProviderProcessSupervisor(
|
||
{
|
||
...owned,
|
||
ownsProcessLifetime: true,
|
||
cleanup:
|
||
owned.cleanup ?? (() => cleanupOwnedProcessGroup(owned.ownership)),
|
||
},
|
||
{ maxDiagnosticBytes: 64 * 1024 },
|
||
);
|
||
expect(await supervisor.waitForOutputClose(15_000)).toBe(true);
|
||
const completed = supervisor.snapshot();
|
||
expect(completed.exitCode, completed.stderr.text).toBe(0);
|
||
expect(completed.signal, completed.stderr.text).toBeNull();
|
||
const parsed = reportSchema.safeParse(JSON.parse(completed.stdout.text));
|
||
if (!parsed.success)
|
||
throw new Error(
|
||
`Native verifier report failed validation: ${completed.stdout.text}`,
|
||
{ cause: parsed.error },
|
||
);
|
||
const report = parsed.data;
|
||
expect(report.ready.stdin.fifo || report.ready.stdin.socket).toBe(true);
|
||
expect(report.ready.descendantRunId).toBe(report.ready.runId);
|
||
expect(report.failure.stdout).toBe("native verifier stdin EOF\n");
|
||
expect(report.failure.stderr).toBe("native verifier diagnostics\n");
|
||
expect(report.labExists).toBe(true);
|
||
expect(report.artifactAtCompletion).toBe(artifact.toString("base64"));
|
||
expect(await readFile(artifactPath)).toEqual(artifact);
|
||
expect(report.liveAtCompletion).toEqual([]);
|
||
} finally {
|
||
await writeFile(
|
||
release,
|
||
"release this authored native verifier descendant",
|
||
);
|
||
if (supervisor !== undefined) {
|
||
const stopped = await supervisor.stop();
|
||
if (stopped.status === "incomplete") throw new Error(stopped.reason);
|
||
} else if (acquired !== undefined) {
|
||
const stopped = await (acquired.cleanup === undefined
|
||
? cleanupOwnedProcessGroup(acquired.ownership)
|
||
: acquired.cleanup());
|
||
if (!stopped.cleaned) throw new Error(stopped.reason);
|
||
}
|
||
await awaitAuthoredExit(readyPath);
|
||
await removeTestWorkspace(workspace.root);
|
||
}
|
||
},
|
||
30_000,
|
||
);
|