fix(verifiers): share package command and workspace ownership

Join canonical owned command cleanup before releasing package verifier workspaces. Replace raw package command wrappers, the updater supervisor, WASM cleanup composite, and boolean-only retention with explicit capability ownership.

Preserve selected environments, npm arguments, no-deadline calls, clean exit status, and distinct aggregate/per-stream output budgets. Exercise actual timeout descendants, cancellation, retained cleanup retry, and workspace admission.
This commit is contained in:
morluto
2026-10-24 16:58:18 +08:00
parent de9a8898dc
commit d9660eb032
47 changed files with 2551 additions and 1061 deletions
+5
View File
@@ -0,0 +1,5 @@
/** Preserve the caller's npm entry and arguments without invoking a shell. */
export function npmInvocation(
arguments_: readonly string[],
environment: Readonly<NodeJS.ProcessEnv>,
): { readonly command: string; readonly arguments: readonly string[] };
+19
View File
@@ -0,0 +1,19 @@
import { dirname, join } from "node:path";
/** Preserve the caller's npm entry and arguments without invoking a shell. */
export const npmInvocation = (arguments_, environment) => {
const selectedEntry = environment.npm_execpath;
if (
selectedEntry !== undefined &&
(typeof selectedEntry !== "string" || selectedEntry.length === 0)
)
throw new Error("npm_execpath must name the selected npm CLI entry point");
const entry =
selectedEntry ??
(process.platform === "win32"
? join(dirname(process.execPath), "node_modules/npm/bin/npm-cli.js")
: undefined);
return entry === undefined
? { command: "npm", arguments: arguments_ }
: { command: process.execPath, arguments: [entry, ...arguments_] };
};
+332
View File
@@ -0,0 +1,332 @@
import { randomUUID } from "node:crypto";
import {
ArtifactResourceScope,
type ArtifactResourceOwner,
} from "../../src/artifacts/ArtifactResourceScope.js";
import { AnalysisCleanupLease } from "../../src/artifacts/AnalysisCleanupLease.js";
import type { RetainedResourceScope } from "../../src/application/AnalysisProvider.js";
import {
prepareOwnedCommand,
runOwnedCommand,
} from "../../src/process/OwnedCommand.js";
import {
spawnOwnedProviderProcess,
type OwnedProviderProcessSpawnOptions,
type ProviderProcessSnapshot,
type SpawnedOwnedProviderProcess,
} from "../../src/process/ProviderProcess.js";
import { retainOwnedCommandCleanup } from "../../src/providers/OwnedCommandCleanup.js";
import { WindowsOwnedProcess } from "../../src/windows/WindowsOwnedProcess.js";
import { npmInvocation } from "./npm-invocation.mjs";
import { createWindowsOwnedCommand } from "./windows-owned-command.mjs";
export interface PackageVerifierWorkspace {
readonly path: string;
close(): Promise<void>;
}
interface PackageCommandContext {
readonly signal?: AbortSignal | undefined;
readonly cwd?: string | undefined;
readonly env?: NodeJS.ProcessEnv | undefined;
readonly timeout?: number | undefined;
readonly windowsHide?: boolean | undefined;
}
export type PackageCommandOptions = PackageCommandContext &
(
| {
/** execFile-compatible capacity for each output stream. */
readonly maxBuffer?: number | undefined;
readonly diagnosticBytes?: never;
}
| {
/** Canonical command capacity across both streams. */
readonly diagnosticBytes: number;
readonly maxBuffer?: never;
}
);
export interface PackageCommandOutput {
readonly stdout: string;
readonly stderr: string;
}
export interface PackageCleanupOwner {
close(): Promise<void>;
readonly cleanupResources?: readonly string[] | undefined;
}
export interface PackageVerifierCommands {
exec(
command: string,
arguments_: readonly string[],
options?: PackageCommandOptions,
): Promise<PackageCommandOutput>;
execNpm(
arguments_: readonly string[],
options?: PackageCommandOptions,
): Promise<PackageCommandOutput>;
run(
command: string,
arguments_: readonly string[],
environment?: NodeJS.ProcessEnv,
): Promise<string>;
runWithStatus(
command: string,
arguments_: readonly string[],
environment?: NodeJS.ProcessEnv,
): Promise<{ readonly stdout: string; readonly status: number }>;
adopt(owner: PackageCleanupOwner, resource?: string): void;
close(): Promise<void>;
}
/** An ordinary nonzero exit whose process and output cleanup both completed. */
export class PackageCommandExitError extends Error {
readonly stdout: string;
readonly stderr: string;
constructor(
readonly command: string,
readonly arguments_: readonly string[],
readonly code: number,
readonly snapshot: ProviderProcessSnapshot,
) {
super(
`Command failed: ${[command, ...arguments_].join(" ")}\n${snapshot.stderr.text}`,
);
this.name = "PackageCommandExitError";
this.stdout = snapshot.stdout.text;
this.stderr = snapshot.stderr.text;
}
}
/** The same command/workspace capability remains available after failed close. */
export class PackageVerifierCleanupFailure extends Error {
readonly path: string;
constructor(
cause: unknown,
readonly cleanupOwner: PackageVerifierCommands,
readonly workspace: PackageVerifierWorkspace,
) {
super(
`Package verification cleanup incomplete; retained ${workspace.path}: ${cause instanceof Error ? cause.message : String(cause)}`,
{ cause },
);
this.name = "PackageVerifierCleanupFailure";
this.path = workspace.path;
}
}
const launchPackageCommand = async (
input: OwnedProviderProcessSpawnOptions,
): Promise<SpawnedOwnedProviderProcess> => {
if (process.platform !== "win32") return spawnOwnedProviderProcess(input);
input.signal?.throwIfAborted();
const child = createWindowsOwnedCommand({
WindowsOwnedProcess,
command: input.command,
arguments: input.arguments,
cwd: input.cwd,
environment: {
...input.hostEnvironment,
...input.env,
REA_PROCESS_RUN_ID: input.runId,
},
});
// These labels describe the launch; cleanup authority is the exact native job.
return {
process: child,
ownership: {
runId: input.runId,
leaderPid: child.pid,
processGroupId: child.pid,
expectedParentPid: process.pid,
},
cleanup: () => child.cleanup(),
};
};
const commandLimits = (options: PackageCommandOptions) => {
const timeout = options.timeout ?? 0;
if (!Number.isSafeInteger(timeout) || timeout < 0)
throw new RangeError(
"Package command timeout must be a nonnegative integer",
);
if (options.maxBuffer !== undefined && options.diagnosticBytes !== undefined)
throw new TypeError("Select a per-stream or aggregate command capacity");
const capacity = options.diagnosticBytes ?? options.maxBuffer ?? 1024 * 1024;
if (typeof capacity !== "number" || Number.isNaN(capacity) || capacity < 0)
throw new RangeError("Package command output capacity must be nonnegative");
if (process.platform === "win32" && options.windowsHide === false)
throw new Error(
"Owned package commands require hidden Windows process launch",
);
return {
timeoutMs: timeout === 0 ? null : timeout,
diagnosticBytes:
options.diagnosticBytes === undefined
? { stdout: capacity, stderr: capacity }
: capacity,
acceptNonZeroExit: true,
};
};
class PackageCommands implements PackageVerifierCommands {
readonly #resources = new ArtifactResourceScope();
readonly #controller = new AbortController();
readonly #adopted = new WeakMap<PackageCleanupOwner, ArtifactResourceOwner>();
readonly #resourceOwner: RetainedResourceScope;
#closed = false;
#workspaceReleaseStarted = false;
#closing: Promise<void> | undefined;
constructor(
readonly workspace: PackageVerifierWorkspace,
readonly launcher: (
input: OwnedProviderProcessSpawnOptions,
) => Promise<SpawnedOwnedProviderProcess>,
) {
this.#resourceOwner = {
retain: (owner, resource) => this.adopt(owner, resource),
run: (work) => this.#resources.run(work),
};
}
exec(
command: string,
arguments_: readonly string[],
options: PackageCommandOptions = {},
): Promise<PackageCommandOutput> {
if (this.#closed)
return Promise.reject(new Error("Package command owner is closed"));
return this.#resources.run(async () => {
try {
const limits = commandLimits(options);
const signal =
options.signal === undefined
? this.#controller.signal
: AbortSignal.any([this.#controller.signal, options.signal]);
// Match execFile's command deadline while keeping native ownership
// preparation independently bounded and inside this resource lifetime.
await prepareOwnedCommand(signal);
const snapshot = await runOwnedCommand(
{
command,
arguments: arguments_,
cwd: options.cwd ?? process.cwd(),
hostEnvironment: options.env ?? process.env,
expectedCommand: null,
runId: `rea-package-command-${randomUUID()}`,
sweepTokenOwnedProcesses: true,
},
limits,
{
signal,
launcher: this.launcher,
},
);
const output = {
stdout: snapshot.stdout.text,
stderr: snapshot.stderr.text,
};
if (typeof snapshot.exitCode === "number" && snapshot.exitCode !== 0)
throw new PackageCommandExitError(
command,
arguments_,
snapshot.exitCode,
snapshot,
);
return output;
} catch (cause: unknown) {
retainOwnedCommandCleanup(
cause,
this.#resourceOwner,
"package-verifier",
command,
);
throw cause;
}
});
}
execNpm(
arguments_: readonly string[],
options: PackageCommandOptions = {},
): Promise<PackageCommandOutput> {
const invocation = npmInvocation(arguments_, options.env ?? process.env);
return this.exec(invocation.command, invocation.arguments, options);
}
async run(
command: string,
arguments_: readonly string[],
environment?: NodeJS.ProcessEnv,
): Promise<string> {
return (await this.exec(command, arguments_, { env: environment })).stdout;
}
async runWithStatus(
command: string,
arguments_: readonly string[],
environment?: NodeJS.ProcessEnv,
): Promise<{ readonly stdout: string; readonly status: number }> {
try {
return {
stdout: await this.run(command, arguments_, environment),
status: 0,
};
} catch (cause: unknown) {
if (cause instanceof PackageCommandExitError && cause.code === 1)
return { stdout: cause.stdout, status: 1 };
throw cause;
}
}
adopt(owner: PackageCleanupOwner, resource = this.workspace.path): void {
if (this.#workspaceReleaseStarted)
throw new Error("Package workspace release has started");
let retained = this.#adopted.get(owner);
if (retained === undefined) {
retained = {
kind: "lease",
resource,
lease: AnalysisCleanupLease.forCloseOwner(owner, resource),
};
this.#adopted.set(owner, retained);
}
this.#resources.retain(retained);
}
close(): Promise<void> {
this.#closed = true;
this.#controller.abort(new Error("Package command owner closed"));
if (this.#closing !== undefined) return this.#closing;
let attempt: Promise<void>;
attempt = this.#release().catch((cause: unknown) => {
if (this.#closing === attempt) this.#closing = undefined;
throw new PackageVerifierCleanupFailure(cause, this, this.workspace);
});
this.#closing = attempt;
return attempt;
}
async #release(): Promise<void> {
await this.#resources.close();
this.#workspaceReleaseStarted = true;
await this.workspace.close();
}
}
/** Allocate one command lifetime whose workspace depends on completed cleanup. */
export async function createPackageVerifierCommands({
workspace,
launcher = launchPackageCommand,
}: {
readonly workspace: PackageVerifierWorkspace;
readonly launcher?: (
input: OwnedProviderProcessSpawnOptions,
) => Promise<SpawnedOwnedProviderProcess>;
}): Promise<PackageVerifierCommands> {
return new PackageCommands(workspace, launcher);
}
@@ -0,0 +1,8 @@
export { createPackageVerifierCommands } from "./package-command-owner.mjs";
export type {
PackageVerifierCommands,
PackageVerifierWorkspace,
PackageCommandOptions,
PackageCommandOutput,
PackageCleanupOwner,
} from "./package-command-owner.mjs";
+13
View File
@@ -0,0 +1,13 @@
let implementation;
/** Load the development owner as one source graph, only when commands are needed. */
export async function createPackageVerifierCommands(options) {
implementation ??= import("tsx/esm/api").then(({ tsImport }) =>
tsImport(
new URL("./package-command-owner.mts", import.meta.url).href,
import.meta.url,
),
);
const module = await implementation;
return module.createPackageVerifierCommands(options);
}
+2 -42
View File
@@ -1,6 +1,5 @@
import { execFile } from "node:child_process";
import { lstat } from "node:fs/promises";
import { dirname, join } from "node:path";
import { promisify } from "node:util";
import { Ajv2020 } from "ajv/dist/2020.js";
@@ -8,55 +7,16 @@ import { Ajv2020 } from "ajv/dist/2020.js";
* `mcp-verifier-results.mjs` reaches into `dist/` at module load, so eager
* importing it propagates that build dependency to every consumer in this
* file. `verifyCompleteToolCatalog` is the only consumer, so we lazy-load
* the module there and keep the other helpers (`exec`, `run`, `json`,
* the module there and keep the remaining helpers (`exec`, `json`,
* `pathExists`) usable from `test:fast` callers that only need `dist/`-free
* utilities — see docs/testing.md § test scripts, which promises `test:fast`
* runs "without build".
*/
const loadMcpVerifierResults = () => import("./mcp-verifier-results.mjs");
/** Promisified child_process execution helper. */
/** Native-client commands expose the live child handle for caller-owned stdin. */
export const exec = promisify(execFile);
/** Run npm through its JavaScript entry point so Windows command scripts are not required. */
export const execNpm = (arguments_, options = {}) => {
const environment = options.env ?? process.env;
const selectedEntry = environment.npm_execpath;
if (
selectedEntry !== undefined &&
(typeof selectedEntry !== "string" || selectedEntry.length === 0)
)
throw new Error("npm_execpath must name the selected npm CLI entry point");
const entry =
selectedEntry ??
(process.platform === "win32"
? join(dirname(process.execPath), "node_modules/npm/bin/npm-cli.js")
: undefined);
return entry === undefined
? exec("npm", arguments_, options)
: exec(process.execPath, [entry, ...arguments_], options);
};
/** Run a command and return its stdout. */
export const run = async (command, args, env) =>
(await exec(command, args, { env })).stdout;
/** Run a command and treat exit code 1 as a non-throwing status result. */
export const runWithStatus = async (command, args, env) => {
try {
return { stdout: await run(command, args, env), status: 0 };
} catch (cause) {
if (
typeof cause === "object" &&
cause !== null &&
cause.code === 1 &&
typeof cause.stdout === "string"
)
return { stdout: cause.stdout, status: 1 };
throw cause;
}
};
/** Parse JSON from a command output string. */
export const json = (text) => JSON.parse(text);
+23 -23
View File
@@ -1,19 +1,12 @@
#!/usr/bin/env node
import assert from "node:assert/strict";
import { execFile } from "node:child_process";
import { randomUUID } from "node:crypto";
import { access, readFile, writeFile } from "node:fs/promises";
import { dirname, isAbsolute, join, resolve } from "node:path";
import { setTimeout as pause } from "node:timers/promises";
import { fileURLToPath } from "node:url";
import { promisify } from "node:util";
import { PrivateRuntimeRoot } from "../dist/process/PrivateRuntimeRoot.js";
import {
OwnedCommandFailure,
runOwnedCommand,
} from "../dist/process/OwnedCommand.js";
import { createVerifierRun, completeVerifierRun } from "./lib/verifier-run.mjs";
import { execNpm } from "./lib/verify-package-core.mjs";
import { createPackageVerifierCommands } from "./lib/verify-package-commands.mjs";
const AGENTENV_VERSION = "0.9.1334";
const AGENTENV_PIN = `agentenv-framework ${AGENTENV_VERSION}`;
@@ -34,7 +27,6 @@ const TEMPLATE_RULES = [
"opaque-token",
"sibling-variance",
];
const execFileAsync = promisify(execFile);
// Both tools are caller-supplied absolute paths, as every real-* lane names its upstream.
for (const key of ["REA_AGENT_ENV_COMMAND", "REA_DOCKER_COMMAND"])
@@ -62,8 +54,10 @@ const failures = [];
let cases = 0;
const receipt = {};
let knownContainers;
let commands;
try {
const version = await execFileAsync(agentEnv, ["--version"], {
commands = await createPackageVerifierCommands({ workspace: runtime });
const version = await commands.exec(agentEnv, ["--version"], {
env: environment,
timeout: 10_000,
maxBuffer: 64 * 1024,
@@ -124,7 +118,7 @@ try {
await writeFile(capturePath, JSON.stringify(capture));
await writeFile(manifestPath, JSON.stringify(manifest));
// The lane proves this checkout, not the registry package: pack it and install the tarball in the image.
const { stdout: packed } = await execNpm(
const { stdout: packed } = await commands.execNpm(
["pack", "--ignore-scripts", "--json", "--pack-destination", runtime.path],
{ cwd: checkout, env: environment, maxBuffer: 16 * 1024 * 1024 },
);
@@ -179,20 +173,25 @@ try {
attempts += 1;
let outcome;
try {
outcome = await runOwnedCommand(
const output = await commands.exec(
agentEnv,
["run", bundle, "--sandbox", "local"],
{
command: agentEnv,
arguments: ["run", bundle, "--sandbox", "local"],
cwd: runtime.path,
runId: `rea-agentenv-run-${randomUUID()}`,
hostEnvironment: environment,
env: environment,
timeout: timeoutMs,
diagnosticBytes: 4 * 1024 * 1024,
},
{ timeoutMs, diagnosticBytes: 4 * 1024 * 1024 },
);
outcome = {
exitCode: 0,
signal: null,
stdout: { text: output.stdout },
stderr: { text: output.stderr },
};
return outcome;
} catch (cause) {
outcome =
cause instanceof OwnedCommandFailure ? cause.snapshot : undefined;
outcome = cause?.snapshot;
throw cause;
} finally {
// Recorded before the outcome is judged, so a failed receipt still says how the last
@@ -257,7 +256,8 @@ try {
failures.push(cause);
}
try {
await runtime.close();
if (commands === undefined) await runtime.close();
else await commands.close();
} catch (cause) {
failures.push(cause);
}
@@ -299,7 +299,7 @@ console.log(
);
async function cli(...args) {
const { stdout } = await execFileAsync(
const { stdout } = await commands.exec(
process.execPath,
[entrypoint, ...args, "--json"],
{ env: environment, cwd: runtime.path, maxBuffer: 64 * 1024 * 1024 },
@@ -339,7 +339,7 @@ function packedFilename(report) {
// Containers are not verifier descendants, so leaks are checked by id against the pre-run set.
async function containers() {
const { stdout } = await execFileAsync(
const { stdout } = await commands.exec(
docker,
["ps", "-a", "--format", "{{.ID}}\t{{.Names}}"],
{ env: environment },
@@ -355,7 +355,7 @@ async function containers() {
}
async function imageIds() {
const { stdout } = await execFileAsync(docker, ["images", "-q"], {
const { stdout } = await commands.exec(docker, ["images", "-q"], {
env: environment,
});
return [...new Set(stdout.split("\n").filter((line) => line.length > 0))];
+22 -7
View File
@@ -3,20 +3,23 @@ import { isDeepStrictEqual } from "node:util";
import { join } from "node:path";
import { TextReader, Uint8ArrayWriter, ZipWriter } from "@zip.js/zip.js";
import { json, run } from "./lib/verify-package-core.mjs";
import { json } from "./lib/verify-package-core.mjs";
/** Run artifact and JavaScript application analysis against packaged fixtures. */
export async function verifyPackageArtifactAndElectron({
commands,
cli,
workspace,
environment,
}) {
const artifactArchive = await verifyPackagedArtifact({
commands,
cli,
workspace,
environment,
});
await verifyPackagedElectronApplication({
commands,
cli,
workspace,
environment,
@@ -24,20 +27,25 @@ export async function verifyPackageArtifactAndElectron({
return { artifactArchive };
}
const verifyPackagedArtifact = async ({ cli, workspace, environment }) => {
const verifyPackagedArtifact = async ({
commands,
cli,
workspace,
environment,
}) => {
const artifactArchive = join(workspace, "artifact.zip");
const artifactWriter = new ZipWriter(new Uint8ArrayWriter());
await artifactWriter.add("app/main.js", new TextReader("main();"));
await writeFile(artifactArchive, await artifactWriter.close());
const artifactInspection = json(
await run(
await commands.run(
cli,
["inspect-artifact", artifactArchive, "--json"],
environment,
),
);
const repeatedArtifactInspection = json(
await run(
await commands.run(
cli,
["inspect-artifact", artifactArchive, "--json"],
environment,
@@ -57,6 +65,7 @@ const verifyPackagedArtifact = async ({ cli, workspace, environment }) => {
)
throw new Error("packaged artifact inventory CLI failed");
await verifyPackagedArtifactExtraction({
commands,
artifactArchive,
artifactInventory,
cli,
@@ -67,6 +76,7 @@ const verifyPackagedArtifact = async ({ cli, workspace, environment }) => {
};
const verifyPackagedElectronApplication = async ({
commands,
cli,
workspace,
environment,
@@ -86,7 +96,7 @@ const verifyPackagedElectronApplication = async ({
'const { contextBridge, ipcRenderer } = require("electron");\ncontextBridge.exposeInMainWorld("rea", { ping: () => ipcRenderer.invoke("rea:ping") });\n',
);
const applicationAnalysis = json(
await run(
await commands.run(
cli,
["analyze-javascript-application", applicationRoot, "--json"],
environment,
@@ -101,7 +111,11 @@ const verifyPackagedElectronApplication = async ({
)
throw new Error("packaged JavaScript application analysis CLI failed");
const routedApplicationAnalysis = json(
await run(cli, ["analyze", applicationRoot, "--json"], environment),
await commands.run(
cli,
["analyze", applicationRoot, "--json"],
environment,
),
);
assertRoutedApplicationAnalysis(routedApplicationAnalysis);
};
@@ -113,6 +127,7 @@ const sameArtifactIdentity = (left, right) =>
isDeepStrictEqual(left?.edges, right?.edges);
const verifyPackagedArtifactExtraction = async ({
commands,
artifactArchive,
artifactInventory,
cli,
@@ -124,7 +139,7 @@ const verifyPackagedArtifactExtraction = async ({
if (occurrence?.logical_path === undefined)
throw new Error("packaged artifact inventory omitted the selected member");
const extraction = json(
await run(
await commands.run(
cli,
["extract-artifact", artifactArchive, "--json"],
environment,
+9 -5
View File
@@ -1,18 +1,22 @@
import { json, run } from "./lib/verify-package-core.mjs";
import { json } from "./lib/verify-package-core.mjs";
import {
expandCapabilitiesOutput,
expandProviderOutput,
} from "./lib/mcp-verifier-results.mjs";
/** Verify packaged capabilities, providers, and non-Linux search results. */
export async function verifyPackageCapabilitiesAndSearch({ cli, environment }) {
export async function verifyPackageCapabilitiesAndSearch({
commands,
cli,
environment,
}) {
const capabilities = expandCapabilitiesOutput(
json(await run(cli, ["capabilities", "--json"], environment)),
json(await commands.run(cli, ["capabilities", "--json"], environment)),
);
if (!Array.isArray(capabilities.capabilities))
throw new Error("packaged capabilities CLI failed");
const providers = expandProviderOutput(
json(await run(cli, ["providers", "--json"], environment)),
json(await commands.run(cli, ["providers", "--json"], environment)),
);
const candidates = providers.analysis_provider_candidates;
if (
@@ -29,7 +33,7 @@ export async function verifyPackageCapabilitiesAndSearch({ cli, environment }) {
throw new Error("packaged providers CLI failed");
if (process.platform !== "linux") {
const searchResult = json(
await run(
await commands.run(
cli,
[
"search",
+3
View File
@@ -1,3 +1,5 @@
import type { PackageVerifierCommands } from "./lib/package-command-owner.mjs";
/**
* Host facts the packaged setup support is derived from.
*
@@ -31,6 +33,7 @@ export function packageSetupSupport(
/** Validate --help, --llms, and doctor output and determine host setup support. */
export function verifyPackageDiscovery(input: {
readonly commands: Pick<PackageVerifierCommands, "run" | "runWithStatus">;
readonly cli: string;
readonly environment: Readonly<Record<string, string | undefined>>;
}): Promise<{
+5 -5
View File
@@ -1,4 +1,4 @@
import { json, run, runWithStatus } from "./lib/verify-package-core.mjs";
import { json } from "./lib/verify-package-core.mjs";
const REQUIRED_HELP_COMMANDS = [
"setup",
@@ -79,10 +79,10 @@ async function readHostLinuxDistribution() {
}
/** Validate --help, --llms, and doctor output and determine host setup support. */
export async function verifyPackageDiscovery({ cli, environment }) {
const help = await run(cli, ["--help"], environment);
const llms = await run(cli, ["--llms"], environment);
const doctorExecution = await runWithStatus(
export async function verifyPackageDiscovery({ commands, cli, environment }) {
const help = await commands.run(cli, ["--help"], environment);
const llms = await commands.run(cli, ["--llms"], environment);
const doctorExecution = await commands.runWithStatus(
cli,
["doctor", "--json"],
environment,
+6 -5
View File
@@ -2,17 +2,18 @@ import { readFile, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { createEvidence } from "../dist/domain/evidence.js";
import { json, run } from "./lib/verify-package-core.mjs";
import { json } from "./lib/verify-package-core.mjs";
/** Round-trip historical reference import and evidence bundles. */
export async function verifyPackageEvidence({
commands,
cli,
evidenceRoot,
referenceRoot,
environment,
}) {
const referenceImport = json(
await run(
await commands.run(
cli,
["import-reference-source", referenceRoot, "--json"],
environment,
@@ -39,21 +40,21 @@ export async function verifyPackageEvidence({
const canonicalBundlePath = join(evidenceRoot, "canonical.json");
await writeFile(sourceBundlePath, serializeEvidenceBundle(sourceBundle));
const importedBundle = json(
await run(
await commands.run(
cli,
["evidence-import", sourceBundlePath, "--json"],
environment,
),
);
const exportedBundle = json(
await run(
await commands.run(
cli,
["evidence-export", sourceBundlePath, canonicalBundlePath, "--json"],
environment,
),
);
const comparedBundle = json(
await run(
await commands.run(
cli,
["compare", sourceBundlePath, canonicalBundlePath, "--json"],
environment,
+21 -10
View File
@@ -2,7 +2,7 @@ import { rm } from "node:fs/promises";
import { join } from "node:path";
import { pathToFileURL } from "node:url";
import { exec, execNpm, json, run } from "./lib/verify-package-core.mjs";
import { json } from "./lib/verify-package-core.mjs";
/** Install the packed CLI globally with and without optional dependencies and verify PTY degradation. */
export async function verifyPackageInstall({
@@ -10,8 +10,9 @@ export async function verifyPackageInstall({
prefix,
workspace,
environment,
commands,
}) {
await execNpm(
await commands.execNpm(
[
"install",
"--global",
@@ -26,7 +27,11 @@ export async function verifyPackageInstall({
);
const cli = join(prefix, "bin", "rea");
const packageRunnerCli = join(prefix, "bin", "rea-agents");
if (!(await run(packageRunnerCli, ["--help"], environment)).includes("setup"))
if (
!(await commands.run(packageRunnerCli, ["--help"], environment)).includes(
"setup",
)
)
throw new Error("packaged rea-agents alias did not expose setup");
const processCaptureCapabilityUrl = pathToFileURL(
join(
@@ -36,7 +41,7 @@ export async function verifyPackageInstall({
).href;
const processCaptureCapability = json(
(
await exec(
await commands.exec(
process.execPath,
[
"--input-type=module",
@@ -52,7 +57,7 @@ export async function verifyPackageInstall({
`packaged PTY backend failed without lifecycle scripts: ${JSON.stringify(processCaptureCapability)}`,
);
const noOptionalPrefix = join(workspace, "prefix-no-optional");
await execNpm(
await commands.execNpm(
[
"install",
"--global",
@@ -81,7 +86,7 @@ export async function verifyPackageInstall({
).href;
const noOptionalCapability = json(
(
await exec(
await commands.exec(
process.execPath,
[
"--input-type=module",
@@ -93,20 +98,26 @@ export async function verifyPackageInstall({
).stdout,
);
if (
!(await run(noOptionalCli, ["--help"], environment)).includes("setup") ||
!(await commands.run(noOptionalCli, ["--help"], environment)).includes(
"setup",
) ||
noOptionalCapability.available !== false
)
throw new Error(
`packaged CLI did not degrade without the optional PTY binary: ${JSON.stringify(noOptionalCapability)}`,
);
await verifyInstalledPackageCanRepack(prefix, environment);
await verifyInstalledPackageCanRepack(commands, prefix, environment);
return { cli, packageRunnerCli };
}
const verifyInstalledPackageCanRepack = async (prefix, environment) => {
const verifyInstalledPackageCanRepack = async (
commands,
prefix,
environment,
) => {
const installedPackageRoot = join(prefix, "lib/node_modules/rea-agents");
const repackedTarball = (
await execNpm(["pack", "--silent"], {
await commands.execNpm(["pack", "--silent"], {
cwd: installedPackageRoot,
env: environment,
})
+38 -12
View File
@@ -4,7 +4,7 @@ import { join } from "node:path";
import { createEvidence } from "../dist/domain/evidence.js";
import { buildManagedPeFixture } from "./fixtures/managed/pe.mjs";
import { functionDossier } from "./fixtures/managed/function-dossier.mjs";
import { json, run } from "./lib/verify-package-core.mjs";
import { json } from "./lib/verify-package-core.mjs";
const buildManagedFixtures = async (workspace) => {
const managedPath = join(workspace, "managed-fixture.exe");
@@ -38,9 +38,14 @@ const buildManagedFixtures = async (workspace) => {
return { managedPath, managedRightPath };
};
const verifyManagedArtifact = async ({ cli, managedPath, environment }) => {
const verifyManagedArtifact = async ({
commands,
cli,
managedPath,
environment,
}) => {
const managedArtifact = json(
await run(
await commands.run(
cli,
["inspect-managed-artifact", managedPath, "--json"],
environment,
@@ -55,9 +60,14 @@ const verifyManagedArtifact = async ({ cli, managedPath, environment }) => {
return managedArtifact;
};
const verifyManagedMembers = async ({ cli, managedPath, environment }) => {
const verifyManagedMembers = async ({
commands,
cli,
managedPath,
environment,
}) => {
const managedMembers = json(
await run(
await commands.run(
cli,
["inspect-managed-members", managedPath, "--json"],
environment,
@@ -72,6 +82,7 @@ const verifyManagedMembers = async ({ cli, managedPath, environment }) => {
};
const verifyManagedReconstruction = async ({
commands,
cli,
managedMembers,
managedMethod,
@@ -101,7 +112,7 @@ const verifyManagedReconstruction = async ({
notes: ["packaged synthetic reconstruction import"],
};
const managedReconstruction = json(
await run(
await commands.run(
cli,
[
"import-managed-reconstruction",
@@ -121,9 +132,14 @@ const verifyManagedReconstruction = async ({
throw new Error("packaged managed reconstruction import CLI failed");
};
const verifyManagedBoundaries = async ({ cli, managedPath, environment }) => {
const verifyManagedBoundaries = async ({
commands,
cli,
managedPath,
environment,
}) => {
const managedBoundaries = json(
await run(
await commands.run(
cli,
["inspect-managed-native-boundaries", managedPath, "--json"],
environment,
@@ -140,6 +156,7 @@ const verifyManagedBoundaries = async ({ cli, managedPath, environment }) => {
};
const verifyManagedApplicationGraph = async ({
commands,
cli,
managedArtifact,
managedMembers,
@@ -158,7 +175,7 @@ const verifyManagedApplicationGraph = async ({
),
};
const managedApplicationGraph = json(
await run(
await commands.run(
cli,
[
"project-managed-application-graph",
@@ -209,6 +226,7 @@ const managedEvidence = (output, operation) => {
};
const verifyManagedNativeVerification = async ({
commands,
cli,
managedBoundaries,
environment,
@@ -243,7 +261,7 @@ const verifyManagedNativeVerification = async ({
],
};
const managedNativeVerification = json(
await run(
await commands.run(
cli,
[
"verify-managed-native-boundaries",
@@ -265,13 +283,14 @@ const verifyManagedNativeVerification = async ({
};
const verifyManagedComparison = async ({
commands,
cli,
managedPath,
managedRightPath,
environment,
}) => {
const managedComparison = json(
await run(
await commands.run(
cli,
["compare-managed-members", managedPath, managedRightPath, "--json"],
environment,
@@ -287,32 +306,37 @@ const verifyManagedComparison = async ({
};
/** Exercise all packaged managed static and workflow CLIs. */
export async function verifyManaged({ cli, workspace, environment }) {
export async function verifyManaged({ commands, cli, workspace, environment }) {
const { managedPath, managedRightPath } =
await buildManagedFixtures(workspace);
const managedArtifact = await verifyManagedArtifact({
commands,
cli,
managedPath,
environment,
});
const managedMembers = await verifyManagedMembers({
commands,
cli,
managedPath,
environment,
});
const managedMethod = managedMembers.normalized_result.methods[0];
await verifyManagedReconstruction({
commands,
cli,
managedMembers,
managedMethod,
environment,
});
const managedBoundaries = await verifyManagedBoundaries({
commands,
cli,
managedPath,
environment,
});
await verifyManagedApplicationGraph({
commands,
cli,
managedArtifact,
managedMembers,
@@ -320,11 +344,13 @@ export async function verifyManaged({ cli, workspace, environment }) {
environment,
});
await verifyManagedNativeVerification({
commands,
cli,
managedBoundaries,
environment,
});
await verifyManagedComparison({
commands,
cli,
managedPath,
managedRightPath,
+38 -20
View File
@@ -4,8 +4,6 @@ import {
} from "./lib/mcp-verifier-results.mjs";
import assert from "node:assert/strict";
import { join } from "node:path";
import { execFile } from "node:child_process";
import { promisify } from "node:util";
import { readFile, rm } from "node:fs/promises";
import { Client } from "@modelcontextprotocol/client";
@@ -14,8 +12,7 @@ import { resolveMcpStartupBudgets } from "../dist/config/mcpStartupBudgets.js";
import { parseEvidence } from "../dist/domain/evidence.js";
import * as prompts from "./verify-package-prompts.mjs";
import { json, verifyCompleteToolCatalog } from "./lib/verify-package-core.mjs";
const execute = promisify(execFile);
import { withVerifierCleanup } from "./lib/verifier-cleanup.mjs";
const verifyMcpToolsAndPrompts = async (client, mcpOptions) => {
await verifyCompleteToolCatalog(client, mcpOptions);
@@ -248,6 +245,7 @@ const verifyMcpInlineArtifactEvidence = async (
/** Connect to the packaged MCP server and verify catalog and Evidence composition. */
export async function verifyPackageMcp({
commands,
cli,
environment,
evidenceRoot,
@@ -255,7 +253,7 @@ export async function verifyPackageMcp({
}) {
const diagnosed = json(
(
await execute(cli, ["mcp", "doctor", "--json"], {
await commands.exec(cli, ["mcp", "doctor", "--json"], {
env: environment,
timeout: resolveMcpStartupBudgets(environment).doctor.milliseconds,
})
@@ -280,19 +278,39 @@ export async function verifyPackageMcp({
mcpStderr += chunk.toString();
});
const client = new Client({ name: "package-smoke", version: "1.0.0" });
try {
await client.connect(transport);
const mcpOptions = { timeout: 15_000 };
await verifyMcpToolsAndPrompts(client, mcpOptions);
await verifyMcpTargetFree(client, mcpOptions);
await verifyMcpUnknownProvider(client, mcpOptions);
await verifyMcpBinaryLifecycle(client, mcpOptions);
await verifyMcpInlineArtifactEvidence(client, mcpOptions, artifactArchive);
await verifyMcpEvidenceBundle(client, mcpOptions, evidenceRoot);
} catch (cause) {
throw new Error(`packaged MCP smoke failed: ${mcpStderr}`, { cause });
} finally {
await client.close();
await transport.close();
}
await withVerifierCleanup(async () => {
try {
await client.connect(transport);
const mcpOptions = { timeout: 15_000 };
await verifyMcpToolsAndPrompts(client, mcpOptions);
await verifyMcpTargetFree(client, mcpOptions);
await verifyMcpUnknownProvider(client, mcpOptions);
await verifyMcpBinaryLifecycle(client, mcpOptions);
await verifyMcpInlineArtifactEvidence(
client,
mcpOptions,
artifactArchive,
);
await verifyMcpEvidenceBundle(client, mcpOptions, evidenceRoot);
} catch (cause) {
throw new Error(`packaged MCP smoke failed: ${mcpStderr}`, { cause });
}
}, [
async () => {
try {
await client.close();
} catch (cause) {
commands.adopt(client, "packaged MCP client");
throw cause;
}
},
async () => {
try {
await transport.close();
} catch (cause) {
commands.adopt(transport, "packaged MCP transport");
throw cause;
}
},
]);
}
+12 -9
View File
@@ -2,27 +2,30 @@ import { mkdir } from "node:fs/promises";
import { join } from "node:path";
import { FULL_CATALOG_IDENTITY } from "../dist/catalogIdentity.js";
import { exec, execNpm } from "./lib/verify-package-core.mjs";
import { parseTarListing } from "./lib/verify-package-listing.mjs";
import { verifyPackedBridge } from "./verify-packed-bridge.mjs";
/** Create a tarball and assert production packaging constraints. */
export async function verifyPackagePack({ root, workspace }) {
export async function verifyPackagePack({ root, workspace, commands }) {
const packDirectory = join(workspace, "pack");
await mkdir(packDirectory);
const tarball = join(
packDirectory,
(
await execNpm(["pack", "--silent", "--pack-destination", packDirectory], {
cwd: root,
})
await commands.execNpm(
["pack", "--silent", "--pack-destination", packDirectory],
{
cwd: root,
},
)
).stdout.trim(),
);
const packedFiles = parseTarListing(
(await exec("tar", ["-tf", tarball])).stdout,
(await commands.exec("tar", ["-tf", tarball])).stdout,
);
const packedManifest = JSON.parse(
(await exec("tar", ["-xOf", tarball, "package/package.json"])).stdout,
(await commands.exec("tar", ["-xOf", tarball, "package/package.json"]))
.stdout,
);
if (
packedManifest.scripts?.postinstall !== undefined ||
@@ -46,7 +49,7 @@ export async function verifyPackagePack({ root, workspace }) {
if (packedFiles.some((path) => path.startsWith("package/.cache/")))
throw new Error("package included development cache or test metadata");
const skill = (
await exec("tar", [
await commands.exec("tar", [
"-xOf",
tarball,
"package/skills/reverse-engineer-anything/SKILL.md",
@@ -68,6 +71,6 @@ export async function verifyPackagePack({ root, workspace }) {
) {
throw new Error("package contained generated Python bytecode");
}
await verifyPackedBridge({ tarball, packedFiles });
await verifyPackedBridge({ tarball, packedFiles, commands });
return { tarball };
}
+3 -5
View File
@@ -1,3 +1,5 @@
import type { PackageVerifierCommands } from "./lib/package-command-owner.mjs";
/** Validate the expected fail-closed Linux fixture outcome. */
export function assertLinuxPackageProviderFailure(execution: {
readonly status: number | null;
@@ -6,12 +8,8 @@ export function assertLinuxPackageProviderFailure(execution: {
/** Run platform-specific packaged analysis checks. */
export function verifyPackagePlatform(input: {
readonly commands: Pick<PackageVerifierCommands, "run" | "runWithStatus">;
readonly cli: string;
readonly environment: Readonly<Record<string, string | undefined>>;
readonly platform?: string;
readonly runCommand?: (
command: string,
arguments_: readonly string[],
environment: Readonly<Record<string, string | undefined>>,
) => Promise<string>;
}): Promise<void>;
+8 -8
View File
@@ -1,14 +1,14 @@
import { json, run, runWithStatus } from "./lib/verify-package-core.mjs";
import { json } from "./lib/verify-package-core.mjs";
/** Run platform-specific deep-analysis smoke tests. */
export async function verifyPackagePlatform({
commands,
cli,
environment,
platform = process.platform,
runCommand = run,
}) {
if (platform === "linux") {
const unsupportedExecution = await runWithStatus(
const unsupportedExecution = await commands.runWithStatus(
cli,
["analyze", process.execPath, "--provider", "hopper", "--json"],
environment,
@@ -16,7 +16,7 @@ export async function verifyPackagePlatform({
assertLinuxPackageProviderFailure(unsupportedExecution);
} else {
const overview = json(
await runCommand(
await commands.run(
cli,
["analyze", process.execPath, "--provider", "hopper", "--json"],
environment,
@@ -27,7 +27,7 @@ export async function verifyPackagePlatform({
`packaged Hopper-backed analyze CLI failed: ${JSON.stringify(overview)}`,
);
const inspected = json(
await runCommand(
await commands.run(
cli,
["inspect", process.execPath, "--provider", "hopper", "--json"],
environment,
@@ -36,7 +36,7 @@ export async function verifyPackagePlatform({
if (!hasPositiveProcedureCount(inspected))
throw new Error("packaged inspect CLI failed");
const functionResult = json(
await runCommand(
await commands.run(
cli,
[
"function",
@@ -58,7 +58,7 @@ export async function verifyPackagePlatform({
`packaged function CLI failed: ${JSON.stringify(functionResult)}`,
);
const xrefs = json(
await runCommand(
await commands.run(
cli,
["xrefs", process.execPath, "0x1000", "--provider", "hopper", "--json"],
environment,
@@ -70,7 +70,7 @@ export async function verifyPackagePlatform({
)
throw new Error(`packaged xrefs CLI failed: ${JSON.stringify(xrefs)}`);
const trace = json(
await runCommand(
await commands.run(
cli,
[
"trace",
+20
View File
@@ -1,3 +1,5 @@
import type { PackageVerifierCommands } from "./lib/package-command-owner.mjs";
/** Validate a live registration and its byte-identical pre-setup backup. */
export function assertClientConfig(
cli: string,
@@ -7,3 +9,21 @@ export function assertClientConfig(
/** Return true only when stty output positively reports canonical input and echo. */
export function terminalModesRestored(termios: unknown): boolean;
/** Verify setup using the parent's explicit command owner. */
export function verifyPackageSetup(input: {
readonly commands: Pick<PackageVerifierCommands, "run" | "runWithStatus">;
readonly cli: string;
readonly packageRunnerCli: string;
readonly environment: NodeJS.ProcessEnv;
readonly home: string;
readonly npxLog: string;
readonly claudeConfig: string;
readonly codexConfig: string;
readonly cursorConfig: string;
readonly codexTarget: string;
readonly cursorTarget: string;
readonly supportedSetupHost: boolean;
readonly hopperSetupSupported: boolean | undefined;
readonly root: string;
}): Promise<void>;
+53 -29
View File
@@ -13,12 +13,7 @@ import { spawn } from "@lydell/node-pty";
import { TOOL_CONTRACTS } from "../dist/contracts/toolContracts.js";
import { PRODUCT_IDENTITY } from "../dist/identity.js";
import {
json,
pathExists,
run,
runWithStatus,
} from "./lib/verify-package-core.mjs";
import { json, pathExists } from "./lib/verify-package-core.mjs";
import { parse as parseJsonc } from "jsonc-parser";
import { skillReferenceIssues } from "./lib/docs-facts.mjs";
import { setupPlanFailureMessage } from "./verify-package-setup-plan.mjs";
@@ -46,8 +41,8 @@ const CODEX_ORIGINAL = [
"",
].join("\n");
const verifyMcpAdd = async ({ cli, environment, npxLog }) => {
await run(cli, ["mcp", "add"], environment);
const verifyMcpAdd = async ({ commands, cli, environment, npxLog }) => {
await commands.run(cli, ["mcp", "add"], environment);
const mcpRegistration = await readFile(npxLog, "utf8");
const expected = `add-mcp npx -y ${PRODUCT_IDENTITY.registrationPackageSpecifier} mcp --name rea`;
if (!mcpRegistration.includes(expected))
@@ -235,6 +230,7 @@ const verifyRedirectedStdoutSetup = async ({
});
const verifySetupPlan = async ({
commands,
cli,
environment,
claudeConfig,
@@ -244,7 +240,7 @@ const verifySetupPlan = async ({
opencodeOriginal,
supportedSetupHost,
}) => {
const plannedExecution = await runWithStatus(
const plannedExecution = await commands.runWithStatus(
cli,
["setup", "--all-detected", "--dry-run", "--json"],
environment,
@@ -287,12 +283,17 @@ const verifySetupPlan = async ({
return { planned };
};
const verifyAbsentOpenCodeSetup = async ({ cli, environment, home }) => {
const verifyAbsentOpenCodeSetup = async ({
commands,
cli,
environment,
home,
}) => {
const xdgConfigHome = join(home, ".xdg-opencode-absent");
const configPath = join(xdgConfigHome, "opencode", "opencode.json");
if (await pathExists(configPath))
throw new Error("packaged OpenCode first-run fixture was not absent");
const execution = await runWithStatus(
const execution = await commands.runWithStatus(
cli,
["setup", "--yes", "--client", "opencode", "--skill=false", "--json"],
{ ...environment, XDG_CONFIG_HOME: xdgConfigHome },
@@ -313,7 +314,12 @@ const verifyAbsentOpenCodeSetup = async ({ cli, environment, home }) => {
await rm(xdgConfigHome, { recursive: true, force: true });
};
const verifyAbsentClaudeCodeSetup = async ({ cli, environment, home }) => {
const verifyAbsentClaudeCodeSetup = async ({
commands,
cli,
environment,
home,
}) => {
const freshHome = join(home, ".claude-code-absent-home");
const configPath = join(freshHome, ".claude.json");
const markerPath = join(freshHome, ".claude");
@@ -336,7 +342,11 @@ const verifyAbsentClaudeCodeSetup = async ({ cli, environment, home }) => {
"--skill=false",
"--json",
];
const firstExecution = await runWithStatus(cli, args, childEnvironment);
const firstExecution = await commands.runWithStatus(
cli,
args,
childEnvironment,
);
const first = json(firstExecution.stdout);
const config = json(await readFile(configPath, "utf8"));
const registration = first.doctor.identity?.registrations?.find(
@@ -355,7 +365,11 @@ const verifyAbsentClaudeCodeSetup = async ({ cli, environment, home }) => {
throw new Error(
`packaged setup did not configure an explicitly selected Claude Code with no marker directory: ${JSON.stringify(first)}`,
);
const repeatExecution = await runWithStatus(cli, args, childEnvironment);
const repeatExecution = await commands.runWithStatus(
cli,
args,
childEnvironment,
);
const repeat = json(repeatExecution.stdout);
if (
repeatExecution.status !== 0 ||
@@ -370,6 +384,7 @@ const verifyAbsentClaudeCodeSetup = async ({ cli, environment, home }) => {
};
const verifySetupApply = async ({
commands,
cli,
environment,
supportedSetupHost,
@@ -378,19 +393,19 @@ const verifySetupApply = async ({
const status = "ready";
const hopperStatus =
hopperSetupSupported === true ? "needs_confirmation" : "needs_human";
const firstExecution = await runWithStatus(
const firstExecution = await commands.runWithStatus(
cli,
["setup", "--yes", "--all-detected", "--json"],
environment,
);
const first = json(firstExecution.stdout);
const alignedExecution = await runWithStatus(
const alignedExecution = await commands.runWithStatus(
cli,
["setup", "--all-detected", "--json"],
environment,
);
const aligned = json(alignedExecution.stdout);
const secondExecution = await runWithStatus(
const secondExecution = await commands.runWithStatus(
cli,
[
"setup",
@@ -520,8 +535,8 @@ const assertSkill = async ({ skillPath, siblingSkillPath, root }) => {
throw new Error("packaged skill installation modified a sibling skill");
};
const assertAlignedDoctor = async (cli, environment) => {
const alignedDoctorExecution = await runWithStatus(
const assertAlignedDoctor = async (commands, cli, environment) => {
const alignedDoctorExecution = await commands.runWithStatus(
cli,
["doctor", "--json"],
environment,
@@ -540,6 +555,7 @@ const assertAlignedDoctor = async (cli, environment) => {
};
const verifyConfigReadback = async ({
commands,
cli,
environment,
claudeConfig,
@@ -575,10 +591,11 @@ const verifyConfigReadback = async ({
throw new Error(
"packaged OpenCode backup did not preserve the original JSONC",
);
await assertAlignedDoctor(cli, environment);
await assertAlignedDoctor(commands, cli, environment);
};
const verifySetupFailureRecovery = async ({
commands,
cli,
environment,
claudeConfig,
@@ -587,7 +604,7 @@ const verifySetupFailureRecovery = async ({
}) => {
await writeFile(claudeConfig, "malformed");
await writeFile(cursorConfig, '{"existing":true}\n');
const failedExecution = await runWithStatus(
const failedExecution = await commands.runWithStatus(
cli,
["setup", "--yes", "--all-detected", "--json"],
environment,
@@ -607,7 +624,7 @@ const verifySetupFailureRecovery = async ({
`packaged setup mutated clients after a preflight failure: ${JSON.stringify(failed)}`,
);
await writeFile(claudeConfig, "{}\n");
const recoveredExecution = await runWithStatus(
const recoveredExecution = await commands.runWithStatus(
cli,
["setup", "--yes", "--all-detected", "--json"],
environment,
@@ -621,12 +638,12 @@ const verifySetupFailureRecovery = async ({
throw new Error("packaged setup did not recover");
};
const verifyDanglingSymlink = async ({ cli, environment, home }) => {
const verifyDanglingSymlink = async ({ commands, cli, environment, home }) => {
const geminiDir = join(home, ".gemini");
const geminiConfig = join(geminiDir, "settings.json");
await mkdir(geminiDir, { recursive: true });
await symlink(join(home, "missing-gemini.json"), geminiConfig);
const danglingExecution = await runWithStatus(
const danglingExecution = await commands.runWithStatus(
cli,
["setup", "--yes", "--client", "gemini_cli", "--json"],
environment,
@@ -649,6 +666,7 @@ const verifyDanglingSymlink = async ({ cli, environment, home }) => {
};
const verifyUninstall = async ({
commands,
cli,
environment,
cursorConfig,
@@ -657,7 +675,7 @@ const verifyUninstall = async ({
codexTarget,
opencodeConfig,
}) => {
const uninstallExecution = await runWithStatus(
const uninstallExecution = await commands.runWithStatus(
cli,
["uninstall", "--json"],
environment,
@@ -707,6 +725,7 @@ const verifyUninstall = async ({
/** Verify setup transactions, skill installation, and uninstall for supported hosts. */
export async function verifyPackageSetup({
commands,
cli,
packageRunnerCli,
environment,
@@ -721,7 +740,7 @@ export async function verifyPackageSetup({
hopperSetupSupported,
root,
}) {
await verifyMcpAdd({ cli, environment, npxLog });
await verifyMcpAdd({ commands, cli, environment, npxLog });
const skillPath = join(
home,
".agents/skills/reverse-engineer-anything/SKILL.md",
@@ -764,6 +783,7 @@ export async function verifyPackageSetup({
unchangedPaths,
});
await verifySetupPlan({
commands,
cli,
environment,
claudeConfig,
@@ -774,15 +794,17 @@ export async function verifyPackageSetup({
supportedSetupHost,
});
const apply = await verifySetupApply({
commands,
cli,
environment,
supportedSetupHost,
hopperSetupSupported,
});
if (supportedSetupHost) {
await verifyAbsentOpenCodeSetup({ cli, environment, home });
await verifyAbsentClaudeCodeSetup({ cli, environment, home });
await verifyAbsentOpenCodeSetup({ commands, cli, environment, home });
await verifyAbsentClaudeCodeSetup({ commands, cli, environment, home });
await verifyConfigReadback({
commands,
cli,
environment,
claudeConfig,
@@ -798,14 +820,16 @@ export async function verifyPackageSetup({
cursorOriginal,
});
await verifySetupFailureRecovery({
commands,
cli,
environment,
claudeConfig,
cursorConfig,
status: apply.status,
});
await verifyDanglingSymlink({ cli, environment, home });
await verifyDanglingSymlink({ commands, cli, environment, home });
await verifyUninstall({
commands,
cli,
environment,
cursorConfig,
+3 -3
View File
@@ -1,8 +1,8 @@
import { json, runWithStatus } from "./lib/verify-package-core.mjs";
import { json } from "./lib/verify-package-core.mjs";
/** Verify that an unknown --provider is rejected by the packaged CLI. */
export async function verifyUnknownProvider({ cli, environment }) {
const unknownProviderExecution = await runWithStatus(
export async function verifyUnknownProvider({ commands, cli, environment }) {
const unknownProviderExecution = await commands.runWithStatus(
cli,
["analyze", process.execPath, "--provider", "missing-provider", "--json"],
environment,
+19 -117
View File
@@ -1,108 +1,12 @@
import { createHash, randomUUID } from "node:crypto";
import { createHash } from "node:crypto";
import { createServer } from "node:http";
import { readFile, writeFile, mkdir } from "node:fs/promises";
import { dirname, join } from "node:path";
import { pathToFileURL } from "node:url";
import { exec, json } from "./lib/verify-package-core.mjs";
import { json } from "./lib/verify-package-core.mjs";
import { withVerifierCleanup } from "./lib/verifier-cleanup.mjs";
/** Keep the live updater owner and its workspace when process cleanup is unproven. */
export class PackageUpdateCleanupFailure extends AggregateError {
constructor(primary, cleanupOwner, result, workspace) {
const cleanup = new Error(result.reason);
super(
primary === undefined ? [cleanup] : [primary, cleanup],
`Package updater cleanup incomplete; retained ${workspace}`,
{ cause: primary },
);
this.cleanupOwner = cleanupOwner;
this.workspace = workspace;
}
}
/** Own the helper and installer descendants until output and process cleanup finish. */
async function runUpdater({
harness,
environment,
signal,
workspace,
retainWorkspace,
}) {
// Only updater execution needs built process ownership; importing this fixture
// does not make unrelated source-only verifier helpers depend on dist.
const { ProviderProcessSupervisor, spawnOwnedProviderProcess } =
await import("../dist/process/ProviderProcess.js");
const { cleanupOwnedProcessGroup } =
await import("../dist/process/ProcessOwnership.js");
const launch = await spawnOwnedProviderProcess({
command: process.execPath,
arguments: [harness],
expectedCommand: null,
hostEnvironment: environment,
cwd: workspace,
runId: randomUUID(),
sweepTokenOwnedProcesses: true,
signal,
});
let collectionFailure;
const supervisor = new ProviderProcessSupervisor(
{
...launch,
ownsProcessLifetime: true,
cleanup:
launch.cleanup ?? (() => cleanupOwnedProcessGroup(launch.ownership)),
},
{
maxDiagnosticBytes: 16 * 1024 * 1024,
onDiagnostic(event) {
if (event.type === "output" && event.truncated)
collectionFailure ??= new Error(
"Package updater exceeded its diagnostic capacity",
);
if (event.type === "error")
collectionFailure ??= new Error(event.message, {
cause: event.cause,
});
},
},
);
let failure;
let stdout;
try {
while (!(await supervisor.waitForOutputClose(50))) {
signal.throwIfAborted();
if (collectionFailure !== undefined) throw collectionFailure;
}
signal.throwIfAborted();
if (collectionFailure !== undefined) throw collectionFailure;
const snapshot = supervisor.snapshot();
if (
snapshot.diagnosticTruncated ||
snapshot.exitCode !== 0 ||
snapshot.signal !== null
)
throw new Error(
`Package updater failed: ${snapshot.stdout.text}\n${snapshot.stderr.text}`,
);
stdout = snapshot.stdout.text;
} catch (cause) {
failure = { cause };
}
const stopped = await supervisor.stop();
if (stopped.status === "incomplete") {
retainWorkspace?.();
throw new PackageUpdateCleanupFailure(
failure?.cause,
supervisor,
stopped,
workspace,
);
}
if (failure !== undefined) throw failure.cause;
return stdout;
}
/** Exercise the packaged updater with real npm and a local release registry fixture. */
export async function verifyPackageUpdate({
prefix,
@@ -110,7 +14,7 @@ export async function verifyPackageUpdate({
tarball,
workspace,
environment,
retainWorkspace,
commands,
}) {
const metadataPath = join(packageRoot, "package.json");
const metadata = json(await readFile(metadataPath, "utf8"));
@@ -126,8 +30,8 @@ export async function verifyPackageUpdate({
const fixture = new AbortController();
const server = createServer((request, response) => {
void serveRegistry(request, response).catch((error) => {
fixtureFailure ??= error;
fixture.abort(fixtureFailure);
fixtureFailure ??= { cause: error };
fixture.abort(fixtureFailure.cause);
if (response.headersSent) {
response.destroy();
return;
@@ -222,7 +126,7 @@ export async function verifyPackageUpdate({
npm_config_registry: registry,
};
const previous = (
await exec(process.execPath, [entry, "--version"], {
await commands.exec(process.execPath, [entry, "--version"], {
env: runtimeEnvironment,
})
).stdout.trim();
@@ -241,13 +145,14 @@ if (result.status === "failed") process.exitCode = 1;
`,
);
const result = json(
await runUpdater({
harness,
environment: runtimeEnvironment,
signal: fixture.signal,
workspace,
retainWorkspace,
}),
(
await commands.exec(process.execPath, [harness], {
env: runtimeEnvironment,
cwd: workspace,
signal: fixture.signal,
diagnosticBytes: 16 * 1024 * 1024,
})
).stdout,
);
if (
result.status !== "updated" ||
@@ -265,14 +170,14 @@ if (result.status === "failed") process.exitCode = 1;
"Update applied integration maintenance without approval",
);
const current = (
await exec(process.execPath, [entry, "--version"], {
await commands.exec(process.execPath, [entry, "--version"], {
env: runtimeEnvironment,
})
).stdout.trim();
if (current !== metadata.version)
throw new Error(`Updated executable reported ${current}`);
const help = (
await exec(process.execPath, [entry, "--help"], {
await commands.exec(process.execPath, [entry, "--help"], {
env: runtimeEnvironment,
})
).stdout;
@@ -280,18 +185,15 @@ if (result.status === "failed") process.exitCode = 1;
throw new Error(
"Updated CLI did not expose update as its sole maintenance command",
);
if (fixtureFailure !== undefined) throw fixtureFailure;
if (fixtureFailure !== undefined) throw fixtureFailure.cause;
return {
installed_version: result.installedVersion,
exact_release: true,
maintenance_read_only: true,
};
} catch (error) {
// A retained process owner takes precedence over its triggering fixture
// failure; otherwise report the first registry/proxy failure exactly.
throw error instanceof PackageUpdateCleanupFailure
? error
: (fixtureFailure ?? error);
// The registry owns its failure; command cleanup remains with commands.
throw fixtureFailure === undefined ? error : fixtureFailure.cause;
}
}, [
async () => {
+24 -22
View File
@@ -1,8 +1,6 @@
import { mkdir, mkdtemp, rm, unlink } from "node:fs/promises";
import { execFile } from "node:child_process";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { promisify } from "node:util";
import { TOOL_CONTRACTS } from "../dist/contracts/toolContracts.js";
import { PRODUCT_IDENTITY } from "../dist/identity.js";
@@ -22,6 +20,7 @@ import { verifyPackageSetup } from "./verify-package-setup.mjs";
import { verifyRepositorySkill } from "./verify-repository-skill.mjs";
import { verifyManaged } from "./verify-package-managed.mjs";
import { verifyUnknownProvider } from "./verify-package-unknown-provider.mjs";
import { createPackageVerifierCommands } from "./lib/verify-package-commands.mjs";
import { withVerifierCleanup } from "./lib/verifier-cleanup.mjs";
import { completeVerifierRun, createVerifierRun } from "./lib/verifier-run.mjs";
import {
@@ -35,7 +34,6 @@ import {
} from "./lib/mcp-startup-probe.mjs";
const root = process.cwd();
const execFileAsync = promisify(execFile);
const retentionDirectory = process.env.REA_VERIFIED_PACKAGE_DIRECTORY;
const verifierRun = createVerifierRun();
const workspace = await mkdtemp(join(tmpdir(), "rea-package-"));
@@ -49,7 +47,11 @@ let repositorySkill;
let report;
let retainedPackage;
let retentionMetadata;
let updateWorkspaceRetained = false;
let commands;
const releaseWorkspace = () =>
runStage("workspace cleanup", () =>
rm(workspace, { recursive: true, force: true }),
);
const runStage = async (name, action) => {
const started = performance.now();
@@ -62,9 +64,12 @@ const runStage = async (name, action) => {
};
await withVerifierCleanup(async () => {
commands = await createPackageVerifierCommands({
workspace: { path: workspace, close: releaseWorkspace },
});
if (retentionDirectory !== undefined && retentionDirectory !== "") {
const sourceSha = (
await execFileAsync("git", ["rev-parse", "HEAD"], { cwd: root })
await commands.exec("git", ["rev-parse", "HEAD"], { cwd: root })
).stdout.trim();
retentionMetadata = createVerifiedPackageMetadata({
sourceSha,
@@ -84,10 +89,10 @@ await withVerifierCleanup(async () => {
});
}
repositorySkill = await runStage("repository skill", () =>
verifyRepositorySkill({ root, workspace }),
verifyRepositorySkill({ commands, root, workspace }),
);
({ tarball } = await runStage("package archive", () =>
verifyPackagePack({ root, workspace }),
verifyPackagePack({ commands, root, workspace }),
));
const environmentData = await runStage("package environment", () =>
verifyPackageEnvironment({
@@ -99,6 +104,7 @@ await withVerifierCleanup(async () => {
);
const { cli, packageRunnerCli } = await runStage("package install", () =>
verifyPackageInstall({
commands,
tarball,
prefix: environmentData.prefix,
workspace,
@@ -107,6 +113,7 @@ await withVerifierCleanup(async () => {
);
update = await runStage("package update", () =>
verifyPackageUpdate({
commands,
prefix: environmentData.prefix,
packageRoot: join(
environmentData.prefix,
@@ -117,9 +124,6 @@ await withVerifierCleanup(async () => {
tarball,
workspace,
environment: environmentData.environment,
retainWorkspace() {
updateWorkspaceRetained = true;
},
}),
);
mcpStartup = await runStage("MCP startup", () =>
@@ -147,12 +151,14 @@ await withVerifierCleanup(async () => {
"package discovery",
() =>
verifyPackageDiscovery({
commands,
cli,
environment: environmentData.environment,
}),
);
const { artifactArchive } = await runStage("artifact and Electron CLI", () =>
verifyPackageArtifactAndElectron({
commands,
cli,
workspace,
environment: environmentData.environment,
@@ -160,6 +166,7 @@ await withVerifierCleanup(async () => {
);
await runStage("managed analysis CLI", () =>
verifyManaged({
commands,
cli,
workspace,
environment: environmentData.environment,
@@ -167,24 +174,28 @@ await withVerifierCleanup(async () => {
);
await runStage("unknown provider CLI", () =>
verifyUnknownProvider({
commands,
cli,
environment: environmentData.environment,
}),
);
await runStage("platform CLI", () =>
verifyPackagePlatform({
commands,
cli,
environment: environmentData.environment,
}),
);
await runStage("capabilities and search CLI", () =>
verifyPackageCapabilitiesAndSearch({
commands,
cli,
environment: environmentData.environment,
}),
);
await runStage("evidence CLI", () =>
verifyPackageEvidence({
commands,
cli,
evidenceRoot,
referenceRoot,
@@ -193,6 +204,7 @@ await withVerifierCleanup(async () => {
);
await runStage("setup and uninstall CLI", () =>
verifyPackageSetup({
commands,
cli,
packageRunnerCli,
environment: environmentData.environment,
@@ -210,6 +222,7 @@ await withVerifierCleanup(async () => {
);
await runStage("MCP tools and evidence", () =>
verifyPackageMcp({
commands,
cli,
environment: environmentData.environment,
evidenceRoot,
@@ -259,18 +272,7 @@ await withVerifierCleanup(async () => {
metadata: retentionMetadata,
});
}
}, [
async () => {
if (updateWorkspaceRetained)
process.stderr.write(
`Package updater cleanup incomplete; retained ${workspace}\n`,
);
else
await runStage("workspace cleanup", () =>
rm(workspace, { recursive: true, force: true }),
);
},
]);
}, [() => (commands === undefined ? releaseWorkspace() : commands.close())]);
const verifierLineage = await runStage("verifier lineage", () =>
completeVerifierRun(verifierRun),
);
File diff suppressed because it is too large Load Diff
+21 -70
View File
@@ -1,60 +1,23 @@
#!/usr/bin/env node
import { join } from "node:path";
import { mkdir } from "node:fs/promises";
import { randomUUID } from "node:crypto";
import { fileURLToPath } from "node:url";
import { PrivateRuntimeRoot } from "../dist/process/PrivateRuntimeRoot.js";
import {
OwnedCommandFailure,
runOwnedCommand,
} from "../dist/process/OwnedCommand.js";
import { withVerifierCleanup } from "./lib/verifier-cleanup.mjs";
import { execNpm } from "./lib/verify-package-core.mjs";
import { createPackageVerifierCommands } from "./lib/verify-package-commands.mjs";
import { WABT_WORKFLOW_TIMEOUT_MS } from "./lib/wabt-verifier-timeouts.mjs";
import { verifyPackagePack } from "./verify-package-pack.mjs";
/** Preserve the actual dependent process/root owner across failed cleanup. */
class PackagedWasmCleanupFailure extends Error {
constructor(cause, cleanupOwner, runtimeRoot) {
super(
`Packaged WASM verifier cleanup incomplete; retained ${runtimeRoot.path}: ${cause instanceof Error ? cause.message : String(cause)}`,
{ cause },
);
this.name = "PackagedWasmCleanupFailure";
this.cleanupOwner = cleanupOwner;
this.runtimeRoot = runtimeRoot;
}
}
const root = await PrivateRuntimeRoot.create({
prefix: "rea-wabt-package-verifier-",
});
let commandCleanupOwner;
let closing;
const cleanupOwner = {
close() {
if (closing !== undefined) return closing;
let attempt;
attempt = (async () => {
if (commandCleanupOwner !== undefined) {
const stopped = await commandCleanupOwner.stop();
if (stopped.status === "incomplete")
throw new Error(stopped.reason, { cause: stopped });
commandCleanupOwner = undefined;
}
await root.close();
})().catch((cause) => {
if (closing === attempt) closing = undefined;
throw new PackagedWasmCleanupFailure(cause, cleanupOwner, root);
});
closing = attempt;
return attempt;
},
};
let commands;
const verifiedOutput = await withVerifierCleanup(async () => {
commands = await createPackageVerifierCommands({ workspace: root });
const { tarball } = await verifyPackagePack({
root: process.cwd(),
workspace: root.path,
commands,
});
const prefix = join(root.path, "prefix");
const home = join(root.path, "home");
@@ -66,7 +29,7 @@ const verifiedOutput = await withVerifierCleanup(async () => {
XDG_CONFIG_HOME: join(home, ".config"),
XDG_CACHE_HOME: join(home, ".cache"),
};
await execNpm(
await commands.execNpm(
[
"install",
"--global",
@@ -88,32 +51,20 @@ const verifiedOutput = await withVerifierCleanup(async () => {
"scripts",
"rea.mjs",
);
let verified;
try {
verified = await runOwnedCommand(
{
command: process.execPath,
arguments: [
fileURLToPath(new URL("./verify-wasm-artifact.mjs", import.meta.url)),
entrypoint,
"--require-tools",
],
cwd: process.cwd(),
hostEnvironment: environment,
expectedCommand: process.execPath,
runId: `rea-packaged-wabt-${randomUUID()}`,
sweepTokenOwnedProcesses: true,
},
{
timeoutMs: WABT_WORKFLOW_TIMEOUT_MS,
diagnosticBytes: 4 * 1024 * 1024,
},
);
} catch (cause) {
if (cause instanceof OwnedCommandFailure)
commandCleanupOwner = cause.cleanupOwner;
throw cause;
}
return verified.stdout.text;
}, [() => cleanupOwner.close()]);
const verified = await commands.exec(
process.execPath,
[
fileURLToPath(new URL("./verify-wasm-artifact.mjs", import.meta.url)),
entrypoint,
"--require-tools",
],
{
cwd: process.cwd(),
env: environment,
timeout: WABT_WORKFLOW_TIMEOUT_MS,
diagnosticBytes: 4 * 1024 * 1024,
},
);
return verified.stdout;
}, [() => (commands === undefined ? root.close() : commands.close())]);
process.stdout.write(verifiedOutput);
+3
View File
@@ -1,5 +1,8 @@
import type { PackageVerifierCommands } from "./lib/verify-package-commands.mjs";
/** Validate required shipped bridges and parse packed Python without extraction. */
export function verifyPackedBridge(input: {
readonly tarball: string;
readonly packedFiles: readonly string[];
readonly commands: PackageVerifierCommands;
}): Promise<void>;
+2 -5
View File
@@ -1,13 +1,10 @@
import { execFile } from "node:child_process";
import { promisify } from "node:util";
import { GHIDRA_BRIDGE_SOURCES } from "../src/ghidra/GhidraBridgeSources.ts";
const exec = promisify(execFile);
const packedHopperBridge = "package/bridge/hopper_bridge.py";
const packedCutterBridge = "package/bridge/cutter/rea_bridge.py";
/** Check shipped bridge files; real-provider lanes verify runtime behavior. */
export async function verifyPackedBridge({ tarball, packedFiles }) {
export async function verifyPackedBridge({ tarball, packedFiles, commands }) {
for (const path of [
packedHopperBridge,
packedCutterBridge,
@@ -20,7 +17,7 @@ export async function verifyPackedBridge({ tarball, packedFiles }) {
// Read the packed source without extracting into the caller's leased root.
// Only the interpreter's standard library is needed; caller-local modules
// and site startup hooks must not replace the parser or execute here.
await exec("python3", [
await commands.exec("python3", [
"-I",
"-S",
"-c",
+7 -6
View File
@@ -1,15 +1,15 @@
import { cp, mkdir, readFile, readdir } from "node:fs/promises";
import { dirname, join, relative } from "node:path";
import { exec, execNpm, pathExists } from "./lib/verify-package-core.mjs";
import { pathExists } from "./lib/verify-package-core.mjs";
/** Install the separately locked Skills CLI so verification resolves no registry metadata. */
const lockedSkillsCli = async (root, workspace) => {
const lockedSkillsCli = async (commands, root, workspace) => {
const tool = join(workspace, "skills-cli");
await mkdir(tool, { recursive: true });
for (const file of ["package.json", "package-lock.json"])
await cp(join(root, "scripts/verify-tools/skills", file), join(tool, file));
await execNpm(
await commands.execNpm(
["ci", "--prefer-offline", "--ignore-scripts", "--no-audit", "--no-fund"],
{ cwd: tool, timeout: 120_000 },
);
@@ -24,12 +24,12 @@ const lockedSkillsCli = async (root, workspace) => {
};
/** Exercise Skills' default root discovery without ignored build outputs. */
export async function verifyRepositorySkill({ root, workspace }) {
export async function verifyRepositorySkill({ root, workspace, commands }) {
const snapshot = join(workspace, "repository");
const consumer = join(workspace, "skill-consumer");
const home = join(workspace, "skill-consumer-home");
const tracked = (
await exec(
await commands.exec(
"git",
["ls-files", "--cached", "--format=%(objectmode) %(path)", "-z"],
{ cwd: root },
@@ -54,10 +54,11 @@ export async function verifyRepositorySkill({ root, workspace }) {
await mkdir(consumer, { recursive: true });
await mkdir(home, { recursive: true });
const { version: skillsVersion, entry } = await lockedSkillsCli(
commands,
root,
workspace,
);
await exec(
await commands.exec(
process.execPath,
[
entry,
+41 -20
View File
@@ -1,6 +1,5 @@
#!/usr/bin/env node
import { execFile } from "node:child_process";
import {
access,
mkdir,
@@ -13,30 +12,35 @@ import { tmpdir } from "node:os";
import { join } from "node:path";
import { parse as parseJsonc } from "jsonc-parser";
import { supportedClients } from "../dist/application/SupportedClients.js";
import { promisify } from "node:util";
import { GHIDRA_BRIDGE_SOURCES } from "../src/ghidra/GhidraBridgeSources.ts";
import { Client } from "@modelcontextprotocol/client";
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
import {
execNpm,
verifyCompleteToolCatalog,
} from "./lib/verify-package-core.mjs";
import { verifyCompleteToolCatalog } from "./lib/verify-package-core.mjs";
import { createPackageVerifierCommands } from "./lib/verify-package-commands.mjs";
import { withVerifierCleanup } from "./lib/verifier-cleanup.mjs";
import { verifyPackageUpdate } from "./verify-package-update.mjs";
import { completeVerifierRun, createVerifierRun } from "./lib/verifier-run.mjs";
const exec = promisify(execFile);
const verifierRun = createVerifierRun();
const root = process.cwd();
const workspace = await mkdtemp(join(tmpdir(), "rea-windows-package-"));
const workspaceOwner = {
path: workspace,
close: () => rm(workspace, { recursive: true, force: true }),
};
const prefix = join(workspace, "prefix");
let commands;
const closeWorkspace = () =>
commands === undefined ? workspaceOwner.close() : commands.close();
let report;
try {
commands = await createPackageVerifierCommands({ workspace: workspaceOwner });
const packed = JSON.parse(
(
await execNpm(
await commands.execNpm(
["pack", "--json", "--silent", "--pack-destination", workspace],
{ cwd: root, windowsHide: true },
)
@@ -69,7 +73,7 @@ try {
const tarball = join(workspace, packageResult.filename);
await access(tarball);
await execNpm(
await commands.execNpm(
["install", "--no-package-lock", "--no-save", "--prefix", prefix, tarball],
{ cwd: workspace, windowsHide: true },
);
@@ -84,7 +88,7 @@ try {
...process.env,
REA_ANALYSIS_PROVIDER: "auto",
};
const help = await exec(process.execPath, [entry, "--help"], {
const help = await commands.exec(process.execPath, [entry, "--help"], {
env: environment,
windowsHide: true,
});
@@ -103,12 +107,27 @@ try {
});
const client = new Client({ name: "rea-windows-package", version: "1.0.0" });
let toolCount = 0;
try {
await withVerifierCleanup(async () => {
await client.connect(server);
toolCount = (await verifyCompleteToolCatalog(client)).length;
} finally {
await client.close();
}
}, [
async () => {
try {
await client.close();
} catch (cause) {
commands.adopt(client, entry);
throw cause;
}
},
async () => {
try {
await server.close();
} catch (cause) {
commands.adopt(server, entry);
throw cause;
}
},
]);
const home = join(workspace, "home");
const setupEnvironment = {
@@ -148,7 +167,7 @@ try {
];
const setup = JSON.parse(
(
await exec(process.execPath, [entry, ...setupArgs], {
await commands.exec(process.execPath, [entry, ...setupArgs], {
env: setupEnvironment,
windowsHide: true,
})
@@ -188,7 +207,7 @@ try {
}
const repeated = JSON.parse(
(
await exec(process.execPath, [entry, ...setupArgs], {
await commands.exec(process.execPath, [entry, ...setupArgs], {
env: setupEnvironment,
windowsHide: true,
})
@@ -202,7 +221,7 @@ try {
throw new Error("Packaged agent setup is not idempotent");
const uninstalled = JSON.parse(
(
await exec(process.execPath, [entry, "uninstall", "--json"], {
await commands.exec(process.execPath, [entry, "uninstall", "--json"], {
env: setupEnvironment,
windowsHide: true,
})
@@ -215,11 +234,12 @@ try {
throw new Error(`Packaged uninstall did not remove ${id}`);
if (process.platform !== "win32")
await execNpm(
await commands.execNpm(
["install", "--global", "--ignore-scripts", "--prefix", prefix, tarball],
{ cwd: workspace, windowsHide: true },
);
const update = await verifyPackageUpdate({
commands,
prefix,
packageRoot:
process.platform === "win32"
@@ -241,9 +261,10 @@ try {
uninstall: true,
update,
};
} finally {
await rm(workspace, { recursive: true, force: true });
} catch (cause) {
await withVerifierCleanup(() => Promise.reject(cause), [closeWorkspace]);
}
await closeWorkspace();
process.stdout.write(
`${JSON.stringify({ verifier_run: await completeVerifierRun(verifierRun), ...report })}\n`,
);
+6 -3
View File
@@ -13,7 +13,10 @@ import {
OwnedCommandFailure,
runOwnedCommand,
} from "../process/OwnedCommand.js";
import type { ProviderProcessSnapshot } from "../process/ProviderProcess.js";
import type {
ProcessDiagnosticBudget,
ProviderProcessSnapshot,
} from "../process/ProviderProcess.js";
import { ApktoolProvider } from "./ApktoolProvider.js";
vi.mock("../process/OwnedCommand.js", async (importOriginal) => ({
@@ -37,8 +40,8 @@ const MANIFEST = [
"",
].join("\n");
const decodeTimeouts: number[] = [];
const outputBudgets: number[] = [];
const decodeTimeouts: (number | null)[] = [];
const outputBudgets: ProcessDiagnosticBudget[] = [];
afterEach(() => {
decodeTimeouts.length = 0;
+2 -2
View File
@@ -72,7 +72,7 @@ describe("native_command_timeout_ms", () => {
const calls: {
command: string;
arguments: readonly string[];
timeoutMs: number;
timeoutMs: number | null;
}[] = [];
vi.mocked(runOwnedCommand).mockImplementation(async (spawn, limits) => {
calls.push({
@@ -99,7 +99,7 @@ describe("native_command_timeout_ms", () => {
});
it("uses 60000 ms for xcrun resolution when the caller omits the deadline", async () => {
const timeouts: number[] = [];
const timeouts: (number | null)[] = [];
vi.mocked(runOwnedCommand).mockImplementation(async (_spawn, limits) => {
timeouts.push(limits.timeoutMs);
throw new OwnedCommandFailure("timeout", "Command deadline elapsed.");
+20 -7
View File
@@ -9,6 +9,7 @@ import { pipeline } from "node:stream/promises";
import type {
OwnedProviderProcessSpawnOptions,
ProviderProcessSnapshot,
ProcessDiagnosticBudget,
ProviderProcessStopResult,
SpawnedOwnedProviderProcess,
} from "./ProviderProcess.js";
@@ -82,8 +83,8 @@ export const ownedCommandResources = (
/** Output handling for one owned command. */
interface OwnedCommandLimits {
/** Retained stderr bytes, plus stdout bytes unless a sink consumes stdout. */
readonly diagnosticBytes: number;
/** Retained output budget; stdout is excluded when a sink consumes it. */
readonly diagnosticBytes: ProcessDiagnosticBudget;
/** Accept an ordinary nonzero exit after output closes without other process errors. */
readonly acceptNonZeroExit?: boolean;
/** Interpret complete physical output synchronously before awaited final cleanup. */
@@ -158,7 +159,10 @@ function throwCommandPreparationFailure(
/** Execute a single owned process with independent cleanup and bounded diagnostic retention. */
export const runOwnedCommand = async (
spawn: OwnedProviderProcessSpawnOptions,
limits: OwnedCommandLimits & { readonly timeoutMs: number },
limits: OwnedCommandLimits & {
/** A positive absolute deadline, or null to keep only caller cancellation. */
readonly timeoutMs: number | null;
},
options: {
readonly signal?: AbortSignal | undefined;
/** Synchronously accept unresolved cleanup ownership; execution still returns its original outcome. Must not throw. */
@@ -180,18 +184,27 @@ export const runOwnedCommand = async (
null,
{ cancellation: { reason: callerSignal.reason } },
);
const deadline = new ProviderStartupDeadline(limits.timeoutMs, callerSignal);
const deadline =
limits.timeoutMs === null
? undefined
: new ProviderStartupDeadline(limits.timeoutMs, callerSignal);
const control = deadline ?? {
signal: callerSignal ?? new AbortController().signal,
get interruption(): "cancelled" | undefined {
return callerSignal?.aborted === true ? "cancelled" : undefined;
},
};
try {
return await collectOwnedCommand(
spawn,
limits,
options.launcher ?? spawnOwnedProviderProcess,
deadline,
control,
callerSignal,
options.retainCleanup,
);
} finally {
deadline.dispose();
deadline?.dispose();
}
};
@@ -201,7 +214,7 @@ const collectOwnedCommand = async (
launcher: (
input: OwnedProviderProcessSpawnOptions,
) => Promise<SpawnedOwnedProviderProcess>,
deadline: ProviderStartupDeadline,
deadline: Pick<ProviderStartupDeadline, "signal" | "interruption">,
callerSignal?: AbortSignal,
retainCleanup?: (
owner: ProviderProcessSupervisor,
@@ -184,3 +184,79 @@ it("bounds simulated Linux preparation by the command deadline before launch", a
await expected;
expect(enteredLaunch).toBe(false);
});
it.each([false, true])(
"keeps no-deadline preparation pending without a timer (caller signal: %s)",
async (withCallerSignal) => {
vi.useFakeTimers();
const controller = new AbortController();
const prepared = createDeferred<void>();
const failure = new Error("authored preparation failure");
let preparationSignal: AbortSignal | undefined;
const pending = runOwnedCommand(
{
command: "never-launched",
arguments: [],
runId: "authored-no-deadline-prepare",
},
{ timeoutMs: null, diagnosticBytes: 1024 },
{
...(withCallerSignal ? { signal: controller.signal } : {}),
launcher: async (input) => {
preparationSignal = input.signal;
await prepared.promise;
throw failure;
},
},
);
const expected = expect(pending).rejects.toBe(failure);
await vi.advanceTimersByTimeAsync(10_000);
expect(preparationSignal?.aborted).toBe(false);
if (withCallerSignal) expect(preparationSignal).toBe(controller.signal);
expect(vi.getTimerCount()).toBe(0);
prepared.resolve();
await expected;
},
);
it("cancels no-deadline preparation with the caller's exact opaque reason", async () => {
const controller = new AbortController();
const reason = new DarwinProcessOwnershipInspectionError(
"opaque caller reason",
);
const started = createDeferred<void>();
const pending = runOwnedCommand(
{
command: "never-launched",
arguments: [],
runId: "authored-no-deadline-cancel",
},
{ timeoutMs: null, diagnosticBytes: 1024 },
{
signal: controller.signal,
launcher: async (input) => {
expect(input.signal).toBe(controller.signal);
started.resolve();
await new Promise<void>((_resolve, reject) =>
input.signal?.addEventListener(
"abort",
() => reject(input.signal?.reason),
{
once: true,
},
),
);
throw new Error("Cancelled preparation must not launch");
},
},
);
const expected = expect(pending).rejects.toMatchObject({
reason: "cancelled",
cancellation: { reason },
preparationFailure: undefined,
snapshot: null,
});
await started.promise;
controller.abort(reason);
await expected;
});
+36 -11
View File
@@ -193,12 +193,17 @@ export type ProviderProcessDiagnostic =
readonly cause?: Error;
};
/** Aggregate byte budget, or independent retained stdout and stderr byte budgets. */
export type ProcessDiagnosticBudget =
| number
| { readonly stdout: number; readonly stderr: number };
/** Options for process lifecycle diagnostics. */
export interface ProviderProcessSupervisorOptions {
/** Retain stdout for diagnostics; disable when it carries a parsed protocol. Defaults to true. */
readonly captureStdout?: boolean;
/** Aggregate retained stdout/stderr byte budget; overflow is drained and reported, not retained. */
readonly maxDiagnosticBytes?: number;
/** Retained output budget; overflow is drained and reported, not retained. */
readonly maxDiagnosticBytes?: ProcessDiagnosticBudget;
/**
* Single-line token ending at a non-ASCII-word byte or EOF, located before retention
* applies, so its latest occurrence survives an exhausted budget.
@@ -395,7 +400,10 @@ export class ProviderProcessSupervisor {
#stopPromise: Promise<ProviderProcessStopResult> | undefined;
#stopRequested = false;
#disposed = false;
#maxDiagnosticBytes: number | undefined;
#maxAggregateDiagnosticBytes: number | undefined;
readonly #maxStreamDiagnosticBytes:
| Exclude<ProcessDiagnosticBudget, number>
| undefined;
#outputEpoch = 0;
constructor(
@@ -403,7 +411,13 @@ export class ProviderProcessSupervisor {
options: ProviderProcessSupervisorOptions = {},
) {
this.#options = options;
this.#maxDiagnosticBytes = options.maxDiagnosticBytes;
const budget = options.maxDiagnosticBytes;
if (typeof budget === "number") this.#maxAggregateDiagnosticBytes = budget;
else if (budget !== undefined)
this.#maxStreamDiagnosticBytes = {
stdout: budget.stdout,
stderr: budget.stderr,
};
this.#resetMarkers();
if (options.captureStdout !== false)
this.#attach(launch.process.stdout, "stdout");
@@ -593,10 +607,10 @@ export class ProviderProcessSupervisor {
Number.MAX_SAFE_INTEGER,
retained + maxBytes,
);
this.#maxDiagnosticBytes =
this.#maxDiagnosticBytes === undefined
this.#maxAggregateDiagnosticBytes =
this.#maxAggregateDiagnosticBytes === undefined
? requestedLimit
: Math.min(this.#maxDiagnosticBytes, requestedLimit);
: Math.min(this.#maxAggregateDiagnosticBytes, requestedLimit);
}
/** Unreference inherited output pipes without closing streams shared with descendants. */
@@ -797,14 +811,25 @@ export class ProviderProcessSupervisor {
this.#stderrMarker?.scan(bytes, this.#stderrObservedBytes);
this.#stderrObservedBytes += bytes.byteLength;
}
const remaining =
this.#maxDiagnosticBytes === undefined
const aggregateRemaining =
this.#maxAggregateDiagnosticBytes === undefined
? bytes.byteLength
: Math.max(
0,
this.#maxDiagnosticBytes - this.#stdout.bytes - this.#stderr.bytes,
this.#maxAggregateDiagnosticBytes -
this.#stdout.bytes -
this.#stderr.bytes,
);
const retained = Math.min(bytes.byteLength, remaining);
const streamLimit = this.#maxStreamDiagnosticBytes?.[stream];
const streamRemaining =
streamLimit === undefined
? bytes.byteLength
: Math.max(0, streamLimit - capture.bytes);
const retained = Math.min(
bytes.byteLength,
aggregateRemaining,
streamRemaining,
);
if (retained < bytes.byteLength) this.#diagnosticTruncated = true;
if (retained > 0) capture.append(bytes.subarray(0, retained));
this.#options.onDiagnostic?.({
+167 -1
View File
@@ -1,7 +1,10 @@
import { EventEmitter } from "node:events";
import { PassThrough } from "node:stream";
import { expect, it, vi } from "vitest";
import { ProviderProcessSupervisor } from "./ProviderProcess.js";
import {
ProviderProcessSupervisor,
type ProcessDiagnosticBudget,
} from "./ProviderProcess.js";
import { redactCapturedTransportCredential } from "./ProviderDiagnosticRedaction.js";
it("retains final stderr between verified exit and output close during stop", async () => {
@@ -332,6 +335,169 @@ it("scopes output checkpoints to raw bytes on one supervisor and epoch", () => {
}
});
it.each<{ budget: ProcessDiagnosticBudget; stderr: string }>([
{ budget: 4, stderr: "" },
{ budget: { stdout: 4, stderr: 4 }, stderr: "wxyz" },
])(
"distinguishes aggregate and per-stream output budgets: $budget",
({ budget, stderr }) => {
const producer = Object.assign(new EventEmitter(), {
stdout: new PassThrough(),
stderr: new PassThrough(),
exitCode: null,
signalCode: null,
kill: () => false,
});
const supervisor = new ProviderProcessSupervisor(
{ process: producer, ownsProcessLifetime: false },
{ maxDiagnosticBytes: budget },
);
try {
const checkpoint = supervisor.checkpointOutput();
producer.stdout.write("abcd");
producer.stderr.write("wxyz");
expect(supervisor.snapshot().stdout).toEqual({
text: "abcd",
bytes: 4,
observedBytes: 4,
});
expect(supervisor.snapshot().stderr).toEqual({
text: stderr,
bytes: stderr.length,
observedBytes: 4,
});
expect(supervisor.snapshot().diagnosticTruncated === true).toBe(
stderr === "",
);
expect(supervisor.snapshotOutputSince(checkpoint).complete).toBe(
stderr !== "",
);
} finally {
supervisor.dispose();
producer.stdout.destroy();
producer.stderr.destroy();
}
},
);
it.each(["stdout", "stderr"] as const)(
"drains and reports overflowing %s without consuming the other stream's budget",
(stream) => {
const producer = Object.assign(new EventEmitter(), {
stdout: new PassThrough(),
stderr: new PassThrough(),
exitCode: null,
signalCode: null,
kill: () => false,
});
const events: unknown[] = [];
const supervisor = new ProviderProcessSupervisor(
{ process: producer, ownsProcessLifetime: false },
{
maxDiagnosticBytes: { stdout: 4, stderr: 4 },
onDiagnostic: (event) => {
if (event.type === "output") events.push(event);
},
},
);
const other = stream === "stdout" ? "stderr" : "stdout";
try {
producer[stream].write("abcde");
producer[stream].write("fg");
producer[other].write("wxyz");
expect(supervisor.snapshot()).toMatchObject({
diagnosticTruncated: true,
[stream]: { text: "abcd", bytes: 4, observedBytes: 7 },
[other]: { text: "wxyz", bytes: 4, observedBytes: 4 },
});
expect(events).toEqual([
{ type: "output", stream, bytes: 5, totalBytes: 5, truncated: true },
{ type: "output", stream, bytes: 2, totalBytes: 7, truncated: true },
{ type: "output", stream: other, bytes: 4, totalBytes: 4 },
]);
} finally {
supervisor.dispose();
producer.stdout.destroy();
producer.stderr.destroy();
}
},
);
it("tightens aggregate retention without replacing or relaxing the original stream caps", () => {
const producer = Object.assign(new EventEmitter(), {
stdout: new PassThrough(),
stderr: new PassThrough(),
exitCode: null,
signalCode: null,
kill: () => false,
});
const supervisor = new ProviderProcessSupervisor(
{ process: producer, ownsProcessLifetime: false },
{ maxDiagnosticBytes: { stdout: 4, stderr: 10 } },
);
try {
producer.stdout.write("abc");
producer.stderr.write("de");
supervisor.limitAdditionalOutputBytes(4);
producer.stdout.write("fgh");
producer.stderr.write("ijklmn");
expect(supervisor.snapshot()).toMatchObject({
diagnosticTruncated: true,
stdout: { text: "abcf", bytes: 4, observedBytes: 6 },
stderr: { text: "deijk", bytes: 5, observedBytes: 8 },
});
supervisor.limitAdditionalOutputBytes(100);
producer.stderr.write("op");
expect(supervisor.snapshot().stderr).toEqual({
text: "deijk",
bytes: 5,
observedBytes: 10,
});
} finally {
supervisor.dispose();
producer.stdout.destroy();
producer.stderr.destroy();
}
});
it.each(["stdout", "stderr"] as const)(
"retains raw UTF-8 and checkpoint facts while scanning %s markers past its stream cap",
(stream) => {
const producer = Object.assign(new EventEmitter(), {
stdout: new PassThrough(),
stderr: new PassThrough(),
exitCode: null,
signalCode: null,
kill: () => false,
});
const supervisor = new ProviderProcessSupervisor(
{ process: producer, ownsProcessLifetime: false },
{ maxDiagnosticBytes: { stdout: 4, stderr: 4 }, outputToken: "DONE" },
);
try {
const checkpoint = supervisor.checkpointOutput();
const bytes = Buffer.from("é🧭 DONE\n");
producer[stream].write(bytes.subarray(0, 3));
producer[stream].write(bytes.subarray(3));
const scoped = supervisor.snapshotOutputSince(checkpoint);
expect(scoped.complete).toBe(false);
expect(scoped.snapshot[stream]).toEqual({
text: "é\ufffd",
bytes: 4,
observedBytes: 12,
invalidUtf8: true,
});
expect(scoped.markers).toEqual([
{ stream, offset: 7, line: "é🧭 DONE", truncated: false },
]);
} finally {
supervisor.dispose();
producer.stdout.destroy();
producer.stderr.destroy();
}
},
);
it.each(["stdout", "stderr"] as const)(
"redacts a budget-cut credential in scoped %s after prior output",
(stream) => {
@@ -210,7 +210,15 @@ it("rejects an omitted auxiliary source at the actual package verification bound
"--input-type=module",
"-e",
`import { verifyPackedBridge } from "./scripts/verify-packed-bridge.mjs";
await verifyPackedBridge({ tarball: "unused-because-admission-must-fail", packedFiles: JSON.parse(process.argv[1]) });`,
import { createPackageVerifierCommands } from "./scripts/lib/verify-package-commands.mjs";
import { withVerifierCleanup } from "./scripts/lib/verifier-cleanup.mjs";
import { PrivateRuntimeRoot } from "./dist/process/PrivateRuntimeRoot.js";
const root = await PrivateRuntimeRoot.create({ prefix: "rea-bridge-admission-" });
let commands;
await withVerifierCleanup(async () => {
commands = await createPackageVerifierCommands({ workspace: root });
await verifyPackedBridge({ tarball: "unused-because-admission-must-fail", packedFiles: JSON.parse(process.argv[1]), commands });
}, [() => commands === undefined ? root.close() : commands.close()]);`,
JSON.stringify(packedFiles),
]),
).rejects.toMatchObject({
+97 -85
View File
@@ -95,17 +95,20 @@ it("bounds retained diagnostics across both streams during a real output burst",
}
});
it("collects a real owned command and verifies its exit before returning diagnostics", async () => {
const result = await runOwnedCommand(
command('process.stdout.write("observed-output")'),
{ timeoutMs: 2_000, diagnosticBytes: 1024 },
);
expect(result).toMatchObject({
exitCode: 0,
signal: null,
stdout: { text: "observed-output", bytes: 15 },
});
});
it.each([2_000, null])(
"collects a real owned command and verifies its exit before returning diagnostics (timeout %s)",
async (timeoutMs) => {
const result = await runOwnedCommand(
command('process.stdout.write("observed-output")'),
{ timeoutMs, diagnosticBytes: 1024 },
);
expect(result).toMatchObject({
exitCode: 0,
signal: null,
stdout: { text: "observed-output", bytes: 15 },
});
},
);
it.each([0, 2])(
"observes complete accepted output once before final cleanup (exit %s)",
@@ -155,50 +158,56 @@ it("does not interpret output rejected by its complete-output budget", async ()
expect(observeCompleted).not.toHaveBeenCalled();
});
it("retains late cancellation separately from a physical failure with refused cleanup", async () => {
const controller = new AbortController();
const callerReason = { request: "cancel during refused finalization" };
let denied = true;
let failure: OwnedCommandFailure | undefined;
try {
await runOwnedCommand(
command("process.exit(2)"),
{ timeoutMs: 2000, diagnosticBytes: 1024 },
{
signal: controller.signal,
launcher: async (input) => {
const acquired = await spawnOwnedProviderProcess(input);
return {
...acquired,
cleanup: async () => {
if (denied) {
controller.abort(callerReason);
return { cleaned: false, reason: "controlled cleanup refusal" };
}
return cleanupOwnedProcessGroup(acquired.ownership);
},
};
it.each([2000, null])(
"retains late cancellation separately from a physical failure with refused cleanup (timeout %s)",
async (timeoutMs) => {
const controller = new AbortController();
const callerReason = { request: "cancel during refused finalization" };
let denied = true;
let failure: OwnedCommandFailure | undefined;
try {
await runOwnedCommand(
command("process.exit(2)"),
{ timeoutMs, diagnosticBytes: 1024 },
{
signal: controller.signal,
launcher: async (input) => {
const acquired = await spawnOwnedProviderProcess(input);
return {
...acquired,
cleanup: async () => {
if (denied) {
controller.abort(callerReason);
return {
cleaned: false,
reason: "controlled cleanup refusal",
};
}
return cleanupOwnedProcessGroup(acquired.ownership);
},
};
},
},
},
).catch((cause: unknown) => {
if (!(cause instanceof OwnedCommandFailure)) throw cause;
failure = cause;
});
expect(failure).toMatchObject({
reason: "process",
snapshot: { exitCode: 2 },
cleanupFailure: "controlled cleanup refusal",
});
expect(failure?.cancellation?.reason).toBe(callerReason);
denied = false;
expect(await failure?.cleanupOwner?.stop()).toEqual({
status: "verified-cleanup",
});
} finally {
denied = false;
await failure?.cleanupOwner?.stop();
}
});
).catch((cause: unknown) => {
if (!(cause instanceof OwnedCommandFailure)) throw cause;
failure = cause;
});
expect(failure).toMatchObject({
reason: "process",
snapshot: { exitCode: 2 },
cleanupFailure: "controlled cleanup refusal",
});
expect(failure?.cancellation?.reason).toBe(callerReason);
denied = false;
expect(await failure?.cleanupOwner?.stop()).toEqual({
status: "verified-cleanup",
});
} finally {
denied = false;
await failure?.cleanupOwner?.stop();
}
},
);
it("keeps an exit failure observed before cancellation primary across delayed classification", async () => {
const controller = new AbortController();
@@ -576,38 +585,41 @@ it.skipIf(process.platform === "win32")(
},
);
it("cancels a real acquired process and independently releases it", async () => {
const controller = new AbortController();
const callerReason = {
request: "active cancellation",
cleanup: "caller metadata",
};
let pid: number | undefined;
const result = runOwnedCommand(
command(
'process.stdout.write("ready\\n"); setInterval(() => undefined, 1000)',
),
{ timeoutMs: 5_000, diagnosticBytes: 1024 },
{
signal: controller.signal,
launcher: async (input) => {
const process = await spawnOwnedProviderProcess(input);
pid = process.process.pid;
await waitForProviderProcessReady(process.process);
setImmediate(() => controller.abort(callerReason));
return process;
it.each([5_000, null])(
"cancels a real acquired process and independently releases it (timeout %s)",
async (timeoutMs) => {
const controller = new AbortController();
const callerReason = {
request: "active cancellation",
cleanup: "caller metadata",
};
let pid: number | undefined;
const result = runOwnedCommand(
command(
'process.stdout.write("ready\\n"); setInterval(() => undefined, 1000)',
),
{ timeoutMs, diagnosticBytes: 1024 },
{
signal: controller.signal,
launcher: async (input) => {
const process = await spawnOwnedProviderProcess(input);
pid = process.process.pid;
await waitForProviderProcessReady(process.process);
setImmediate(() => controller.abort(callerReason));
return process;
},
},
},
);
await expect(result).rejects.toMatchObject({
reason: "cancelled",
cleanupFailure: null,
cancellation: { reason: callerReason },
});
if (pid === undefined) throw new Error("Process was not acquired");
const acquiredPid = pid;
expect(() => process.kill(acquiredPid, 0)).toThrow();
});
);
await expect(result).rejects.toMatchObject({
reason: "cancelled",
cleanupFailure: null,
cancellation: { reason: callerReason },
});
if (pid === undefined) throw new Error("Process was not acquired");
const acquiredPid = pid;
expect(() => process.kill(acquiredPid, 0)).toThrow();
},
);
it("returns a timeout for a running process after independent cleanup", async () => {
await expect(
@@ -1,49 +1,61 @@
import { execFile } from "node:child_process";
import { copyFile, mkdir, writeFile } from "node:fs/promises";
import { dirname, join, resolve } from "node:path";
import { pathToFileURL } from "node:url";
import { promisify } from "node:util";
import { expect, it } from "vitest";
import { expect } from "vitest";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
import { processTest } from "../../support/process/processFixture.js";
const execute = promisify(execFile);
it("runs the caller-selected npm entry through Node without rewriting its inputs", async () => {
const root = await createTestTempDirectory("rea-npm-invocation-");
const npmEntry = join(root, "selected npm entry.mjs");
await writeFile(
npmEntry,
`process.stdout.write(JSON.stringify({ arguments: process.argv.slice(2), cwd: process.cwd(), marker: process.env.REA_NPM_TEST_MARKER }));\n`,
);
const helper = pathToFileURL(
resolve("scripts/lib/verify-package-core.mjs"),
).href;
const program = `
const { execNpm } = await import(${JSON.stringify(helper)});
const result = await execNpm(["pack", "value with spaces"], {
cwd: ${JSON.stringify(root)},
env: { ...process.env, npm_execpath: ${JSON.stringify(npmEntry)}, REA_NPM_TEST_MARKER: "caller-selected" },
processTest(
"runs the caller-selected npm entry through Node without rewriting its inputs",
async ({ processes }) => {
const root = await createTestTempDirectory("rea-npm-invocation-");
const npmEntry = join(root, "selected npm entry.mjs");
await writeFile(
npmEntry,
`process.stdout.write(JSON.stringify({ arguments: process.argv.slice(2), cwd: process.cwd(), marker: process.env.REA_NPM_TEST_MARKER }));\n`,
);
const helper = pathToFileURL(
resolve("scripts/lib/verify-package-commands.mjs"),
).href;
const cleanup = pathToFileURL(
resolve("scripts/lib/verifier-cleanup.mjs"),
).href;
const program = `
const { createPackageVerifierCommands } = await import(${JSON.stringify(helper)});
const { withVerifierCleanup } = await import(${JSON.stringify(cleanup)});
const commands = await createPackageVerifierCommands({
workspace: { path: ${JSON.stringify(root)}, close: async () => {} },
});
const result = await withVerifierCleanup(
() => commands.execNpm(["pack", "value with spaces"], {
cwd: ${JSON.stringify(root)},
env: { ...process.env, npm_execpath: ${JSON.stringify(npmEntry)}, REA_NPM_TEST_MARKER: "caller-selected" },
}),
[() => commands.close()],
);
process.stdout.write(result.stdout);
`;
const result = await execute(
process.execPath,
["--input-type=module", "--eval", program],
{ windowsHide: true },
);
expect(JSON.parse(result.stdout)).toEqual({
arguments: ["pack", "value with spaces"],
cwd: root,
marker: "caller-selected",
});
});
const result = await processes.run(
process.execPath,
["--input-type=module", "--eval", program],
{ windowsHide: true, timeoutMs: 20_000 },
);
expect(result.exitCode, result.stderr).toBe(0);
expect(result.signal, result.stderr).toBeNull();
expect(JSON.parse(result.stdout)).toEqual({
arguments: ["pack", "value with spaces"],
cwd: root,
marker: "caller-selected",
});
},
);
const windowsIt = process.platform === "win32" ? it : it.skip;
const windowsIt = processTest.skipIf(process.platform !== "win32");
windowsIt(
"uses the npm entry adjacent to Node when none is selected",
async () => {
async ({ processes }) => {
const root = await createTestTempDirectory("rea-adjacent-npm-");
const node = join(root, "node.exe");
const npmEntry = join(root, "node_modules/npm/bin/npm-cli.js");
@@ -56,25 +68,37 @@ windowsIt(
),
]);
const helper = pathToFileURL(
resolve("scripts/lib/verify-package-core.mjs"),
resolve("scripts/lib/verify-package-commands.mjs"),
).href;
const cleanup = pathToFileURL(
resolve("scripts/lib/verifier-cleanup.mjs"),
).href;
const program = `
const { execNpm } = await import(${JSON.stringify(helper)});
const { createPackageVerifierCommands } = await import(${JSON.stringify(helper)});
const { withVerifierCleanup } = await import(${JSON.stringify(cleanup)});
const commands = await createPackageVerifierCommands({
workspace: { path: ${JSON.stringify(root)}, close: async () => {} },
});
const environment = { ...process.env };
delete environment.npm_execpath;
const result = await execNpm(["pack", "value with spaces"], {
cwd: ${JSON.stringify(root)},
env: environment,
});
const result = await withVerifierCleanup(
() => commands.execNpm(["pack", "value with spaces"], {
cwd: ${JSON.stringify(root)},
env: environment,
}),
[() => commands.close()],
);
process.stdout.write(result.stdout);
`;
const environment = { ...process.env };
delete environment.npm_execpath;
const result = await execute(
const result = await processes.run(
node,
["--input-type=module", "--eval", program],
{ env: environment, windowsHide: true },
{ env: environment, windowsHide: true, timeoutMs: 20_000 },
);
expect(result.exitCode, result.stderr).toBe(0);
expect(result.signal, result.stderr).toBeNull();
expect(JSON.parse(result.stdout)).toEqual({
arguments: ["pack", "value with spaces"],
cwd: root,
@@ -38,19 +38,30 @@ it("keeps npm pack output in the owned workspace when package validation fails",
const packerUrl = pathToFileURL(
resolve("scripts/verify-package-pack.mjs"),
).href;
const commandsUrl = pathToFileURL(
resolve("scripts/lib/verify-package-commands.mjs"),
).href;
const cleanupUrl = pathToFileURL(
resolve("scripts/lib/verifier-cleanup.mjs"),
).href;
const harness = `
import { rm } from "node:fs/promises";
import { createPackageVerifierCommands } from ${JSON.stringify(commandsUrl)};
import { withVerifierCleanup } from ${JSON.stringify(cleanupUrl)};
const workspace = ${JSON.stringify(workspace)};
const scratch = { path: workspace, close: () => rm(workspace, { recursive: true, force: true }) };
let commands;
let failure;
await withVerifierCleanup(async () => {
commands = await createPackageVerifierCommands({ workspace: scratch });
try {
const { verifyPackagePack } = await import(${JSON.stringify(packerUrl)});
await verifyPackagePack({ root: ${JSON.stringify(packageRoot)}, workspace });
await verifyPackagePack({ root: ${JSON.stringify(packageRoot)}, workspace, commands });
} catch (cause) {
if (!(cause instanceof Error) || cause.message !== ${JSON.stringify(expectedFailure)}) throw cause;
failure = cause.message;
} finally {
await rm(workspace, { recursive: true, force: true });
}
}, [() => commands === undefined ? scratch.close() : commands.close()]);
if (failure !== ${JSON.stringify(expectedFailure)}) throw new Error("Expected package metadata validation to fail after packing");
process.stdout.write(JSON.stringify({ failure }));
`;
@@ -33,13 +33,9 @@ async function verifyListing(listing: string) {
const { stdout } = await execFileAsync(process.execPath, [
"--input-type=module",
"--eval",
`import childProcess from "node:child_process";
import { syncBuiltinESMExports } from "node:module";
import { promisify } from "node:util";
import { FULL_CATALOG_IDENTITY } from ${JSON.stringify(catalogUrl)};
`import { FULL_CATALOG_IDENTITY } from ${JSON.stringify(catalogUrl)};
let bridgeParsed = false;
childProcess.execFile = () => { throw new Error("Expected promisified execution"); };
childProcess.execFile[promisify.custom] = async (command, args) => {
const exec = async (command, args) => {
let stdout;
if ((command === "npm" && args[0] === "pack") || (command === process.execPath && args[1] === "pack")) stdout = "fixture.tgz\\n";
else if (command === "tar" && args[0] === "-tf") stdout = ${JSON.stringify(listing)};
@@ -52,10 +48,10 @@ childProcess.execFile[promisify.custom] = async (command, args) => {
else throw new Error("Unexpected command: " + command + " " + JSON.stringify(args));
return { stdout, stderr: "" };
};
syncBuiltinESMExports();
const commands = { exec, execNpm: (args) => exec("npm", args) };
const { verifyPackagePack } = await import(${JSON.stringify(packerUrl)});
try {
const result = await verifyPackagePack({ root: process.cwd(), workspace: ${JSON.stringify(workspace)} });
const result = await verifyPackagePack({ root: process.cwd(), workspace: ${JSON.stringify(workspace)}, commands });
process.stdout.write(JSON.stringify({ ...result, bridgeParsed }));
} catch (cause) {
process.stdout.write(JSON.stringify({ error: cause.message, bridgeParsed }));
@@ -72,7 +72,16 @@ it("retains upstream body failure and joins the active updater and installer chi
const verifierUrl = pathToFileURL(
resolve("scripts/verify-package-update.mjs"),
).href;
const commandsUrl = pathToFileURL(
resolve("scripts/lib/verify-package-commands.mjs"),
).href;
const cleanupUrl = pathToFileURL(
resolve("scripts/lib/verifier-cleanup.mjs"),
).href;
const harness = `
import { readFile, rm } from "node:fs/promises";
import { createPackageVerifierCommands } from ${JSON.stringify(commandsUrl)};
import { withVerifierCleanup } from ${JSON.stringify(cleanupUrl)};
globalThis.fetch = async () => ({
status: 200,
headers: new Headers({ "content-type": "application/json" }),
@@ -81,7 +90,12 @@ globalThis.fetch = async () => ({
},
});
const { verifyPackageUpdate } = await import(${JSON.stringify(verifierUrl)});
const scratch = { path: ${JSON.stringify(workspace)}, close: () => rm(${JSON.stringify(workspace)}, { recursive: true, force: true }) };
let commands;
let failure;
let pids;
await withVerifierCleanup(async () => {
commands = await createPackageVerifierCommands({ workspace: scratch });
try {
await verifyPackageUpdate({
prefix: ${JSON.stringify(prefix)},
@@ -89,12 +103,13 @@ try {
tarball: ${JSON.stringify(tarball)},
workspace: ${JSON.stringify(workspace)},
environment: { PATH: process.env.PATH },
commands,
});
} catch (cause) {
failure = cause instanceof Error ? cause.message : String(cause);
}
const { readFile } = await import("node:fs/promises");
const pids = JSON.parse(await readFile(${JSON.stringify(descendants)}, "utf8"));
pids = JSON.parse(await readFile(${JSON.stringify(descendants)}, "utf8"));
}, [() => commands === undefined ? scratch.close() : commands.close()]);
let livePids;
if (process.platform === "win32") {
livePids = pids.filter((pid) => {
@@ -0,0 +1,230 @@
import { readFile, writeFile } from "node:fs/promises";
import { join, resolve } from "node:path";
import { setTimeout as delay } from "node:timers/promises";
import { pathToFileURL } from "node:url";
import { expect } from "vitest";
import { z } from "zod";
import { liveProcesses } from "../../../src/process/ProcessOwnershipProcessTree.js";
import { systemProcessOwnershipHost } from "../../../src/process/ProcessOwnershipObservation.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
import { processTest } from "../../support/process/processFixture.js";
const descendantSource = `
import { appendFileSync, existsSync, writeFileSync } from "node:fs";
const record = (entry) => appendFileSync(process.env.OWNER_EVENTS, JSON.stringify(entry) + "\\n");
const finish = (reason) => {
record({ kind: "descendant-exit", reason, rootExists: existsSync(process.env.OWNER_ROOT) });
process.exit(0);
};
process.on("SIGTERM", () => finish("SIGTERM"));
writeFileSync(process.env.OWNER_DESCENDANT, String(process.pid));
process.send({ pid: process.pid });
setInterval(() => {
if (existsSync(process.env.OWNER_RELEASE)) finish("fixture-release");
}, 25);
setTimeout(() => finish("fixture-deadline"), 20000);
`;
const npmSource = `
import { spawn } from "node:child_process";
import { appendFileSync, existsSync, writeFileSync } from "node:fs";
process.once("SIGTERM", () => {
appendFileSync(process.env.OWNER_EVENTS, JSON.stringify({ kind: "leader-exit", rootExists: existsSync(process.env.OWNER_ROOT) }) + "\\n");
process.kill(process.pid, "SIGTERM");
});
const child = spawn(process.execPath, [process.env.OWNER_DESCENDANT_ENTRY], {
stdio: ["ignore", "ignore", "ignore", "ipc"],
});
const ready = await new Promise((resolve, reject) => {
child.once("message", resolve);
child.once("error", reject);
});
writeFileSync(process.env.OWNER_READY, JSON.stringify({ pids: [process.pid, ready.pid] }));
process.stdout.write("npm descendant ready\\n");
process.stderr.write("selected npm diagnostics\\n");
setInterval(() => {}, 1000);
`;
const reportSchema = z.object({
pids: z.array(z.number().int().positive()).length(2),
liveAtRemoval: z.array(z.number().int().positive()),
rootRemoved: z.boolean(),
failure: z.object({
reason: z.literal("timeout"),
stdout: z.string(),
stderr: z.string(),
}),
});
const stopReceiptSchema = z.object({
kind: z.enum(["leader-exit", "descendant-exit"]),
rootExists: z.boolean(),
reason: z.string().optional(),
});
const releaseDescendant = async (release: string, receipt: string) => {
await writeFile(release, "release this authored descendant");
const pid = await readFile(receipt, "utf8").then(Number, (cause: unknown) => {
if (cause instanceof Error && "code" in cause && cause.code === "ENOENT")
return undefined;
throw cause;
});
if (pid === undefined) return;
const deadline = Date.now() + 7_500;
while (
liveProcesses(await systemProcessOwnershipHost.listProcesses()).some(
(entry) => entry.pid === pid,
)
) {
if (Date.now() >= deadline)
throw new Error(
"Authored npm descendant did not consume its release marker",
);
await delay(25);
}
};
// Actual npm-entry/descendant processes and actual scratch removal; no package
// install, native engine, or WABT behavior is simulated or claimed.
processTest.skipIf(process.platform === "win32")(
"joins a timed-out npm entry and its acknowledged descendant before removing the verifier root",
{ timeout: 30_000 },
async ({ processes }) => {
const root = await createTestTempDirectory("rea-package-command-owner-");
const scratch = join(root, "scratch");
const entry = join(root, "selected npm entry.mjs");
const descendant = join(root, "descendant.mjs");
const release = join(root, "release-descendant");
const receipt = join(root, "descendant.pid");
const ready = join(root, "ready.json");
const events = join(root, "events.jsonl");
const driver = join(root, "driver.mjs");
await writeFile(entry, npmSource);
await writeFile(descendant, descendantSource);
const commandsModule = pathToFileURL(
resolve("scripts/lib/verify-package-commands.mjs"),
).href;
const cleanup = pathToFileURL(
resolve("scripts/lib/verifier-cleanup.mjs"),
).href;
const observations = pathToFileURL(
resolve("dist/process/ProcessOwnershipObservation.js"),
).href;
const tree = pathToFileURL(
resolve("dist/process/ProcessOwnershipProcessTree.js"),
).href;
await writeFile(
driver,
`
import { existsSync } from "node:fs";
import { mkdir, readFile, rm } from "node:fs/promises";
import { createPackageVerifierCommands } from ${JSON.stringify(commandsModule)};
import { withVerifierCleanup } from ${JSON.stringify(cleanup)};
import { systemProcessOwnershipHost } from ${JSON.stringify(observations)};
import { liveProcesses } from ${JSON.stringify(tree)};
const scratch = process.env.OWNER_ROOT;
await mkdir(scratch);
let commands, recoveryOwner, pids, liveAtRemoval, failure, diagnostics;
const workspace = {
path: scratch,
async close() {
const ready = await readFile(process.env.OWNER_READY, "utf8").then(JSON.parse, cause => {
if (cause.code === "ENOENT") return { pids: [] };
throw cause;
});
pids = ready.pids;
liveAtRemoval = liveProcesses(await systemProcessOwnershipHost.listProcesses()).filter(({ pid }) => pids.includes(pid)).map(({ pid }) => pid);
await rm(scratch, { recursive: true, force: true });
},
};
try {
await withVerifierCleanup(
async () => {
commands = await createPackageVerifierCommands({ workspace });
await commands.execNpm(["pack", "caller value with spaces"], { cwd: scratch, env: process.env, timeout: 1500 });
},
[async () => {
if (commands === undefined) await rm(scratch, { recursive: true, force: true });
else await commands.close();
}],
);
} catch (cause) {
const failures = cause instanceof AggregateError ? cause.errors : [cause];
// Capture exact cleanup authority before projecting facts or making assertions.
recoveryOwner = failures.find(error => typeof error?.cleanupOwner?.close === "function")?.cleanupOwner ?? commands;
const primary = failures.find(error => error?.snapshot !== undefined && typeof error?.reason === "string");
failure = { reason: primary?.reason, stdout: primary?.snapshot?.stdout.text, stderr: primary?.snapshot?.stderr.text };
diagnostics = failures.map(error => ({
name: error?.name ?? typeof error,
message: error?.message ?? String(error),
reason: error?.reason,
snapshotAvailable: error?.snapshot !== undefined && error?.snapshot !== null,
stack: error?.stack,
cleanupFailure: error?.cleanupFailure,
processError: error?.processError?.message,
preparationFailure: error?.preparationFailure?.message,
cause: {
name: error?.cause?.name,
message: error?.cause?.message ?? String(error?.cause),
stack: error?.cause?.stack,
cause: error?.cause?.cause?.message ?? String(error?.cause?.cause),
},
}));
} finally {
if (recoveryOwner !== undefined) await recoveryOwner.close();
else if (commands !== undefined) await commands.close();
else await rm(scratch, { recursive: true, force: true });
}
process.stdout.write(JSON.stringify({ pids, liveAtRemoval, rootRemoved: !existsSync(scratch), failure, diagnostics }));
`,
);
try {
const result = await processes.run(process.execPath, [driver], {
timeoutMs: 20_000,
env: {
...process.env,
npm_execpath: entry,
OWNER_ROOT: scratch,
OWNER_DESCENDANT_ENTRY: descendant,
OWNER_DESCENDANT: receipt,
OWNER_RELEASE: release,
OWNER_READY: ready,
OWNER_EVENTS: events,
},
});
expect(result.exitCode, result.stderr).toBe(0);
expect(result.signal, result.stderr).toBeNull();
const parsed = reportSchema.safeParse(JSON.parse(result.stdout));
expect(parsed.success, result.stdout).toBe(true);
if (!parsed.success) throw parsed.error;
const report = parsed.data;
expect(report.failure).toEqual({
reason: "timeout",
stdout: "npm descendant ready\n",
stderr: "selected npm diagnostics\n",
});
expect(report.liveAtRemoval).toEqual([]);
expect(report.rootRemoved).toBe(true);
const receipts = await readFile(events, "utf8").catch(
(cause: unknown) => {
if (
cause instanceof Error &&
"code" in cause &&
cause.code === "ENOENT"
)
return "";
throw cause;
},
);
const stopped = receipts
.trim()
.split("\n")
.filter((line) => line !== "")
.map((line) => stopReceiptSchema.parse(JSON.parse(line)));
for (const receipt of stopped) expect(receipt.rootExists).toBe(true);
} finally {
await releaseDescendant(release, receipt);
}
},
);
@@ -0,0 +1,459 @@
import { existsSync } from "node:fs";
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { setTimeout as delay } from "node:timers/promises";
import { expect, it } from "vitest";
import { z } from "zod";
import { createPackageVerifierCommands } from "../../../scripts/lib/verify-package-commands.mjs";
import { systemProcessOwnershipHost } from "../../../src/process/ProcessOwnershipObservation.js";
import { liveProcesses } from "../../../src/process/ProcessOwnershipProcessTree.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
const observed = <Value>(promise: Promise<Value>) =>
promise.then(
(value) => ({ ok: true as const, value }),
(cause: unknown) => ({ ok: false as const, cause }),
);
const commandFixture = async (
beforeWorkspaceClose?: (path: string) => void | Promise<void>,
) => {
const parent = await createTestTempDirectory(
"rea-package-command-semantics-",
);
const path = join(parent, "workspace");
await mkdir(path);
let closeCalls = 0;
const workspace = {
path,
async close() {
closeCalls += 1;
await beforeWorkspaceClose?.(path);
await rm(path, { recursive: true, force: true });
},
};
const commands = await createPackageVerifierCommands({ workspace });
return { parent, workspace, commands, closeCalls: () => closeCalls };
};
it("admits complete stdout and stderr independently of their combined size", async () => {
const fixture = await commandFixture();
let output;
try {
output = await fixture.commands.exec(
process.execPath,
[
"-e",
'process.stdout.write("o".repeat(48)); process.stderr.write("e".repeat(48));',
],
{ maxBuffer: 64 },
);
} finally {
await fixture.commands.close();
}
expect(output).toEqual({ stdout: "o".repeat(48), stderr: "e".repeat(48) });
expect(existsSync(fixture.workspace.path)).toBe(false);
expect(fixture.closeCalls()).toBe(1);
});
it("enforces an explicit aggregate diagnostic budget across both streams", async () => {
const fixture = await commandFixture();
let outcome;
try {
outcome = await observed(
fixture.commands.exec(
process.execPath,
[
"-e",
'process.stdout.write("o".repeat(48)); process.stderr.write("e".repeat(48));',
],
{ diagnosticBytes: 64 },
),
);
} finally {
await fixture.commands.close();
}
expect(outcome).toEqual({
ok: false,
cause: expect.objectContaining({ reason: "output-limit" }),
});
expect(existsSync(fixture.workspace.path)).toBe(false);
expect(fixture.closeCalls()).toBe(1);
});
it("rejects either overflowing stream even when the command exits with status one", async () => {
const fixture = await commandFixture();
const outcomes = [];
try {
for (const stream of ["stdout", "stderr"] as const)
outcomes.push(
await observed(
fixture.commands.runWithStatus(process.execPath, [
"-e",
`process.${stream}.write("x".repeat(1024 * 1024 + 1)); process.exitCode = 1;`,
]),
),
);
} finally {
await fixture.commands.close();
}
expect(outcomes).toEqual([
{ ok: false, cause: expect.objectContaining({ reason: "output-limit" }) },
{ ok: false, cause: expect.objectContaining({ reason: "output-limit" }) },
]);
expect(existsSync(fixture.workspace.path)).toBe(false);
});
it("returns exact stdout for clean exit one and rejects exit two", async () => {
const fixture = await commandFixture();
let first;
let second;
try {
first = await fixture.commands.runWithStatus(process.execPath, [
"-e",
'process.stdout.write("selected stdout\\n "); process.stderr.write("exit one diagnostics"); process.exitCode = 1;',
]);
second = await observed(
fixture.commands.runWithStatus(process.execPath, [
"-e",
'process.stdout.write("exit two stdout"); process.stderr.write("exit two diagnostics"); process.exitCode = 2;',
]),
);
} finally {
await fixture.commands.close();
}
expect(first).toEqual({ stdout: "selected stdout\n ", status: 1 });
expect(second).toEqual({
ok: false,
cause: expect.objectContaining({
name: "PackageCommandExitError",
code: 2,
stdout: "exit two stdout",
stderr: "exit two diagnostics",
}),
});
});
it.skipIf(process.platform === "win32")(
"rejects a signal exit instead of reporting status one",
async () => {
const fixture = await commandFixture();
let outcome;
try {
outcome = await observed(
fixture.commands.runWithStatus(process.execPath, [
"-e",
'process.kill(process.pid, "SIGTERM");',
]),
);
} finally {
await fixture.commands.close();
}
expect(outcome).toEqual({
ok: false,
cause: expect.objectContaining({
reason: "process",
snapshot: expect.objectContaining({ signal: "SIGTERM" }),
}),
});
},
);
it("uses the exact selected environment and npm entry without rewriting arguments", async () => {
const fixture = await commandFixture();
const entry = join(fixture.parent, "selected npm entry.mjs");
const sentinel = "REA_PACKAGE_COMMAND_AMBIENT_SENTINEL";
const previous = process.env[sentinel];
const source = `process.stdout.write(JSON.stringify({ args: process.argv.slice(2), cwd: process.cwd(), selected: process.env.REA_PACKAGE_COMMAND_SELECTED, ambient: process.env.${sentinel} ?? null }));`;
await writeFile(entry, source);
const arguments_ = [
"pack",
"value with spaces",
"--literal=$HOME",
"quote'and\"double",
];
const environment = {
...(process.platform === "win32"
? { SystemRoot: process.env.SystemRoot }
: {}),
REA_PACKAGE_COMMAND_SELECTED: "caller-selected",
npm_execpath: entry,
};
let direct;
let npm;
process.env[sentinel] = "ambient-only";
try {
direct = await fixture.commands.exec(process.execPath, ["-e", source], {
cwd: fixture.workspace.path,
env: environment,
});
npm = await fixture.commands.execNpm(arguments_, {
cwd: fixture.workspace.path,
env: environment,
});
} finally {
if (previous === undefined) delete process.env[sentinel];
else process.env[sentinel] = previous;
await fixture.commands.close();
}
expect(JSON.parse(direct!.stdout)).toEqual({
args: [],
cwd: fixture.workspace.path,
selected: "caller-selected",
ambient: null,
});
expect(JSON.parse(npm!.stdout)).toEqual({
args: arguments_,
cwd: fixture.workspace.path,
selected: "caller-selected",
ambient: null,
});
expect(direct?.stderr).toBe("");
expect(npm?.stderr).toBe("");
});
it("allows delayed commands with omitted and explicit zero timeouts", async () => {
const fixture = await commandFixture();
const outputs = [];
try {
for (const options of [{}, { timeout: 0 }])
outputs.push(
await fixture.commands.exec(
process.execPath,
["-e", 'setTimeout(() => process.stdout.write("delayed\\n"), 75);'],
options,
),
);
} finally {
await fixture.commands.close();
}
expect(outputs).toEqual([
{ stdout: "delayed\n", stderr: "" },
{ stdout: "delayed\n", stderr: "" },
]);
});
const waitForMarker = async (path: string): Promise<void> => {
const deadline = Date.now() + 5_000;
while (!existsSync(path)) {
if (Date.now() >= deadline)
throw new Error("Authored command did not become ready");
await delay(10);
}
};
it.skipIf(process.platform === "win32")(
"shares concurrent close and joins the active command before removing its workspace",
async () => {
let acknowledgedPid: number | undefined;
let liveAtWorkspaceClose: readonly number[] | undefined;
let rootExistsAtCensus = false;
const fixture = await commandFixture(async (path) => {
rootExistsAtCensus = existsSync(path);
liveAtWorkspaceClose = liveProcesses(
await systemProcessOwnershipHost.listProcesses(),
)
.filter(({ pid }) => pid === acknowledgedPid)
.map(({ pid }) => pid);
});
const ready = join(fixture.parent, "ready");
const source = `
require("node:fs").writeFileSync(${JSON.stringify(ready)}, JSON.stringify({ pid: process.pid }));
setInterval(() => {}, 1000);
setTimeout(() => process.exit(0), 10000);
`;
const running = observed(
fixture.commands.exec(process.execPath, ["-e", source]),
);
let sameClose = false;
let outcome;
try {
await waitForMarker(ready);
({ pid: acknowledgedPid } = z
.object({ pid: z.number().int().positive() })
.parse(JSON.parse(await readFile(ready, "utf8"))));
const first = fixture.commands.close();
const second = fixture.commands.close();
sameClose = first === second;
await Promise.all([first, second]);
outcome = await running;
} finally {
await fixture.commands.close();
await running;
}
expect(sameClose).toBe(true);
expect(outcome).toEqual({
ok: false,
cause: expect.objectContaining({ reason: "cancelled" }),
});
expect(acknowledgedPid).toBeGreaterThan(0);
expect(liveAtWorkspaceClose).toEqual([]);
expect(rootExistsAtCensus).toBe(true);
expect(existsSync(fixture.workspace.path)).toBe(false);
expect(fixture.closeCalls()).toBe(1);
},
);
it("retains the same adopted capability and workspace after failed close, then releases each once", async () => {
const events: string[] = [];
const fixture = await commandFixture(() => {
events.push("workspace");
});
const child = join(fixture.workspace.path, "adopted");
const forbidden = join(fixture.parent, "forbidden-command");
await mkdir(child);
const owners: object[] = [];
let attempts = 0;
const capability = {
async close() {
owners.push(this);
attempts += 1;
if (attempts === 1) {
events.push("adopted-failed");
throw new Error("retry this exact adopted directory capability");
}
await rm(child, { recursive: true, force: true });
events.push("adopted-released");
},
};
fixture.commands.adopt(capability, child);
fixture.commands.adopt(capability, child);
let failure: unknown;
let retained;
let blockedBefore;
let blockedAfter;
try {
const closed = await observed(fixture.commands.close());
if (!closed.ok) failure = closed.cause;
retained = {
root: existsSync(fixture.workspace.path),
child: existsSync(child),
rootCloseCalls: fixture.closeCalls(),
};
const arguments_ = [
"-e",
`require("node:fs").writeFileSync(${JSON.stringify(forbidden)}, "unexpected launch");`,
];
blockedBefore = await observed(
fixture.commands.exec(process.execPath, arguments_, {
cwd: fixture.parent,
}),
);
await fixture.commands.close();
blockedAfter = await observed(
fixture.commands.exec(process.execPath, arguments_, {
cwd: fixture.parent,
}),
);
} finally {
await fixture.commands.close();
}
expect(failure).toMatchObject({
name: "PackageVerifierCleanupFailure",
path: fixture.workspace.path,
});
if (
!(failure instanceof Error) ||
!("cleanupOwner" in failure) ||
!("workspace" in failure)
)
throw new Error("Expected retained package cleanup authority");
expect(failure.cleanupOwner).toBe(fixture.commands);
expect(failure.workspace).toBe(fixture.workspace);
expect(retained).toEqual({ root: true, child: true, rootCloseCalls: 0 });
expect(owners).toHaveLength(2);
expect(owners[0]).toBe(capability);
expect(owners[1]).toBe(capability);
expect(events).toEqual(["adopted-failed", "adopted-released", "workspace"]);
expect(fixture.closeCalls()).toBe(1);
expect(existsSync(fixture.workspace.path)).toBe(false);
expect(existsSync(forbidden)).toBe(false);
for (const outcome of [blockedBefore, blockedAfter])
expect(outcome).toEqual({
ok: false,
cause: expect.objectContaining({
message: "Package command owner is closed",
}),
});
});
it("rejects a new cleanup owner once workspace release has started", async () => {
let markReleaseStarted!: () => void;
const releaseStarted = new Promise<void>((resolve) => {
markReleaseStarted = resolve;
});
let unblockWorkspaceClose!: () => void;
const allowRelease = new Promise<void>((resolve) => {
unblockWorkspaceClose = resolve;
});
const events: string[] = [];
const fixture = await commandFixture(async () => {
events.push("workspace-release-started");
markReleaseStarted();
await allowRelease;
events.push("workspace-close-unblocked");
});
// This resource belongs to the caller until adoption succeeds. Keep it
// outside the verifier workspace so workspace removal cannot conceal a leak.
const path = join(fixture.parent, "caller-owned-directory");
await mkdir(path);
const releases: object[] = [];
const capability = {
async close() {
releases.push(this);
await rm(path, { recursive: true, force: true });
events.push("caller-owner-released");
},
};
const closing = fixture.commands.close();
let rejection: unknown;
let retained;
try {
await releaseStarted;
try {
fixture.commands.adopt(capability, path);
} catch (cause: unknown) {
rejection = cause;
}
retained = {
workspace: existsSync(fixture.workspace.path),
callerResource: existsSync(path),
callerCloseCalls: releases.length,
};
} finally {
try {
await capability.close();
} finally {
unblockWorkspaceClose();
await closing;
await fixture.commands.close();
}
}
expect(rejection).toBeInstanceOf(Error);
expect(retained).toEqual({
workspace: true,
callerResource: true,
callerCloseCalls: 0,
});
expect(releases).toHaveLength(1);
expect(releases[0]).toBe(capability);
expect(events).toEqual([
"workspace-release-started",
"caller-owner-released",
"workspace-close-unblocked",
]);
expect(existsSync(path)).toBe(false);
expect(existsSync(fixture.workspace.path)).toBe(false);
expect(fixture.closeCalls()).toBe(1);
});
@@ -20,9 +20,12 @@ const builtModule = (path: string): string =>
pathToFileURL(resolve("dist", path)).href;
const PACKAGE_PACK_FIXTURE = `
import assert from "node:assert/strict";
import { appendFileSync, writeFileSync } from "node:fs";
import { join } from "node:path";
export async function verifyPackagePack({ root, workspace }) {
import { packageCommands } from "./lib/verify-package-commands.mjs";
export async function verifyPackagePack({ root, workspace, commands }) {
assert.equal(commands, packageCommands);
appendFileSync(process.env.OWNERSHIP_EVENTS, JSON.stringify({ kind: "pack", root, workspace }) + "\\n");
const tarball = join(workspace, "authored.tgz");
writeFileSync(tarball, "authored archive boundary");
@@ -30,15 +33,13 @@ export async function verifyPackagePack({ root, workspace }) {
}
`;
const PACKAGE_INSTALL_FIXTURE = `
const NPM_INSTALL_FIXTURE = `
import assert from "node:assert/strict";
import { appendFileSync } from "node:fs";
export async function execNpm(args, { env }) {
assert.equal(args[0], "install");
assert.ok(args.includes("--ignore-scripts"));
appendFileSync(process.env.OWNERSHIP_EVENTS, JSON.stringify({ kind: "install", prefix: args[args.indexOf("--prefix") + 1], home: env.HOME }) + "\\n");
return { stdout: "", stderr: "" };
}
const args = process.argv.slice(2);
assert.equal(args[0], "install");
assert.ok(args.includes("--ignore-scripts"));
appendFileSync(process.env.OWNERSHIP_EVENTS, JSON.stringify({ kind: "install", prefix: args[args.indexOf("--prefix") + 1], home: process.env.HOME }) + "\\n");
`;
const PRIVATE_ROOT_OBSERVER = `
@@ -65,15 +66,20 @@ export const PrivateRuntimeRoot = {
};
`;
const OWNED_COMMAND_FIXTURE = `
const PACKAGE_COMMANDS_FIXTURE = `
import { appendFileSync, existsSync } from "node:fs";
import { runOwnedCommand as actualRun, OwnedCommandFailure } from ${JSON.stringify(builtModule("process/OwnedCommand.js"))};
import { fileURLToPath } from "node:url";
import { createPackageVerifierCommands as createActualCommands } from ${JSON.stringify(pathToFileURL(resolve("scripts/lib/verify-package-commands.mjs")).href)};
import { spawnOwnedProviderProcess } from ${JSON.stringify(builtModule("process/ProviderProcess.js"))};
import { cleanupOwnedProcessGroup } from ${JSON.stringify(builtModule("process/ProcessOwnership.js"))};
export { OwnedCommandFailure };
export const runOwnedCommand = (spawn, limits) => actualRun(spawn, limits, {
const nestedVerifier = fileURLToPath(new URL("../verify-wasm-artifact.mjs", import.meta.url));
export let packageCommands;
export async function createPackageVerifierCommands(options) {
packageCommands = await createActualCommands({
...options,
launcher: async (input) => {
const acquired = await spawnOwnedProviderProcess(input);
if (!input.arguments.includes(nestedVerifier)) return acquired;
return {
...acquired,
cleanup: async () => {
@@ -85,6 +91,8 @@ export const runOwnedCommand = (spawn, limits) => actualRun(spawn, limits, {
};
},
});
return packageCommands;
}
`;
const NESTED_VERIFIER_FIXTURE = `
@@ -121,29 +129,32 @@ const HARNESS_FIXTURE = `
import assert from "node:assert/strict";
import { existsSync } from "node:fs";
import { readFile, writeFile } from "node:fs/promises";
import { OwnedCommandFailure, prepareOwnedCommand } from ${JSON.stringify(builtModule("process/OwnedCommand.js"))};
import { prepareOwnedCommand } from ${JSON.stringify(builtModule("process/OwnedCommand.js"))};
import { systemProcessOwnershipHost } from ${JSON.stringify(builtModule("process/ProcessOwnershipObservation.js"))};
import { liveProcesses } from ${JSON.stringify(builtModule("process/ProcessOwnershipProcessTree.js"))};
import { withVerifierCleanup } from "./scripts/lib/verifier-cleanup.mjs";
import { packageCommands } from "./scripts/lib/verify-package-commands.mjs";
await prepareOwnedCommand();
let failure;
try { await import("./scripts/verify-packaged-wasm-artifact.mjs"); }
catch (cause) { failure = cause; }
// Capture actual capabilities before any fixture parsing or assertion can fail.
const failures = failure instanceof AggregateError ? failure.errors : [failure];
const primary = failures.find((error) => error instanceof OwnedCommandFailure);
const primary = failures.find((error) => error !== null && typeof error === "object" &&
error.reason === "timeout" && error.snapshot !== null && typeof error.snapshot === "object");
const cleanup = failures.find((error) => error !== null && typeof error === "object" &&
(error.runtimeRoot !== undefined || typeof error.cleanupOwner?.close === "function"));
typeof error.cleanupOwner?.close === "function");
const compositeOwner = typeof cleanup?.cleanupOwner?.close === "function" ? cleanup.cleanupOwner : undefined;
const retainedProcess = primary?.cleanupOwner;
const retainedRoot = cleanup?.runtimeRoot;
const retainedRoot = cleanup?.workspace;
await withVerifierCleanup(async () => {
if (process.env.OWNERSHIP_HARNESS_ASSERT === "1") throw new Error("authored harness assertion failure");
const events = (await readFile(process.env.OWNERSHIP_EVENTS, "utf8")).trim().split("\\n").map(JSON.parse);
const ready = events.find((entry) => entry.kind === "ready");
assert.ok(ready, "The actual descendant must acknowledge startup before the command deadline");
assert.ok(primary instanceof OwnedCommandFailure);
assert.ok(primary, "The owned nested command must fail with its timeout snapshot");
assert.equal(primary.reason, "timeout");
assert.ok(primary.snapshot.stdout.text.includes("nested ready"));
const pids = [ready.leader, ready.descendant];
const remaining = async () => liveProcesses(await systemProcessOwnershipHost.listProcesses()).filter(({ pid }) => pids.includes(pid)).map(({ pid }) => pid);
const result = { reason: primary.reason, runtime: ready.runtime, rootExists: existsSync(ready.runtime), livePids: await remaining(), pids, runId: ready.runId, descendantRunId: ready.descendantRunId };
@@ -151,9 +162,10 @@ if (process.env.OWNERSHIP_DENY === "1") {
assert.ok(failure instanceof AggregateError);
assert.equal(failure.cause, primary);
assert.equal(failure.errors[1], cleanup);
assert.equal(cleanup.name, "PackagedWasmCleanupFailure");
assert.equal(cleanup.runtimeRoot.path, ready.runtime);
assert.equal(cleanup.name, "PackageVerifierCleanupFailure");
assert.equal(cleanup.workspace.path, ready.runtime);
assert.equal(typeof cleanup.cleanupOwner.close, "function");
assert.equal(cleanup.cleanupOwner, packageCommands);
assert.ok(primary.cleanupOwner);
result.compositeOwner = true;
}
@@ -207,7 +219,7 @@ const expectHarnessFailureCleanup = async (
.map((line) => JSON.parse(line));
const ready = events.find(({ kind }) => kind === "ready");
expect(ready).toBeDefined();
expect(ready.runId).toMatch(/^rea-packaged-wabt-/);
expect(ready.runId).toMatch(/^rea-package-command-/);
expect(ready.descendantRunId).toBe(ready.runId);
expectRootCleanupAfterProcessExit(events, ready.runtime, [
ready.leader,
@@ -251,10 +263,10 @@ it.skipIf(process.platform === "win32").each([
),
workspace.write("scripts/verify-package-pack.mjs", PACKAGE_PACK_FIXTURE),
workspace.write(
"scripts/lib/verify-package-core.mjs",
PACKAGE_INSTALL_FIXTURE,
"scripts/lib/verify-package-commands.mjs",
PACKAGE_COMMANDS_FIXTURE,
),
workspace.write("dist/process/OwnedCommand.js", OWNED_COMMAND_FIXTURE),
workspace.write("scripts/npm-fixture.mjs", NPM_INSTALL_FIXTURE),
workspace.write(
"scripts/verify-wasm-artifact.mjs",
NESTED_VERIFIER_FIXTURE,
@@ -279,6 +291,7 @@ it.skipIf(process.platform === "win32").each([
TMPDIR: runtimeParent,
TMP: runtimeParent,
TEMP: runtimeParent,
npm_execpath: workspace.path("scripts/npm-fixture.mjs"),
OWNERSHIP_EVENTS: events,
OWNERSHIP_REPORT: report,
OWNERSHIP_ALLOW: allow,
@@ -303,7 +316,7 @@ it.skipIf(process.platform === "win32").each([
const observed = JSON.parse(await readFile(report, "utf8"));
expect(observed.reason).toBe("timeout");
expect(observed.pids).toHaveLength(2);
expect(observed.runId).toMatch(/^rea-packaged-wabt-/);
expect(observed.runId).toMatch(/^rea-package-command-/);
expect(observed.descendantRunId).toBe(observed.runId);
expect(observed.rootExists).toBe(denied);
expect(observed.livePids.sort()).toEqual(
@@ -9,16 +9,24 @@ import {
} from "node:fs/promises";
import { dirname, join } from "node:path";
import { promisify } from "node:util";
import { expect, it } from "vitest";
import { expect, it, onTestFinished } from "vitest";
import { z } from "zod";
import {
createPackageVerifierCommands,
type PackageVerifierCommands,
} from "../../../scripts/lib/verify-package-commands.mjs";
import { withVerifierCleanup } from "../../../scripts/lib/verifier-cleanup.mjs";
import { verifyPackedBridge } from "../../../scripts/verify-packed-bridge.mjs";
import {
PrivateRuntimeRoot,
privateRuntimeRootCapability,
} from "../../../src/process/PrivateRuntimeRoot.js";
import { GHIDRA_BRIDGE_SOURCES } from "../../../src/ghidra/GhidraBridgeSources.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
import {
createTestWorkspace,
removeTestWorkspace,
} from "../../support/workspace/workspaceFixture.js";
const exec = promisify(execFile);
const packedFiles = [
@@ -33,7 +41,15 @@ const validSource =
'# Unicode source: 雨\nraise RuntimeError("Packed source must never execute")\n';
async function fixture(source: string = validSource, omitHopper = false) {
const directory = await createTestTempDirectory("rea-packed-bridge-");
const scratch = await createTestWorkspace("rea-packed-bridge-");
const directory = scratch.root;
let commands: PackageVerifierCommands | undefined;
onTestFinished(() =>
commands === undefined ? removeTestWorkspace(directory) : commands.close(),
);
commands = await createPackageVerifierCommands({
workspace: { path: directory, close: () => removeTestWorkspace(directory) },
});
const staging = join(directory, "source");
const workspace = join(directory, "workspace");
await mkdir(workspace);
@@ -45,7 +61,7 @@ async function fixture(source: string = validSource, omitHopper = false) {
}
const tarball = join(directory, "packed bridges.tgz");
await exec("tar", ["-czf", tarball, "-C", staging, "package"]);
return { workspace, tarball, packedFiles };
return { workspace, tarball, packedFiles, commands };
}
it("parses packed Python without executing it or extracting archive members", async () => {
@@ -88,12 +104,20 @@ it.each(["cwd", "pythonpath", "sitecustomize"] as const)(
"--input-type=module",
"--eval",
`import { verifyPackedBridge } from ${JSON.stringify(verifier)};
import { createPackageVerifierCommands } from ${JSON.stringify(new URL("../../../scripts/lib/verify-package-commands.mjs", import.meta.url).href)};
import { withVerifierCleanup } from ${JSON.stringify(new URL("../../../scripts/lib/verifier-cleanup.mjs", import.meta.url).href)};
import { PrivateRuntimeRoot } from ${JSON.stringify(new URL("../../../dist/process/PrivateRuntimeRoot.js", import.meta.url).href)};
const root = await PrivateRuntimeRoot.create({ prefix: "rea-packed-python-command-" });
let commands;
await withVerifierCleanup(async () => {
commands = await createPackageVerifierCommands({ workspace: root });
try {
await verifyPackedBridge(JSON.parse(process.env.REA_PACKED_VERIFY_INPUT));
await verifyPackedBridge({ ...JSON.parse(process.env.REA_PACKED_VERIFY_INPUT), commands });
console.log(JSON.stringify({ accepted: true }));
} catch (error) {
console.log(JSON.stringify({ accepted: false, stderr: String(error.stderr) }));
}`,
}
}, [() => commands === undefined ? root.close() : commands.close()]);`,
],
{
cwd: kind === "cwd" ? modules : input.workspace,
@@ -105,7 +129,10 @@ try {
),
PYTHONPATH: kind === "cwd" ? "" : modules,
REA_PACKED_IMPORT_MARKER: marker,
REA_PACKED_VERIFY_INPUT: JSON.stringify(input),
REA_PACKED_VERIFY_INPUT: JSON.stringify({
tarball: input.tarball,
packedFiles: input.packedFiles,
}),
},
},
);
@@ -160,12 +187,14 @@ it.each(["valid", "invalid"] as const)(
const root = await PrivateRuntimeRoot.create({
prefix: "rea-packed-bridge-lease-",
});
try {
let commands: PackageVerifierCommands | undefined;
await withVerifierCleanup(async () => {
commands = await createPackageVerifierCommands({ workspace: root });
const pack = join(root.path, "pack");
await mkdir(pack);
const tarball = join(pack, "packed bridges.tgz");
await copyFile(input.tarball, tarball);
const parsed = verifyPackedBridge({ tarball, packedFiles });
const parsed = verifyPackedBridge({ tarball, packedFiles, commands });
if (kind === "valid") await parsed;
else
await expect(parsed).rejects.toMatchObject({
@@ -173,9 +202,7 @@ it.each(["valid", "invalid"] as const)(
});
expect(await readdir(root.path)).toEqual(["pack"]);
expect(await readFile(tarball)).toEqual(await readFile(input.tarball));
} finally {
await root.close();
}
}, [() => (commands === undefined ? root.close() : commands.close())]);
await expect(access(root.path)).rejects.toMatchObject({ code: "ENOENT" });
},
);
+1 -1
View File
@@ -578,7 +578,7 @@ it("forwards selected and omitted deadlines through the production command runne
const directory = await mkdtemp(join(tmpdir(), "rea-owned-timeout-"));
const path = join(directory, "fixture.bin");
await writeFile(path, "before");
const timeouts: number[] = [];
const timeouts: (number | null)[] = [];
const service = new ReverseEngineeringService({
runOwned: async (_spawn, limits) => {
timeouts.push(limits.timeoutMs);
@@ -54,8 +54,13 @@ const verifyMacOS = async (
cli: "fixture-cli",
environment: {},
platform: "darwin",
runCommand: async (_cli, arguments_) =>
JSON.stringify(outputs[arguments_[0]!]) ?? "",
commands: {
run: async (_cli, arguments_) =>
JSON.stringify(outputs[arguments_[0]!]) ?? "",
runWithStatus: async () => {
throw new Error("Unexpected status command in macOS fixture");
},
},
});
};