fix: terminate guarded providers on fatal module import

This commit is contained in:
Dotta committed 2026-10-11 04:09:40 -05:00
1 parent 72042f165c
commit 1ab3cb94e0
4 files changed
+106 -1

No files matched your search

@@ -126,6 +126,28 @@ describe("ACPX sidecar input sequencing", () => {
expect(acpxSidecarErrorCode(new Error("wrapped", { cause: error }))).toBe(code);
});
it.each(["OTHER", "UNVERIFIED_MODULE"])("classifies closed import marker %s without private stderr", category => {
const classify = (stderrSummary: string) => acpxSidecarErrorCode(Object.assign(new Error("private-canary"), {
detailCode: "AGENT_STARTUP_FAILED", stderrSummary, exitCode: 1,
}));
expect(classify(`PAPERCLIP_ACPX_IMPORT_FAILURE:${category}\nprivate-canary /secret`))
.toBe(`AGENT_STARTUP_FAILED.${category}`);
expect(classify(`private-canary PAPERCLIP_ACPX_IMPORT_FAILURE:OTHER\n`))
.toBe("AGENT_STARTUP_FAILED.EXIT_NONZERO");
expect(classify(`PAPERCLIP_ACPX_IMPORT_FAILURE:OTHER private-canary\n`))
.toBe("AGENT_STARTUP_FAILED.EXIT_NONZERO");
});
it.each([
["Error [ERR_MODULE_NOT_FOUND]: private-canary", "MODULE_NOT_FOUND"],
["SyntaxError: private-canary", "SYNTAX_ERROR"],
])("preserves detailed closed startup category with a generic import marker (%s)", (detail, category) => {
expect(acpxSidecarErrorCode(Object.assign(new Error("private-canary"), {
detailCode: "AGENT_STARTUP_FAILED",
stderrSummary: `PAPERCLIP_ACPX_IMPORT_FAILURE:OTHER\n${detail}`,
}))).toBe(`AGENT_STARTUP_FAILED.${category}`);
});
it("preserves stable ACPX error identities without copying startup stderr", () => {
const missingModule = Object.assign(new Error("provider exited"), {
detailCode: "AGENT_STARTUP_FAILED",
@@ -88,6 +88,10 @@ function directAcpxSidecarErrorCode(error: Error): string | null {
const stderr =
typeof details.stderrSummary === "string" ? details.stderrSummary : "";
// Bootstrap writes this fixed marker before printing private error details and
// terminating. Never copy those details into the public classification.
const importFailure = /(?:^|\n)PAPERCLIP_ACPX_IMPORT_FAILURE:(UNVERIFIED_MODULE|OTHER)(?:\r?\n|$)/.exec(stderr);
if (importFailure?.[1] === "UNVERIFIED_MODULE") return "AGENT_STARTUP_FAILED.UNVERIFIED_MODULE";
if (/ERR_ACPX_UNVERIFIED_MODULE/.test(stderr)) {
return "AGENT_STARTUP_FAILED.UNVERIFIED_MODULE";
}
@@ -106,6 +110,7 @@ function directAcpxSidecarErrorCode(error: Error): string | null {
if (/ERR_INVALID_ARG|invalid argument/i.test(stderr)) {
return "AGENT_STARTUP_FAILED.INVALID_ARGUMENT";
}
if (importFailure) return "AGENT_STARTUP_FAILED.OTHER";
if (!stderr.trim()) return "AGENT_STARTUP_FAILED.NO_STDERR";
if (typeof details.signal === "string" && details.signal) {
return "AGENT_STARTUP_FAILED.SIGNAL";
@@ -3,6 +3,8 @@ import { constants } from "node:fs";
import { runInNewContext } from "node:vm";
import { fork, type ChildProcess } from "node:child_process";
import { once } from "node:events";
import { pathToFileURL } from "node:url";
import { acpxSidecarErrorCode } from "../../cli/acpx-sidecar-input.js";
import {
chmod,
link,
@@ -1707,6 +1709,73 @@ describe("ACPX installation integrity", () => {
await expectFailure(child, "requires Linux descriptor-pinned paths");
}
});
it.runIf(process.platform !== "win32").each(["OTHER", "UNVERIFIED_MODULE"] as const)(
"settles a guarded ESM import failure without guardian retirement (%s)",
async (category) => {
const fixture = await installationFixture();
const outside = join(fixture.root, "private-canary.mjs");
await writeFile(outside, 'throw new Error("must-not-execute");');
const command = category === "OTHER"
? 'throw new Error("guarded-import-fixture-failed private-canary");'
: `import ${JSON.stringify(pathToFileURL(outside).href)};`;
await writeFile(fixture.commandPath, command);
await writeFile(fixture.serverPackageJsonPath, JSON.stringify({
version: "0.0.33", bin: "bin/server.js", type: "module",
}));
const installation = await verifyQualifiedAcpxInstallation({
...fixture.profile,
commandDigest: `sha256:${createHash("sha256").update(command).digest("hex")}`,
}, fixture.resolve);
const credentialHome = join(fixture.root, "credential-home");
await mkdir(credentialHome, { mode: 0o700 });
const credential = await stageManagedCodexCredential({
agentHomeDirectory: credentialHome,
environment: { PAPERCLIP_ACPX_CODEX_AUTH_JSON_SECRET: '{"fixture":true}' },
});
const lease = await installation.openCommand();
const child = lease.spawn([], {}, {
credentialFenceFds: credential.lifetimeFenceFds,
activateCredentialFenceOwner: pid => credential.activateLifetimeOwner(pid),
});
const exit = once(child, "exit");
const closed = once(child, "close");
let stderr = "";
const failureSeen = new Promise<void>(resolve => child.stderr?.on("data", bytes => {
stderr += String(bytes);
if (stderr.includes("PAPERCLIP_ACPX_IMPORT_FAILURE:")) resolve();
}));
let timer: ReturnType<typeof setTimeout> | undefined;
let startupTimer: ReturnType<typeof setTimeout> | undefined;
try {
await Promise.race([failureSeen, new Promise<never>((_, reject) => {
startupTimer = setTimeout(() => reject(new Error("fixture startup marker missing")), 3_000);
})]);
// Bound starts after the exact child error, excluding startup speed.
const settled = await Promise.race([exit.then(() => true), new Promise<boolean>(resolve => {
timer = setTimeout(() => resolve(false), 500);
})]);
expect(settled).toBe(true);
await closed;
await awaitVerifiedAcpxProviderExit(child);
expect(child.exitCode === 0 && child.signalCode === null).toBe(false);
const code = acpxSidecarErrorCode(Object.assign(new Error("private-canary"), {
detailCode: "AGENT_STARTUP_FAILED", stderrSummary: stderr,
exitCode: child.exitCode, signal: child.signalCode,
}));
expect(code).toBe(`AGENT_STARTUP_FAILED.${category}`);
expect(code).not.toMatch(/canary|\//);
expect(stderr).not.toContain("must-not-execute");
} finally {
if (timer) clearTimeout(timer);
if (startupTimer) clearTimeout(startupTimer);
if (child.exitCode === null && child.signalCode === null) child.kill("SIGTERM");
await exit;
await lease.close();
await credential.close();
}
},
);
it.runIf(process.platform !== "win32")(
"rejects incomplete or duplicate provider credential quorum descriptors",
async () => {
@@ -1990,7 +1990,16 @@ function snapshotBootstrap(format: AcpxCommandFormat, guarded = false): string {
"} finally { fs.closeSync(moduleFd); }",
"});",
"} });",
"import(target).catch((error) => { console.error(error); process.exitCode = 1; });",
// A guardian stream intentionally keeps the provider alive. A failed module
// import must use the existing owned-group reaper or ACP initialize waits
// until its deadline. process.exit can itself wait on the guardian pipe read.
// Capture these before provider code runs; emit a closed marker synchronously
// so the existing startup-error classifier survives truncated private stderr.
"const writeImportFailure = fs.writeSync.bind(fs);",
guarded
? "const exitOnImportFailure = reapOnGuardianLoss;"
: "const exitOnImportFailure = process.exit.bind(process);",
'import(target).catch((error) => { let category = "OTHER"; try { if (error?.code === "ERR_ACPX_UNVERIFIED_MODULE") category = "UNVERIFIED_MODULE"; } catch {} try { writeImportFailure(2, "PAPERCLIP_ACPX_IMPORT_FAILURE:" + category + "\\n"); console.error(error); } finally { exitOnImportFailure(1); } });',
].join("");
}