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`,
);