Files
reverse-engineering-anything/tests/boundary/process/darwinProcessRunTokenReader.test.ts
T
morluto ab84ba6839 fix(process)!: retain inspection cancellation and cleanup ownership (#10113)
* fix(process): preserve producer-owned inspection cancellation

Prototype the explicit inspection origin and retained file/batch owner protocol. Downstream adapter migration and lease binding remain required before integration.

* fix(process): type inspection ownership handoffs precisely

* fix(process): retain plain inspection preparation cancellation

* fix(process): retain explicit inspection cleanup and cancellation facts

* fix(process): require explicit inspection facts and cleanup scope

* refactor(process): remove redundant cleanup forwarding wrapper

* fix(firmware): retain inspection cancellation and cleanup ownership

* fix(wakaru): retain typed inspection outcomes and caller scope

* fix(ghidra): preserve inspection facts and launch cleanup scope

* fix(providers): preserve typed process inspection outcomes

* refactor(process): remove obsolete inspection cancellation re-export

* fix(native): preserve inspection preparation cleanup and cancellation

* fix(registration): retain typed inspection cleanup during Git discovery

* fix(process): preserve inspection deadline origin in adapter handoff

* fix(process): consume the canonical inspection selector in binding

* fix(native): preserve inspection deadlines and cleanup retry completion

* test(process): narrow the retained Darwin preparation owner

* fix(providers): preserve preparation ownership and timeout policy

Narrow preparation facts through their canonical owner, preserve actual cleanup capabilities and recorded cancellation, and keep enclosing command deadlines authoritative. Retain raw inspection records through Java refusal and configuration projections without changing availability fallback policy.

* refactor(process): use one inspection failure type owner

* fix(providers): retain recorded secondary failures when rebinding

* fix(ghidra): preserve normalized inspection facts through public projection

* fix(process): normalize native process listing cancellation

* docs(development): clarify failure provenance and cleanup verification

* fix(process): preserve direct inspection cancellation facts

* fix(process): retain inspection owners through termination

* fix(process): preserve command inspection cleanup provenance

* fix(desktop): preserve inspection facts after cleanup retry

* refactor(process)!: migrate desktop capture with inspection producers

* fix(desktop)!: preserve capture failure origins and cleanup owners (#10115)

* fix(desktop): preserve capture failure origins and cleanup

* fix(desktop): type keyring cleanup cancellation

* fix(kuna)!: retain copy cleanup before retiring workspaces (#10114)

* fix(kuna): own copy cancellation and descriptor cleanup

* fix(kuna): close copy descriptors before workspace removal

* test(kuna): type copy lifecycle gates for project library

* docs(kuna): explain retained copy-file cleanup and staging ownership

* docs(kuna): separate cache publication, integrity and cleanup guidance

* fix(desktop): retain sampling inspection owners before release

* fix(process): preserve capture file read cancellation origin

* fix(process): retain capture inspection cancellation and cleanup owners

* fix(process): retain sampling inspection failures in capture scope

* fix(process): retain inspection failures through mandatory cleanup

* fix(process): distinguish sampler shutdown from capture cancellation

* fix(browser): retain startup EOF before later cancellation

* fix(process): bind capture inspection cleanup to the owning run

* test(process): migrate cancellation fixtures to typed owners

Record explicit preparation and command cancellation facts, retain exact opaque reasons and physical causes, and guard the mocked version probe from native preparation. Keep the live Wakaru command control while adding a recording equivalent for bounded source verification.

* fix(process): own sampling reads and settle inspection peers

* fix(errors): retain authored recovery through cleanup composition

* fix(process): retain all sampling inspection leaves for final cleanup

* test(browser): report recording cleanup signal outcome

* fix(process): keep inspection error context out of implicit JSON

* fix(desktop): retain normalized sampling cleanup ownership

* test(process): assert tagged startup cancellation
2026-11-11 16:37:48 -05:00

1737 lines
62 KiB
TypeScript

import { isProcessOwnershipInspectionCancellation } from "../../../src/process/ProcessInspectionCancellation.js";
import { execFile, spawn, type ChildProcess } from "node:child_process";
import { once } from "node:events";
import {
access,
chmod,
mkdir,
mkdtemp,
readFile,
readdir,
rm,
symlink,
writeFile,
} from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { fileURLToPath } from "node:url";
import { promisify } from "node:util";
import { _electron as electron } from "playwright-core";
import { expect, it, onTestFinished, vi } from "vitest";
import { z } from "zod";
import { awaitAcknowledgement } from "../../fixtures/lifecycleAcknowledgement.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
import {
createSystemProcessOwnershipHost,
systemProcessOwnershipHost,
} from "../../../src/process/ProcessOwnershipObservation.js";
import {
createDarwinProcessRunTokenReader,
DarwinProcessOwnershipInspectionError,
SWIFTC_INTERRUPT,
} from "../../../src/process/DarwinProcessRunTokenReader.js";
import { execFileOutput } from "../../../src/process/ExecFileOutput.js";
import {
normalizeCaptureFailure,
ProcessCaptureError,
} from "../../../src/process/capture/ProcessCaptureError.js";
import { projectAnalysisError } from "../../../src/domain/analysisErrorProjection.js";
import { PlaywrightElectronActiveProvider } from "../../../src/browser/PlaywrightElectronActiveProvider.js";
import { electronActiveObservationInputSchema } from "../../../src/domain/javascript/electronActiveObservation.js";
const execFileAsync = promisify(execFile);
const onDarwin = process.platform === "darwin";
// Readiness files are polled by another process; expose only complete JSON.
const publishFixtureStateSource = `
const publishFixtureState = (file, value) => {
const pending = file + "." + process.pid + ".pending";
fs.writeFileSync(pending, JSON.stringify(value));
fs.renameSync(pending, file);
};
`;
const writeBlockingCompiler = async (directory: string) => {
const executable = join(directory, "fake-xcrun");
const statePath = join(directory, "compile-state.json");
const releasePath = join(directory, "release-compiler");
const source = `#!/usr/bin/env node
const fs = process.getBuiltinModule("node:fs");
${publishFixtureStateSource}
const path = process.getBuiltinModule("node:path");
const statePath = ${JSON.stringify(statePath)};
const releasePath = ${JSON.stringify(releasePath)};
const state = fs.existsSync(statePath)
? JSON.parse(fs.readFileSync(statePath, "utf8"))
: { calls: 0 };
state.calls += 1;
const outputIndex = process.argv.indexOf("-o");
state.output = process.argv[outputIndex + 1];
state.root = path.dirname(state.output);
state.arguments = process.argv.slice(2);
state.releasePath = releasePath;
publishFixtureState(statePath, state);
if (state.calls === 1) {
const timer = setInterval(() => {
if (!fs.existsSync(releasePath)) return;
clearInterval(timer);
fs.writeFileSync(state.output, "compiled");
}, 10);
} else {
fs.writeFileSync(state.output, "compiled");
}
`;
await writeFile(executable, source);
await chmod(executable, 0o755);
return { executable, statePath, releasePath };
};
const writeBlockingIdentityCompiler = async (directory: string) => {
const executable = join(directory, "fake-identity-xcrun");
const statePath = join(directory, "identity-helper-state.json");
const helperSource = `#!/usr/bin/env node
const fs = process.getBuiltinModule("node:fs");
${publishFixtureStateSource}
publishFixtureState(${JSON.stringify(statePath)}, {
root: process.getBuiltinModule("node:path").dirname(fs.realpathSync(process.argv[1])),
});
setInterval(() => {}, 1000);
`;
const compilerSource = `#!/usr/bin/env node
const fs = process.getBuiltinModule("node:fs");
const outputIndex = process.argv.indexOf("-o");
const output = process.argv[outputIndex + 1];
fs.writeFileSync(output, ${JSON.stringify(helperSource)});
fs.chmodSync(output, 0o755);
`;
await writeFile(executable, compilerSource);
await chmod(executable, 0o755);
return { executable, statePath };
};
const waitForCompileState = async (statePath: string) => {
const deadline = Date.now() + 5_000;
while (Date.now() < deadline) {
try {
const parsed: unknown = JSON.parse(await readFile(statePath, "utf8"));
if (
typeof parsed === "object" &&
parsed !== null &&
"calls" in parsed &&
typeof parsed.calls === "number" &&
"root" in parsed &&
typeof parsed.root === "string" &&
"output" in parsed &&
typeof parsed.output === "string" &&
"releasePath" in parsed &&
typeof parsed.releasePath === "string"
)
return {
calls: parsed.calls,
root: parsed.root,
output: parsed.output,
releasePath: parsed.releasePath,
arguments:
"arguments" in parsed && Array.isArray(parsed.arguments)
? parsed.arguments
: [],
};
} catch {
await new Promise((resolve) => setTimeout(resolve, 10));
}
}
throw new Error("fake compiler did not start");
};
const waitForIdentityHelper = async (statePath: string) => {
const deadline = Date.now() + 5_000;
while (Date.now() < deadline) {
try {
const parsed: unknown = JSON.parse(await readFile(statePath, "utf8"));
if (
typeof parsed === "object" &&
parsed !== null &&
"root" in parsed &&
typeof parsed.root === "string"
)
return { root: parsed.root };
} catch {
await new Promise((resolve) => setTimeout(resolve, 10));
}
}
throw new Error("fake identity helper did not start");
};
type ElectronCaptureResult = Awaited<
ReturnType<PlaywrightElectronActiveProvider["capture"]>
>;
type ElectronCaptureOutcome =
| { readonly state: "result"; readonly value: ElectronCaptureResult }
| { readonly state: "rejected"; readonly cause: unknown };
it("reports an actionable missing Swift compiler without installing it", async () => {
const reader = createDarwinProcessRunTokenReader({
compiler: {
kind: "xcrun",
executable: join(tmpdir(), "rea-no-such-xcrun"),
},
});
try {
await expect(reader.read([{ pid: 1 }])).rejects.toThrow(
/requires the Apple Swift compiler via xcrun/u,
);
} finally {
await reader.close();
}
});
const preparationFailure = async (
reader: ReturnType<typeof createDarwinProcessRunTokenReader>,
): Promise<DarwinProcessOwnershipInspectionError> => {
const cause = await reader.prepare().then(
() => undefined,
(failure: unknown) => failure,
);
expect(cause).toBeInstanceOf(DarwinProcessOwnershipInspectionError);
return cause as DarwinProcessOwnershipInspectionError;
};
it.skipIf(process.platform === "win32")(
"reports the compiler exit code with its diagnostics when preparation fails",
async () => {
const directory = await createTestTempDirectory(
"rea-process-token-compiler-failure-test-",
);
const executable = join(directory, "failing-xcrun");
await writeFile(
executable,
'#!/usr/bin/env node\nprocess.stderr.write("module cache is not writable\\n" + "x".repeat(12000)); process.exitCode = 1;\n',
);
await chmod(executable, 0o755);
const reader = createDarwinProcessRunTokenReader({
compiler: { kind: "xcrun", executable: executable },
});
try {
const failure = await preparationFailure(reader);
expect(failure.compilerOutcome).toMatchObject({
outcome: "exited",
code: 1,
signal: null,
stderr: expect.stringMatching(/^module cache is not writable\n/u),
stderrTruncated: true,
killed: false,
});
expect(failure.message).toMatch(
/exit code 1.*module cache is not writable/iu,
);
expect(failure.message).toMatch(
/diagnostics truncated at 512 bytes.*no runtime override/iu,
);
const normalized = normalizeCaptureFailure(failure);
if (!(normalized instanceof ProcessCaptureError))
throw new Error("Expected a normalized process capture error");
expect(projectAnalysisError(normalized)).toMatchObject({
code: "process_capture_failed",
details: {
execution_failure: expect.stringContaining(
"compiler result: exit code 1",
),
captured_output: {
stderr: expect.stringContaining("module cache is not writable"),
exit_code: 1,
truncated: true,
},
},
});
} finally {
await reader.close();
}
},
);
it.skipIf(process.platform === "win32")(
"reports compiler diagnostics captured before the compilation deadline",
async () => {
const directory = await createTestTempDirectory(
"rea-process-token-compiler-timeout-test-",
);
const executable = join(directory, "hanging-xcrun");
await writeFile(
executable,
'#!/usr/bin/env node\nprocess.stderr.write("error: PCH was compiled with module cache path that differs\\n", () => { setInterval(() => {}, 1000); });\n',
);
await chmod(executable, 0o755);
const reader = createDarwinProcessRunTokenReader({
compiler: { kind: "xcrun", executable: executable },
// Headroom for the child interpreter's cold start under suite load.
compileTimeoutMs: 3_000,
});
try {
const failure = await preparationFailure(reader);
expect(failure.message).toMatch(
/ownership helper.*xcrun.*timed out after 3000 ms/iu,
);
expect(failure.message).toContain("REA_SWIFTC_COMPILE_TIMEOUT_MS");
expect(failure.message).toContain("1..2147483647");
expect(failure.message).toContain("120000 ms");
expect(failure.message).toMatch(/restart REA/i);
expect(failure.message).toMatch(/next ownership-helper compile/i);
expect(failure.message).toContain(
"error: PCH was compiled with module cache path that differs",
);
expect(failure.compilerOutcome).toMatchObject({
outcome: "timed_out",
code: "ETIMEDOUT",
timeoutMs: 3_000,
});
} finally {
await reader.close();
}
},
);
it.skipIf(process.platform === "win32")(
"reports the stop signal when the compiler dies without an exit code",
async () => {
const directory = await createTestTempDirectory(
"rea-process-token-compiler-signal-test-",
);
const executable = join(directory, "segv-xcrun");
await writeFile(
executable,
'#!/usr/bin/env node\nprocess.stderr.write("fatal: segmentation fault during type checking\\n");\nprocess.kill(process.pid, "SIGSEGV");\n',
);
await chmod(executable, 0o755);
const reader = createDarwinProcessRunTokenReader({
compiler: { kind: "xcrun", executable: executable },
});
try {
await expect(preparationFailure(reader)).resolves.toMatchObject({
message: expect.stringMatching(
/xcrun.*SIGSEGV.*fatal: segmentation fault during type checking/i,
),
compilerOutcome: {
outcome: "signaled",
code: null,
signal: "SIGSEGV",
},
});
} finally {
await reader.close();
}
},
);
it.skipIf(process.platform === "win32")(
"bounds embedded compiler diagnostics and marks the truncation",
async () => {
const directory = await createTestTempDirectory(
"rea-process-token-compiler-verbose-test-",
);
const executable = join(directory, "verbose-xcrun");
const diagnostic = "x".repeat(600);
await writeFile(
executable,
`#!/usr/bin/env node\nprocess.stderr.write(${JSON.stringify(`${diagnostic}\n`)}); process.exitCode = 1;\n`,
);
await chmod(executable, 0o755);
const reader = createDarwinProcessRunTokenReader({
compiler: { kind: "xcrun", executable: executable },
});
try {
const failure = await preparationFailure(reader);
const outcome = failure.compilerOutcome;
if (outcome === undefined) throw new Error("Expected compiler outcome");
expect(outcome.stderrBytes).toBe(601);
expect(outcome.stderrTruncated).toBe(false);
expect(failure.message).toContain(
`: ${"x".repeat(512)}… [diagnostics truncated at 512 bytes; this release has no runtime override]`,
);
const excerpt = failure.message.split("): ")[1]?.split("…")[0];
expect(excerpt).toBe("x".repeat(512));
expect(Buffer.byteLength(excerpt ?? "", "utf8")).toBeLessThanOrEqual(512);
expect(Buffer.byteLength(outcome.stderr, "utf8")).toBeLessThanOrEqual(
8 * 1024,
);
} finally {
await reader.close();
}
},
);
it
.skipIf(!onDarwin || process.getuid?.() === 0)
.each(["file", "read-only directory"])(
"ignores unusable inherited Swift module cache: %s",
async (kind) => {
const directory = await createTestTempDirectory(
"rea-process-token-inherited-cache-test-",
);
const cacheParent = join(directory, "cache-parent");
if (kind === "file") await writeFile(cacheParent, "not a directory");
else {
await mkdir(cacheParent);
await chmod(cacheParent, 0o500);
}
const cachePath = join(cacheParent, "modules");
vi.stubEnv("CLANG_MODULE_CACHE_PATH", cachePath);
const reader = createDarwinProcessRunTokenReader();
try {
const executable = await reader.prepare();
const { stdout } = await execFileOutput(
executable,
["--identities", String(process.pid)],
{ timeout: 15_000 },
);
expect(JSON.parse(stdout)).toMatchObject({
results: [{ pid: process.pid, state: "readable" }],
});
expect(process.env.CLANG_MODULE_CACHE_PATH).toBe(cachePath);
} finally {
vi.unstubAllEnvs();
if (kind === "read-only directory") await chmod(cacheParent, 0o700);
await reader.close();
}
},
);
it.skipIf(process.platform === "win32")(
"uses the compiler-managed module cache instead of rebuilding system modules per REA process",
async () => {
const directory = await createTestTempDirectory(
"rea-process-token-module-cache-test-",
);
const fakeCompiler = await writeBlockingCompiler(directory);
const reader = createDarwinProcessRunTokenReader({
compiler: { kind: "xcrun", executable: fakeCompiler.executable },
});
const preparation = reader.prepare();
try {
const state = await waitForCompileState(fakeCompiler.statePath);
expect(state.arguments).not.toContain("-module-cache-path");
await writeFile(fakeCompiler.releasePath, "release");
await preparation;
} finally {
await Promise.all([reader.close(), Promise.allSettled([preparation])]);
}
},
);
const writeCacheRecordingCompiler = async (directory: string) => {
const executable = join(directory, "fake-cache-xcrun");
const statePath = join(directory, "cache-compile-state.json");
const source = `#!/usr/bin/env node
const fs = process.getBuiltinModule("node:fs");
${publishFixtureStateSource}
const path = process.getBuiltinModule("node:path");
const flagIndex = process.argv.indexOf("-module-cache-path");
const cache = flagIndex === -1 ? undefined : process.argv[flagIndex + 1];
publishFixtureState(${JSON.stringify(statePath)}, {
arguments: process.argv.slice(2),
moduleCacheEnvironment: process.env.CLANG_MODULE_CACHE_PATH ?? null,
});
if (cache !== undefined) {
fs.mkdirSync(cache, { recursive: true });
fs.writeFileSync(path.join(cache, "written-into-cache"), "kept");
}
const output = process.argv[process.argv.indexOf("-o") + 1];
fs.writeFileSync(output, "#!/bin/sh\\nexit 0\\n");
fs.chmodSync(output, 0o755);
`;
await writeFile(executable, source);
await chmod(executable, 0o755);
return { executable, statePath };
};
const readCacheCompileState = async (statePath: string) =>
JSON.parse(await readFile(statePath, "utf8")) as {
arguments: string[];
moduleCacheEnvironment: string | null;
};
const existsPath = async (target: string) =>
access(target).then(
() => true,
() => false,
);
it
.skipIf(process.platform === "win32")
.each([
"selected environment",
"selected environment and deadline",
"default environment",
] as const)(
"captures the compiler and helper environment from %s",
async (selection) => {
const directory = await createTestTempDirectory(
"rea-process-token-selected-environment-",
);
const developerDirectory = join(directory, "selected Xcode.app");
const compileState = join(directory, "compile-environment.json");
const executable = join(directory, "fixture-xcrun");
const helper = `#!${process.execPath}
console.log(JSON.stringify({ results: process.argv.slice(2).map(pid => ({
pid: Number(pid), state: "readable", run_id: process.env.DEVELOPER_DIR,
})) }));
`;
await writeFile(
executable,
`#!${process.execPath}
const fs = process.getBuiltinModule("node:fs");
fs.writeFileSync(${JSON.stringify(compileState)}, JSON.stringify({
developerDirectory: process.env.DEVELOPER_DIR ?? null,
}));
const output = process.argv[process.argv.indexOf("-o") + 1];
fs.writeFileSync(output, ${JSON.stringify(helper)});
fs.chmodSync(output, 0o755);
`,
{ mode: 0o755 },
);
onTestFinished(() => {
vi.unstubAllEnvs();
});
vi.stubEnv("DEVELOPER_DIR", developerDirectory);
const environment = { ...process.env };
const compiler = { kind: "xcrun" as const, executable };
const reader =
selection === "default environment"
? createDarwinProcessRunTokenReader({ compiler })
: createSystemProcessOwnershipHost("darwin", environment, {
darwinCompiler: compiler,
...(selection === "selected environment and deadline"
? { compileTimeoutMs: 15_000 }
: {}),
});
environment.DEVELOPER_DIR = join(directory, "later caller selection");
vi.stubEnv("DEVELOPER_DIR", join(directory, "later ambient selection"));
try {
await reader.prepare?.(AbortSignal.timeout(15_000));
expect(JSON.parse(await readFile(compileState, "utf8"))).toEqual({
developerDirectory,
});
const processes = [
{
pid: 123,
parentPid: 1,
processGroupId: 123,
state: "S",
command: "fixture",
},
];
const observations =
"read" in reader
? await reader.read(processes)
: await reader.runTokens?.(processes);
expect(observations?.get(123)).toEqual({
state: "readable",
runId: developerDirectory,
});
} finally {
await reader.close?.();
}
},
);
it.skipIf(process.platform === "win32")(
"forwards a caller-owned module cache and keeps it after the helper root is removed",
async () => {
const directory = await createTestTempDirectory(
"rea-process-token-caller-cache-test-",
);
const cachePath = join(directory, "caller", " modules ");
const fakeCompiler = await writeCacheRecordingCompiler(directory);
const reader = createDarwinProcessRunTokenReader({
compiler: { kind: "xcrun", executable: fakeCompiler.executable },
moduleCachePath: cachePath,
});
let executable: string;
try {
executable = await reader.prepare();
} finally {
await reader.close();
}
const state = await readCacheCompileState(fakeCompiler.statePath);
const flagIndex = state.arguments.indexOf("-module-cache-path");
expect(state.arguments[0]).toBe("swiftc");
expect(flagIndex).toBe(1);
expect(state.arguments[flagIndex + 1]).toBe(cachePath);
expect(state.moduleCacheEnvironment).toBe(cachePath);
expect(await existsPath(join(cachePath, "written-into-cache"))).toBe(true);
expect(await existsPath(executable)).toBe(false);
},
);
it.skipIf(process.platform === "win32")(
"selects the caller module cache through REA_DARWIN_MODULE_CACHE_PATH",
async () => {
const directory = await createTestTempDirectory(
"rea-process-token-host-cache-test-",
);
const cachePath = join(directory, "from-environment", "modules");
const fakeCompiler = await writeCacheRecordingCompiler(directory);
const host = createSystemProcessOwnershipHost(
"darwin",
{ ...process.env, REA_DARWIN_MODULE_CACHE_PATH: cachePath },
{
darwinCompiler: { kind: "xcrun", executable: fakeCompiler.executable },
},
);
try {
await host.prepare?.(AbortSignal.timeout(15_000));
const state = await readCacheCompileState(fakeCompiler.statePath);
const flagIndex = state.arguments.indexOf("-module-cache-path");
expect(state.arguments[flagIndex + 1]).toBe(cachePath);
} finally {
await host.close?.();
}
},
);
it.skipIf(process.platform === "win32")(
"keeps the shared module cache for the system host without the setting",
async () => {
const directory = await createTestTempDirectory(
"rea-process-token-host-default-test-",
);
vi.stubEnv("CLANG_MODULE_CACHE_PATH", join(directory, "ambient-cache"));
const hostEnvironment = { ...process.env };
delete hostEnvironment.REA_DARWIN_MODULE_CACHE_PATH;
const fakeCompiler = await writeCacheRecordingCompiler(directory);
const host = createSystemProcessOwnershipHost("darwin", hostEnvironment, {
darwinCompiler: { kind: "xcrun", executable: fakeCompiler.executable },
});
try {
await host.prepare?.(AbortSignal.timeout(15_000));
const state = await readCacheCompileState(fakeCompiler.statePath);
expect(state.arguments).not.toContain("-module-cache-path");
expect(state.moduleCacheEnvironment).toBeNull();
} finally {
await host.close?.();
}
},
);
it.skipIf(process.platform === "win32")(
"bypasses xcrun for an explicitly selected Swift compiler and SDK",
async () => {
const directory = await createTestTempDirectory(
"rea-process-token-direct-compiler-",
);
const fakeCompiler = await writeCacheRecordingCompiler(directory);
const sdkRoot = join(directory, "MacOSX.sdk");
const cachePath = join(directory, "modules");
const host = createSystemProcessOwnershipHost("darwin", {
...process.env,
REA_DARWIN_SWIFTC_PATH: fakeCompiler.executable,
SDKROOT: sdkRoot,
REA_DARWIN_MODULE_CACHE_PATH: cachePath,
});
try {
await host.prepare?.(AbortSignal.timeout(15_000));
const state = await readCacheCompileState(fakeCompiler.statePath);
expect(state.arguments.slice(0, 2)).toEqual(["-sdk", sdkRoot]);
expect(state.arguments).not.toContain("swiftc");
expect(state.arguments).toContain(cachePath);
} finally {
await host.close?.();
}
},
);
it.skipIf(process.platform === "win32")(
"reports the missing SDK prerequisite before starting a direct compiler",
async () => {
const directory = await createTestTempDirectory(
"rea-process-token-direct-no-sdk-",
);
const fakeCompiler = await writeCacheRecordingCompiler(directory);
const host = createSystemProcessOwnershipHost("darwin", {
...process.env,
REA_DARWIN_SWIFTC_PATH: fakeCompiler.executable,
SDKROOT: "",
});
try {
await expect(
host.prepare?.(AbortSignal.timeout(15_000)),
).rejects.toMatchObject({
name: "DarwinProcessOwnershipInspectionError",
message: expect.stringContaining("REA_DARWIN_SWIFTC_PATH and SDKROOT"),
});
expect(await existsPath(fakeCompiler.statePath)).toBe(false);
} finally {
await host.close?.();
}
},
);
it.skipIf(process.platform === "win32")(
"identifies a missing selected Swift compiler rather than xcrun",
async () => {
const directory = await createTestTempDirectory(
"rea-process-token-direct-missing-",
);
const host = createSystemProcessOwnershipHost("darwin", {
...process.env,
REA_DARWIN_SWIFTC_PATH: join(directory, "missing-swiftc"),
SDKROOT: join(directory, "MacOSX.sdk"),
});
try {
await expect(
host.prepare?.(AbortSignal.timeout(15_000)),
).rejects.toMatchObject({
message: expect.stringContaining(
"selected Swift compiler (REA_DARWIN_SWIFTC_PATH)",
),
compilerOutcome: expect.objectContaining({
outcome: "spawn_failed",
code: "ENOENT",
}),
});
} finally {
await host.close?.();
}
},
);
it.skipIf(!onDarwin)(
"compiles the helper against the caller cache with the host compiler",
async () => {
const directory = await createTestTempDirectory(
"rea-process-token-real-cache-test-",
);
const cachePath = join(directory, "caller", "modules");
const { stdout: compilerPath } = await execFileAsync("/usr/bin/xcrun", [
"--find",
"swiftc",
]);
const { stdout: sdkRoot } = await execFileAsync("/usr/bin/xcrun", [
"--show-sdk-path",
]);
const host = createSystemProcessOwnershipHost("darwin", {
...process.env,
REA_DARWIN_MODULE_CACHE_PATH: cachePath,
REA_DARWIN_SWIFTC_PATH: compilerPath.trim(),
SDKROOT: sdkRoot.trim(),
});
try {
await host.prepare?.(AbortSignal.timeout(180_000));
const current = (await host.listProcesses()).find(
({ pid }) => pid === process.pid,
);
if (current === undefined)
throw new Error("Current process missing from host snapshot");
if (host.processIdentities === undefined)
throw new Error("Darwin host omitted native identity inspection");
const identity = (await host.processIdentities([current])).get(
process.pid,
);
expect(identity).toMatchObject({
state: "readable",
relation: {
state: "observed",
parentPid: process.ppid,
processGroupId: current.processGroupId,
},
});
if (identity?.state !== "readable")
throw new Error("Current native identity unavailable");
expect(identity.identity).not.toBe("");
expect(
(await host.processIdentities([current])).get(process.pid),
).toMatchObject({
state: "readable",
identity: identity.identity,
});
const entries = await readdir(cachePath);
expect(
entries.some(
(name) =>
name.endsWith(".swiftmodule") ||
name.endsWith(".pcm") ||
name === "Modules",
),
).toBe(true);
} finally {
await host.close?.();
}
expect(await existsPath(join(directory, "caller"))).toBe(true);
},
);
it.skipIf(process.platform === "win32")(
"rethrows compile cancellation, removes its temporary root, and permits retry",
async () => {
const directory = await mkdtemp(
join(tmpdir(), "rea-process-token-abort-test-"),
);
const fakeCompiler = await writeBlockingCompiler(directory);
const reader = createDarwinProcessRunTokenReader({
compiler: { kind: "xcrun", executable: fakeCompiler.executable },
});
const controller = new AbortController();
const compilation = reader.prepare(controller.signal).then(
() => ({ state: "fulfilled" as const }),
(cause: unknown) => ({ state: "rejected" as const, cause }),
);
try {
const firstAttempt = await waitForCompileState(fakeCompiler.statePath);
controller.abort();
const outcome = await compilation;
expect(outcome.state).toBe("rejected");
if (outcome.state !== "rejected")
throw new Error(
"aborted native reader compilation unexpectedly succeeded",
);
expect(isProcessOwnershipInspectionCancellation(outcome.cause)).toBe(
true,
);
if (!isProcessOwnershipInspectionCancellation(outcome.cause))
throw new Error("Expected producer-owned inspection cancellation");
expect(outcome.cause.reason).toBe(controller.signal.reason);
expect(normalizeCaptureFailure(outcome.cause)).toMatchObject({
reason: "cancelled",
userCategory: "cancelled",
});
await expect(access(firstAttempt.root)).rejects.toThrow();
await reader.prepare();
const retry = await waitForCompileState(fakeCompiler.statePath);
expect(retry).toMatchObject({ calls: 2 });
await expect(access(retry.output)).resolves.toBeUndefined();
await reader.close();
await expect(access(retry.root)).rejects.toThrow();
} finally {
if (!controller.signal.aborted) controller.abort();
await compilation;
await reader.close();
await rm(directory, { recursive: true, force: true });
}
},
);
const temporaryStateSchema = z.object({
root: z.string(),
temporary: z.string(),
});
/**
* A compiler whose detached job closes standard streams but retains fd 3,
* writes an object after the SIGINT-stopped driver exits, then closes fd 3.
*/
const writeTemporaryObjectCompiler = async (directory: string) => {
const executable = join(directory, "fake-temporary-xcrun");
const statePath = join(directory, "temporary-state.json");
const source = `#!/usr/bin/env node
const fs = process.getBuiltinModule("node:fs");
${publishFixtureStateSource}
const os = process.getBuiltinModule("node:os");
const path = process.getBuiltinModule("node:path");
const { spawn } = process.getBuiltinModule("node:child_process");
const temporary = fs.mkdtempSync(path.join(os.tmpdir(), "TemporaryDirectory."));
const object = path.join(temporary, "ProcessRunTokenReader-1.o");
let lifetime = "ignore";
try { if (fs.fstatSync(3).isSocket()) lifetime = 3; } catch {}
const job = spawn(process.execPath, ["-e",
"setTimeout(() => { const fs = process.getBuiltinModule('node:fs'); fs.mkdirSync(" + JSON.stringify(temporary) +
", { recursive: true }); fs.writeFileSync(" + JSON.stringify(object) + ", 'object'); }, 300)"],
{ detached: true, stdio: ["ignore", "ignore", "ignore", lifetime] });
job.unref();
process.on("SIGINT", () => {
process.exit(130);
});
const output = process.argv[process.argv.indexOf("-o") + 1];
publishFixtureState(${JSON.stringify(statePath)}, { root: path.dirname(output), temporary });
setInterval(() => {}, 1000);
`;
await writeFile(executable, source);
await chmod(executable, 0o755);
return { executable, statePath };
};
it.skipIf(process.platform === "win32")(
"removes cancelled compiler temporaries with the helper root",
async () => {
const directory = await createTestTempDirectory(
"rea-process-token-temporary-test-",
);
const compiler = await writeTemporaryObjectCompiler(directory);
const reader = createDarwinProcessRunTokenReader({
compiler: { kind: "xcrun", executable: compiler.executable },
});
const controller = new AbortController();
const preparation = reader.prepare(controller.signal).then(
() => undefined,
(cause: unknown) => cause,
);
let temporary: string | undefined;
try {
const deadline = Date.now() + 5_000;
let state: z.infer<typeof temporaryStateSchema> | undefined;
while (state === undefined && Date.now() < deadline) {
const parsed = temporaryStateSchema.safeParse(
await readFile(compiler.statePath, "utf8").then(
(text): unknown => JSON.parse(text),
() => undefined,
),
);
if (parsed.success) state = parsed.data;
else await new Promise((resolve) => setTimeout(resolve, 10));
}
if (state === undefined) throw new Error("fake compiler did not start");
temporary = state.temporary;
expect(state.temporary.startsWith(`${state.root}/`)).toBe(true);
controller.abort();
const cancelled = await preparation;
expect(isProcessOwnershipInspectionCancellation(cancelled)).toBe(true);
if (!isProcessOwnershipInspectionCancellation(cancelled))
throw new Error("Expected producer-owned inspection cancellation");
expect(cancelled.reason).toBe(controller.signal.reason);
await reader.close();
// A job that outlived the compiler would recreate the root by now.
await new Promise((resolve) => setTimeout(resolve, 600));
await expect(access(state.root)).rejects.toThrow();
} finally {
if (!controller.signal.aborted) controller.abort();
await preparation;
await reader.close();
if (temporary !== undefined)
await rm(temporary, { recursive: true, force: true });
}
},
);
it.skipIf(process.platform === "win32").each(["first", "second"] as const)(
"keeps shared compilation alive when the %s waiter cancels",
async (cancelledWaiter) => {
const directory = await mkdtemp(
join(tmpdir(), "rea-process-token-shared-compile-test-"),
);
const fakeCompiler = await writeBlockingCompiler(directory);
const reader = createDarwinProcessRunTokenReader({
compiler: { kind: "xcrun", executable: fakeCompiler.executable },
});
const firstController = new AbortController();
const secondController = new AbortController();
const firstWaiter = reader.prepare(firstController.signal).then(
(value) => ({ state: "fulfilled" as const, value }),
(cause: unknown) => ({ state: "rejected" as const, cause }),
);
const secondWaiterPromise = (async () => {
const state = await waitForCompileState(fakeCompiler.statePath);
const secondWaiter = reader.prepare(secondController.signal).then(
(value) => ({ state: "fulfilled" as const, value }),
(cause: unknown) => ({ state: "rejected" as const, cause }),
);
const cancelled =
cancelledWaiter === "first" ? firstWaiter : secondWaiter;
const surviving =
cancelledWaiter === "first" ? secondWaiter : firstWaiter;
const cancelledController =
cancelledWaiter === "first" ? firstController : secondController;
cancelledController.abort();
const firstOutcome = await cancelled;
expect(firstOutcome.state).toBe("rejected");
if (firstOutcome.state !== "rejected")
throw new Error("cancelled compilation waiter unexpectedly succeeded");
expect(isProcessOwnershipInspectionCancellation(firstOutcome.cause)).toBe(
true,
);
if (!isProcessOwnershipInspectionCancellation(firstOutcome.cause))
throw new Error("Expected producer-owned inspection cancellation");
expect(firstOutcome.cause.reason).toBe(cancelledController.signal.reason);
await expect(access(state.root)).resolves.toBeUndefined();
await writeFile(fakeCompiler.releasePath, "release");
return { state, outcome: await surviving };
})();
try {
const { state, outcome } = await secondWaiterPromise;
expect(state.calls).toBe(1);
expect(outcome.state).toBe("fulfilled");
if (outcome.state !== "fulfilled") throw outcome.cause;
expect(outcome.value).toBe(state.output);
await reader.close();
await expect(access(state.root)).rejects.toThrow();
} finally {
if (!firstController.signal.aborted) firstController.abort();
if (!secondController.signal.aborted) secondController.abort();
await Promise.allSettled([firstWaiter, secondWaiterPromise]);
await reader.close();
await rm(directory, { recursive: true, force: true });
}
},
);
it
.skipIf(process.platform === "win32" || process.getuid?.() === 0)
.each(["reader-close", "exact-root"] as const)(
"retains a failed compilation cleanup for a later %s retry",
async (retry) => {
const directory = await mkdtemp(
join(tmpdir(), "rea-process-token-cleanup-retry-test-"),
);
const fakeCompiler = await writeBlockingCompiler(directory);
const reader = createDarwinProcessRunTokenReader({
compiler: { kind: "xcrun", executable: fakeCompiler.executable },
});
const controller = new AbortController();
const preparation = reader.prepare(controller.signal).then(
() => ({ state: "fulfilled" as const }),
(cause: unknown) => ({ state: "rejected" as const, cause }),
);
let compilationRoot: string | undefined;
try {
const state = await waitForCompileState(fakeCompiler.statePath);
compilationRoot = state.root;
await writeFile(
join(state.root, "retained.txt"),
"cleanup must remain tracked",
);
await chmod(state.root, 0o000);
controller.abort();
const outcome = await preparation;
expect(outcome.state).toBe("rejected");
if (retry === "reader-close") {
await expect(reader.close()).rejects.toThrow();
await chmod(state.root, 0o700);
await reader.close();
} else {
if (
outcome.state !== "rejected" ||
!(outcome.cause instanceof DarwinProcessOwnershipInspectionError)
)
throw new Error("Expected owned compilation cleanup failure");
const failure = outcome.cause;
expect(failure.cleanupOwner).toBeDefined();
expect(failure.cleanupFailure?.cleanupIncomplete).toBe(true);
await expect(failure.cleanupOwner?.close()).rejects.toThrow();
await chmod(state.root, 0o700);
await failure.cleanupOwner?.close();
expect(failure.cleanupFailure?.cleanupIncomplete).toBe(false);
// Releasing the failed root does not close the shared reader itself.
const stopped = new AbortController();
const reason = { request: "probe reader remains open" };
stopped.abort(reason);
const cancelled: unknown = await reader
.prepare(stopped.signal)
.catch((cause: unknown) => cause);
expect(isProcessOwnershipInspectionCancellation(cancelled)).toBe(true);
if (!isProcessOwnershipInspectionCancellation(cancelled))
throw new Error("Expected producer-owned inspection cancellation");
expect(cancelled.reason).toBe(reason);
}
await expect(access(state.root)).rejects.toThrow();
} finally {
if (!controller.signal.aborted) controller.abort();
await preparation;
if (compilationRoot !== undefined)
await chmod(compilationRoot, 0o700).catch(() => undefined);
await reader.close();
await rm(directory, { recursive: true, force: true });
}
},
);
it.skipIf(process.platform === "win32")(
"aborts and cleans an active compilation when the reader closes",
async () => {
const directory = await mkdtemp(
join(tmpdir(), "rea-process-token-close-compile-test-"),
);
const fakeCompiler = await writeBlockingCompiler(directory);
const reader = createDarwinProcessRunTokenReader({
compiler: { kind: "xcrun", executable: fakeCompiler.executable },
});
const preparation = reader.prepare().then(
() => ({ state: "fulfilled" as const }),
(cause: unknown) => ({ state: "rejected" as const, cause }),
);
try {
const state = await waitForCompileState(fakeCompiler.statePath);
await reader.close();
const outcome = await preparation;
expect(outcome.state).toBe("rejected");
if (outcome.state !== "rejected")
throw new Error("closed reader compilation unexpectedly succeeded");
expect(isProcessOwnershipInspectionCancellation(outcome.cause)).toBe(
true,
);
if (!isProcessOwnershipInspectionCancellation(outcome.cause))
throw new Error("Expected producer-owned inspection cancellation");
expect(outcome.cause.reason).toBeInstanceOf(DOMException);
if (!(outcome.cause.reason instanceof DOMException))
throw new Error("Expected the reader's default abort reason");
expect(outcome.cause.reason.name).toBe("AbortError");
await expect(access(state.root)).rejects.toThrow();
await expect(reader.prepare()).rejects.toMatchObject({
_tag: "ProcessAdmissionError",
code: "reader_closed",
});
await expect(reader.close()).resolves.toBeUndefined();
} finally {
await Promise.allSettled([preparation]);
await reader.close();
await rm(directory, { recursive: true, force: true });
}
},
);
it.skipIf(process.platform === "win32")(
"maps cancellation during native preparation before Electron launch",
async () => {
const directory = await mkdtemp(
join(tmpdir(), "rea-electron-token-abort-test-"),
);
const fakeCompiler = await writeBlockingCompiler(directory);
const reader = createDarwinProcessRunTokenReader({
compiler: { kind: "xcrun", executable: fakeCompiler.executable },
});
const prepareSpy = vi
.spyOn(systemProcessOwnershipHost, "prepare")
.mockImplementation(async (signal) => {
await reader.prepare(signal);
});
const launchSpy = vi.spyOn(electron, "launch");
const controller = new AbortController();
let captureOutcome: Promise<ElectronCaptureOutcome> | undefined;
try {
const input = electronActiveObservationInputSchema.parse({
executable_path: "/missing/electron",
application_path: "/missing/application.js",
});
captureOutcome = new PlaywrightElectronActiveProvider({})
.capture(
{ ...input, application_root: process.cwd() },
{ signal: controller.signal },
)
.then(
(value) => ({ state: "result" as const, value }),
(cause: unknown) => ({ state: "rejected" as const, cause }),
);
const compileState = await waitForCompileState(fakeCompiler.statePath);
controller.abort();
const outcome = await captureOutcome;
expect(outcome.state).toBe("result");
if (outcome.state !== "result") throw outcome.cause;
expect(outcome.value).toMatchObject({
ok: false,
error: { reason: "cancelled", userCategory: "cancelled" },
});
expect(launchSpy).not.toHaveBeenCalled();
await expect(access(compileState.root)).rejects.toThrow();
} finally {
if (!controller.signal.aborted) controller.abort();
if (captureOutcome !== undefined) await captureOutcome;
prepareSpy.mockRestore();
launchSpy.mockRestore();
await reader.close();
await rm(directory, { recursive: true, force: true });
}
},
);
it.skipIf(process.platform === "win32").each(["baseline", "tokens"] as const)(
"preserves cancellation through system-host %s inspection",
async (kind) => {
const directory = await mkdtemp(
join(tmpdir(), "rea-process-baseline-abort-test-"),
);
const fakeCompiler = await writeBlockingIdentityCompiler(directory);
const host = createSystemProcessOwnershipHost("darwin", process.env, {
darwinCompiler: { kind: "xcrun", executable: fakeCompiler.executable },
});
const controller = new AbortController();
let helperRoot: string | undefined;
const inspection =
kind === "baseline"
? host.captureBaseline?.(controller.signal)
: host.runTokens?.([{ pid: process.pid }], controller.signal);
const baseline =
inspection?.then(
() => ({ state: "fulfilled" as const }),
(cause: unknown) => ({ state: "rejected" as const, cause }),
) ?? Promise.resolve({ state: "missing" as const });
try {
if (host.captureBaseline === undefined)
throw new Error("Darwin host did not expose process baselines");
const helper = await waitForIdentityHelper(fakeCompiler.statePath);
helperRoot = helper.root;
controller.abort();
const outcome = await baseline;
expect(outcome.state).toBe("rejected");
if (outcome.state !== "rejected")
throw new Error("aborted identity inspection unexpectedly succeeded");
expect(isProcessOwnershipInspectionCancellation(outcome.cause)).toBe(
true,
);
if (!isProcessOwnershipInspectionCancellation(outcome.cause))
throw new Error("Expected producer-owned inspection cancellation");
expect(outcome.cause.reason).toBe(controller.signal.reason);
expect(normalizeCaptureFailure(outcome.cause)).toMatchObject({
reason: "cancelled",
userCategory: "cancelled",
});
await expect(access(helper.root)).resolves.toBeUndefined();
} finally {
if (!controller.signal.aborted) controller.abort();
await baseline;
await host.close?.();
if (helperRoot !== undefined)
await expect(access(helperRoot)).rejects.toThrow();
await rm(directory, { recursive: true, force: true });
}
},
);
it.skipIf(process.platform === "win32")(
"passes Electron cancellation into capture-local baseline inspection",
async () => {
const directory = await mkdtemp(
join(tmpdir(), "rea-electron-baseline-abort-test-"),
);
const fakeCompiler = await writeBlockingIdentityCompiler(directory);
const reader = createDarwinProcessRunTokenReader({
compiler: { kind: "xcrun", executable: fakeCompiler.executable },
});
const prepareSpy = vi
.spyOn(systemProcessOwnershipHost, "prepare")
.mockResolvedValue();
const baselineSpy = vi
.spyOn(systemProcessOwnershipHost, "captureBaseline")
.mockImplementation(async (signal) => {
const processes = [
{
pid: process.pid,
parentPid: process.ppid,
processGroupId: process.pid,
state: "S",
command: process.execPath,
},
];
await reader.identities(processes, signal);
return [];
});
const launchSpy = vi.spyOn(electron, "launch");
const controller = new AbortController();
let captureOutcome: Promise<ElectronCaptureOutcome> | undefined;
try {
const input = electronActiveObservationInputSchema.parse({
executable_path: process.execPath,
application_path: fileURLToPath(import.meta.url),
application_root: process.cwd(),
});
captureOutcome = new PlaywrightElectronActiveProvider({})
.capture(
{ ...input, application_root: process.cwd() },
{ signal: controller.signal },
)
.then(
(value) => ({ state: "result" as const, value }),
(cause: unknown) => ({ state: "rejected" as const, cause }),
);
const helper = await waitForIdentityHelper(fakeCompiler.statePath);
controller.abort();
const outcome = await captureOutcome;
expect(outcome.state).toBe("result");
if (outcome.state !== "result") throw outcome.cause;
expect(outcome.value).toMatchObject({
ok: false,
error: { reason: "cancelled", userCategory: "cancelled" },
});
expect(launchSpy).not.toHaveBeenCalled();
await expect(access(helper.root)).resolves.toBeUndefined();
} finally {
if (!controller.signal.aborted) controller.abort();
if (captureOutcome !== undefined) await captureOutcome;
prepareSpy.mockRestore();
baselineSpy.mockRestore();
launchSpy.mockRestore();
await reader.close();
await rm(directory, { recursive: true, force: true });
}
},
);
it.skipIf(!onDarwin)(
"parses exact NUL-delimited token records and rejects truncated argv",
async () => {
const root = await mkdtemp(
join(tmpdir(), "rea-process-token-parser-test-"),
);
const executable = join(root, "parser-test");
const core = fileURLToPath(
new URL(
"../../../bridge/process/ProcessRunTokenReader.swift",
import.meta.url,
),
);
const fixture = fileURLToPath(
new URL(
"../../fixtures/processRunTokenParserProbe.swift",
import.meta.url,
),
);
try {
await execFileOutput(
"/usr/bin/xcrun",
["swiftc", core, fixture, "-o", executable],
{ timeout: 120_000, stopSignal: SWIFTC_INTERRUPT },
);
const { stdout } = await execFileAsync(executable, [], {
encoding: "utf8",
timeout: 10_000,
});
expect(JSON.parse(stdout)).toEqual({
argvDecoyFailsClosed: true,
readableEnvironmentWithoutToken: {
runId: null,
environmentEntryCount: 1,
},
restrictedReadableEnvironmentWithoutToken: {
state: "readable",
reason: null,
},
restrictedEmptyEnvironment: {
state: "unavailable",
reason: "restricted_process_environment_withheld",
},
platformEmptyEnvironment: {
state: "unavailable",
reason: "platform_binary_environment_withheld",
},
restrictedUnavailableEnvironment: {
state: "unavailable",
reason: "restricted_process_environment_withheld",
},
restrictedAmbiguousEnvironment: {
state: "unavailable",
reason: "restricted_process_environment_withheld",
},
ordinaryAmbiguousEnvironment: {
state: "unavailable",
reason: "ambiguous_environment_boundary",
},
ordinaryUnavailableEnvironment: {
state: "unavailable",
reason: "environment_unavailable",
},
alternatePaddingRead: true,
appleVectorTokenFailsClosed: true,
clearedAppleVectorTokenFailsClosed: true,
callerPfzBeforeTokenRead: true,
emptyLaterArgumentRead: true,
emptyArgv0Read: true,
emptyEnvironmentRecordsBeforeTokenFailClosed: true,
duplicateTokenFailsClosed: true,
invalidTokenEncodingFailsClosed: true,
finalAssignmentArgumentRead: true,
missingAppleBoundaryFailsClosed: true,
malformedBuffersRejected: true,
invalidPointerSizeFailsClosed: true,
incompleteRestrictedArguments: [
"malformed_procargs_path_padding",
"malformed_procargs_arguments",
"apple_vector_unavailable",
].map((reason) => [
{
state: "unavailable",
reason: "platform_binary_environment_withheld",
},
{
state: "unavailable",
reason: "restricted_process_environment_withheld",
},
{ state: "unavailable", reason },
]),
signedReadableTokens: [true, true],
signedInvalidTokens: [
"duplicate_run_token",
"invalid_run_token_encoding",
].map((reason) => [
{ state: "unavailable", reason },
{ state: "unavailable", reason },
]),
malformedStages: [
{ state: "unavailable", reason: "malformed_procargs_header" },
{ state: "unavailable", reason: "malformed_procargs_argument_count" },
{
state: "unavailable",
reason: "malformed_procargs_executable_path",
},
{ state: "unavailable", reason: "malformed_procargs_pointer_size" },
{ state: "unavailable", reason: "malformed_procargs_path_padding" },
{ state: "unavailable", reason: "malformed_procargs_arguments" },
{ state: "unavailable", reason: "malformed_procargs_terminator" },
{
state: "unavailable",
reason: "malformed_procargs_trailing_entries",
},
],
malformedObservation: {
state: "unavailable",
reason: "malformed_procargs",
},
nonAsciiPathRead: true,
realTokenRead: true,
reservedArgumentWithRealTokenFailsClosed: true,
titleOverwriteFailsClosed: true,
truncatedArgvFailsClosed: true,
zeroArgumentsRead: true,
});
} finally {
await rm(root, { recursive: true, force: true });
}
},
150_000,
);
it.skipIf(!onDarwin)(
"retries a growing process table after ENOMEM and preserves other sysctl errors",
async () => {
const root = await mkdtemp(
join(tmpdir(), "rea-process-snapshot-retry-test-"),
);
const executable = join(root, "snapshot-probe");
const core = fileURLToPath(
new URL(
"../../../bridge/process/ProcessRunTokenReader.swift",
import.meta.url,
),
);
const fixture = fileURLToPath(
new URL(
"../../fixtures/processRunTokenSnapshotProbe.swift",
import.meta.url,
),
);
try {
await execFileOutput(
"/usr/bin/xcrun",
["swiftc", core, fixture, "-o", executable],
{ timeout: 120_000, stopSignal: SWIFTC_INTERRUPT },
);
const { stdout } = await execFileAsync(executable, [], {
encoding: "utf8",
timeout: 10_000,
});
expect(JSON.parse(stdout)).toEqual({
preservedOtherErrno: true,
retriedAfterFreshSize: true,
successfulSnapshotMissingPidIsAbsent: true,
failedSnapshotMissingPidIsUnavailable: true,
invalidPidIsUnavailable: true,
zombieLifecycleIsSeparateFromIdentity: true,
effectiveUidComesFromNativeIdentity: true,
nativeRelationIsSeparateFromIdentity: true,
nativeNameIsSeparateFromIdentity: true,
});
} finally {
await rm(root, { recursive: true, force: true });
}
},
150_000,
);
it.skipIf(!onDarwin)(
"reads exact ownership keys and preserves unknowns after process title changes",
async () => {
const host = createSystemProcessOwnershipHost("darwin");
const children: Awaited<ReturnType<typeof startNodeChild>>[] = [];
try {
const decoy = await startNodeChild({
DECOY: "words REA_PROCESS_RUN_ID=synthetic-decoy",
});
children.push(decoy);
const owned = await startNodeChild({
REA_PROCESS_RUN_ID: "synthetic-owned",
});
children.push(owned);
const titleMutated = await startNodeChild(
{ REA_PROCESS_RUN_ID: "synthetic-title-mutated" },
process.execPath,
["-e", 'process.title = "npm run check"; setInterval(() => {}, 1_000)'],
);
children.push(titleMutated);
await expect
.poll(async () => {
const process = (await host.listProcesses()).find(
({ pid }) => pid === titleMutated.pid,
);
return process?.command ?? "";
})
.toMatch(/^npm run check/u);
const processes = await host.listProcesses();
const entries = [decoy, owned, titleMutated].map(({ pid }) => {
const entry = processes.find((process) => process.pid === pid);
if (entry === undefined)
throw new Error("test child is absent from process table");
return entry;
});
const observations = await host.runTokens?.(entries);
const identities = await host.processIdentities?.(entries);
expect(observations?.get(decoy.pid)).toEqual({
state: "readable",
runId: undefined,
});
expect(observations?.get(owned.pid)).toEqual({
state: "readable",
runId: "synthetic-owned",
});
expect(observations?.get(titleMutated.pid)).toMatchObject({
state: "unavailable",
reason: expect.stringMatching(/\S/u),
});
expect(identities?.get(decoy.pid)?.state).toBe("readable");
expect(identities?.get(owned.pid)?.state).toBe("readable");
expect(identities?.get(titleMutated.pid)?.state).toBe("readable");
} finally {
try {
await Promise.all(children.map(({ child }) => stopNodeChild(child)));
} finally {
await host.close?.();
}
}
},
);
it.skipIf(!onDarwin)(
"keeps a long title overwrite unknown when it hides the child's token",
async () => {
const host = createSystemProcessOwnershipHost("darwin");
const children: ChildProcess[] = [];
try {
for (const length of [256, 384, 512, 1024]) {
const child = spawn(
process.execPath,
[
"-e",
`if(process.env.PROBE_TITLE!=='-') process.title=process.env.PROBE_TITLE;process.send({ready:true,stillOwned:process.env.REA_PROCESS_RUN_ID==='probe-owned'});setInterval(()=>{},1000)`,
"arg1",
"arg2",
"arg3",
],
{
env: {
REA_PROCESS_RUN_ID: "probe-owned",
PROBE_TITLE: "n".repeat(length),
PATH: process.env.PATH,
},
stdio: ["ignore", "ignore", "ignore", "ipc"],
},
);
children.push(child);
const message = await waitForChildReadiness(child);
expect(message).toEqual({ ready: true, stillOwned: true });
if (child.pid === undefined)
throw new Error("test child did not receive a PID");
const entry = (await host.listProcesses()).find(
({ pid }) => pid === child.pid,
);
if (
entry === undefined ||
host.processIdentities === undefined ||
host.runTokens === undefined
)
throw new Error("native child inspection unavailable");
const before = (await host.processIdentities([entry])).get(child.pid);
const token = (await host.runTokens([entry])).get(child.pid);
const after = (await host.processIdentities([entry])).get(child.pid);
expect(before).toMatchObject({
state: "readable",
lifecycle: { state: "observed", lifecycle: "non-zombie" },
});
if (before?.state !== "readable")
throw new Error("child identity unavailable");
expect(after).toMatchObject({
state: "readable",
identity: before.identity,
lifecycle: { state: "observed", lifecycle: "non-zombie" },
});
if (token?.state === "readable") {
expect(token.runId).toBe("probe-owned");
} else {
expect(token).toEqual({
state: "unavailable",
reason: "ambiguous_environment_boundary",
});
}
}
} finally {
try {
await Promise.all(children.map(stopNodeChild));
} finally {
await host.close?.();
}
}
},
150_000,
);
it.skipIf(!onDarwin)(
"reads owned tokens across executable padding and distinguishes a real empty environment",
async () => {
const root = await mkdtemp(join(tmpdir(), "rea-process-token-padding-"));
const host = createSystemProcessOwnershipHost("darwin");
const children: Awaited<ReturnType<typeof startNodeChild>>[] = [];
try {
const executables = Array.from({ length: 8 }, (_, index) =>
join(root, "n".repeat(index + 1)),
);
for (const executable of executables)
await symlink(process.execPath, executable);
for (let index = 0; index < executables.length; index += 1) {
const executable = executables[index];
if (executable === undefined)
throw new Error("test executable is missing");
children.push(
await startNodeChild(
{ REA_PROCESS_RUN_ID: `synthetic-padding-${String(index)}` },
executable,
["-e", "setInterval(() => {}, 1_000)", "", "FOO=bar"],
index === 0 ? "" : undefined,
),
);
}
children.push(await startNodeChild({}));
const processes = await host.listProcesses();
const entries = children.map(({ pid }) => {
const entry = processes.find((process) => process.pid === pid);
if (entry === undefined)
throw new Error("owned test child is absent from process table");
return entry;
});
const observations = await host.runTokens?.(entries);
expect(children.map(({ pid }) => observations?.get(pid))).toEqual([
...executables.map((_, index) => ({
state: "readable",
runId: `synthetic-padding-${String(index)}`,
})),
{ state: "readable" },
]);
} finally {
for (const { child } of children) await stopNodeChild(child);
await host.close?.();
await rm(root, { recursive: true, force: true });
}
},
);
const startNodeChild = async (
environment: NodeJS.ProcessEnv,
command = process.execPath,
arguments_: readonly string[] = ["-e", "setInterval(() => {}, 1_000)"],
argv0?: string,
) => {
const child = spawn(command, [...arguments_], {
env: environment,
stdio: "ignore",
...(argv0 === undefined ? {} : { argv0 }),
});
if (child.pid === undefined)
throw new Error("test child did not receive a PID");
await new Promise<void>((resolve, reject) => {
child.once("spawn", resolve);
child.once("error", reject);
});
return { child, pid: child.pid };
};
const stopNodeChild = async (child: ChildProcess) => {
if (child.exitCode !== null || child.signalCode !== null) return;
const exited = new Promise<void>((resolve) =>
child.once("exit", () => resolve()),
);
child.kill("SIGKILL");
await exited;
};
const waitForChildReadiness = async (child: ChildProcess): Promise<unknown> => {
const listeners = new AbortController();
const message = once(child, "message", { signal: listeners.signal });
const exit = once(child, "exit", { signal: listeners.signal });
try {
await awaitAcknowledgement(
message.then(() => undefined),
exit,
"title-mutating child readiness",
);
return (await message)[0];
} finally {
listeners.abort();
}
};
it("stops a child that never acknowledges readiness", async () => {
const child = spawn(process.execPath, ["-e", "setInterval(() => {}, 1000)"], {
stdio: ["ignore", "ignore", "ignore", "ipc"],
});
try {
await expect(waitForChildReadiness(child)).rejects.toThrow(
"title-mutating child readiness was not acknowledged before the gate deadline",
);
} finally {
await stopNodeChild(child);
}
expect(child.exitCode !== null || child.signalCode !== null).toBe(true);
});
it.skipIf(process.platform === "win32").each([
{
name: "invalid UTF-8",
stdout: "owned compiler stdout",
stderrHex: "ff",
stderrBytes: 1,
truncated: false,
},
{
name: "bounded stdout",
stdout: "x".repeat(12000),
stderrHex: "61",
stderrBytes: 1,
truncated: true,
},
])(
"retains original output facts for $name compiler failures",
async ({ stdout, stderrHex, stderrBytes, truncated }) => {
const directory = await createTestTempDirectory(
"rea-compiler-output-facts-",
);
const executable = join(directory, "compiler");
await writeFile(
executable,
`#!/usr/bin/env node\nprocess.stdout.write(${JSON.stringify(stdout)}); process.stderr.write(Buffer.from(${JSON.stringify(stderrHex)}, "hex")); process.exitCode=7;\n`,
{ mode: 0o755 },
);
const reader = createDarwinProcessRunTokenReader({
compiler: { kind: "xcrun", executable },
});
try {
const failure = await preparationFailure(reader);
expect(failure.compilerOutcome?.stderrBytes).toBe(stderrBytes);
expect(failure.capturedOutput).toEqual({
stdout: stdout.slice(0, 8192),
stderr: Buffer.from(stderrHex, "hex").toString("utf8"),
stdout_bytes: Buffer.byteLength(stdout),
stderr_bytes: stderrBytes,
truncated,
exit_code: 7,
signal: null,
});
const normalized = normalizeCaptureFailure(failure);
if (!(normalized instanceof ProcessCaptureError))
throw new Error("Expected capture failure");
expect(normalized.capturedOutput).toEqual(failure.capturedOutput);
} finally {
await reader.close();
}
},
);