mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-16 05:17:41 +02:00
fix: retain matching-host runner development overrides
Use the controller override only for matching remote OS and CPU, preserving explicit remote priority and strict target header verification. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
e79254b2fa
commit
bb7591dfd0
3 files changed
+75
-3
No files matched your search
@@ -12,7 +12,7 @@ import {
|
||||
resolveRemotePaperclipRunnerBinary,
|
||||
} from "./native-codex-runner.js";
|
||||
|
||||
afterEach(() => { vi.restoreAllMocks(); vi.unstubAllGlobals(); });
|
||||
afterEach(() => { vi.restoreAllMocks(); vi.unstubAllGlobals(); vi.unstubAllEnvs(); });
|
||||
|
||||
describe("installed runner selection", () => {
|
||||
it("uses the runner's platform-aware packaged selection", async () => {
|
||||
@@ -79,6 +79,9 @@ describe("selected remote daemon target", () => {
|
||||
try {
|
||||
vi.stubGlobal("process", { ...process, platform: "darwin", arch: controllerArch });
|
||||
await writeFile(binary, header(`linux-${targetArch}`));
|
||||
const controllerBinary = join(root, "controller-runnerd");
|
||||
await writeFile(controllerBinary, header(`darwin-${controllerArch}`), { mode: 0o700 });
|
||||
vi.stubEnv("PAPERCLIP_RUNNER_BINARY", controllerBinary);
|
||||
const select = vi.spyOn(runner, "defaultCapabilityRunnerdBinary").mockReturnValue(binary);
|
||||
const target = remote(`Linux\n${targetArch === "arm64" ? "aarch64" : "x86_64"}\n`);
|
||||
expect(await resolveRemotePaperclipRunnerBinary({ runner: target, remoteCwd: "/sandbox-workspace" })).toBe(binary);
|
||||
@@ -87,10 +90,54 @@ describe("selected remote daemon target", () => {
|
||||
} finally { await rm(root, { recursive: true, force: true }); }
|
||||
},
|
||||
);
|
||||
it.each(["linux", "darwin"].flatMap(platform => ["arm64", "x64"].map(architecture => ({ platform, architecture }))))(
|
||||
"prefers the supervisor's fresh $platform/$architecture override for the same-host target", async ({ platform, architecture }) => {
|
||||
const root = await mkdtemp(join(tmpdir(), "paperclip-same-host-daemon-"));
|
||||
try {
|
||||
vi.stubGlobal("process", { ...process, platform, arch: architecture });
|
||||
const fresh = join(root, "fresh-runnerd"), stale = join(root, "stale-packaged-runnerd");
|
||||
await writeFile(fresh, Buffer.concat([header(`${platform}-${architecture}`), Buffer.from("fresh")]), { mode: 0o700 });
|
||||
await writeFile(stale, Buffer.concat([header(`${platform}-${architecture}`), Buffer.from("stale")]), { mode: 0o700 });
|
||||
vi.stubEnv("PAPERCLIP_RUNNER_BINARY", fresh);
|
||||
const select = vi.spyOn(runner, "defaultCapabilityRunnerdBinary").mockReturnValue(stale);
|
||||
const target = remote(`${platform === "linux" ? "Linux" : "Darwin"}\n${architecture === "arm64" ? "arm64" : "x86_64"}\n`);
|
||||
expect(await resolveRemotePaperclipRunnerBinary({ runner: target, remoteCwd: "/workspace" })).toBe(fresh);
|
||||
expect(select).not.toHaveBeenCalled();
|
||||
} finally { await rm(root, { recursive: true, force: true }); }
|
||||
},
|
||||
);
|
||||
it.each(["missing", "relative", "malformed", "wrong-architecture"])("retains same-host override policy and target validation (%s)", async scenario => {
|
||||
const root = await mkdtemp(join(tmpdir(), "paperclip-same-host-daemon-"));
|
||||
try {
|
||||
vi.stubGlobal("process", { ...process, platform: "linux", arch: "x64" });
|
||||
const override = join(root, "configured-runnerd"), fallback = join(root, "packaged-runnerd");
|
||||
if (scenario !== "missing") await writeFile(override, scenario === "malformed" ? Buffer.alloc(64) : header("linux-arm64"), { mode: 0o700 });
|
||||
await writeFile(fallback, header("linux-x64"), { mode: 0o700 });
|
||||
vi.stubEnv("PAPERCLIP_RUNNER_BINARY", scenario === "relative" ? "relative-runnerd" : override);
|
||||
vi.spyOn(runner, "defaultCapabilityRunnerdBinary").mockReturnValue(fallback);
|
||||
const request = resolveRemotePaperclipRunnerBinary({ runner: remote("Linux\nx86_64\n"), remoteCwd: "/workspace" });
|
||||
if (scenario === "missing") expect(await request).toBe(fallback);
|
||||
else await expect(request).rejects.toThrow(scenario === "relative" ? "PAPERCLIP_RUNNER_BINARY must be an absolute path" : "runner_remote_artifact_platform_mismatch");
|
||||
} finally { await rm(root, { recursive: true, force: true }); }
|
||||
});
|
||||
it("ignores a relative controller override when the actual target differs", async () => {
|
||||
const root = await mkdtemp(join(tmpdir(), "paperclip-cross-host-daemon-"));
|
||||
try {
|
||||
vi.stubGlobal("process", { ...process, platform: "darwin", arch: "arm64" });
|
||||
vi.stubEnv("PAPERCLIP_RUNNER_BINARY", "relative-controller-runnerd");
|
||||
const binary = join(root, "linux-runnerd");
|
||||
await writeFile(binary, header("linux-arm64"));
|
||||
const select = vi.spyOn(runner, "defaultCapabilityRunnerdBinary").mockReturnValue(binary);
|
||||
expect(await resolveRemotePaperclipRunnerBinary({ runner: remote("Linux\naarch64\n"), remoteCwd: "/workspace" })).toBe(binary);
|
||||
expect(select).toHaveBeenCalledExactlyOnceWith("linux", "arm64");
|
||||
} finally { await rm(root, { recursive: true, force: true }); }
|
||||
});
|
||||
it.each(["matching", "wrong-architecture", "malformed", "missing"])("verifies an explicit controller-owned remote artifact (%s)", async scenario => {
|
||||
const root = await mkdtemp(join(tmpdir(), "paperclip-ssh-daemon-"));
|
||||
const binary = join(root, "configured-runnerd");
|
||||
try {
|
||||
vi.stubGlobal("process", { ...process, platform: "linux", arch: "arm64" });
|
||||
vi.stubEnv("PAPERCLIP_RUNNER_BINARY", "invalid-relative-controller-override");
|
||||
if (scenario !== "missing") await writeFile(binary, scenario === "malformed" ? Buffer.alloc(64) : header(scenario === "matching" ? "linux-arm64" : "linux-x64"));
|
||||
const select = vi.spyOn(runner, "defaultCapabilityRunnerdBinary");
|
||||
const request = resolveRemotePaperclipRunnerBinary({ runner: remote("Linux\naarch64\n"), remoteCwd: "/workspace", configuredPath: binary });
|
||||
|
||||
@@ -224,7 +224,13 @@ export async function resolveRemotePaperclipRunnerBinary(input: {
|
||||
}
|
||||
const configured = input.configuredPath?.trim();
|
||||
if (configured && !isAbsolute(configured)) throw new Error("PAPERCLIP_RUNNER_REMOTE_BINARY_PATH must be an absolute path");
|
||||
const binary = configured || defaultCapabilityRunnerdBinary(platform, architecture);
|
||||
// A same-host sandbox can use the supervisor's fresh development artifact.
|
||||
// Cross-platform targets must never inherit the controller's override.
|
||||
const controllerOverride = platform === process.platform && architecture === process.arch
|
||||
? process.env.PAPERCLIP_RUNNER_BINARY : undefined;
|
||||
const binary = configured || (controllerOverride
|
||||
? resolvePaperclipRunnerBinary(controllerOverride)
|
||||
: defaultCapabilityRunnerdBinary(platform, architecture));
|
||||
let bytes: Buffer;
|
||||
try { bytes = readFileSync(binary); }
|
||||
catch (cause) { throw new Error(`runner_remote_artifact_unavailable: install the Paperclip release containing the ${platform}-${architecture} daemon or configure PAPERCLIP_RUNNER_REMOTE_BINARY_PATH`, { cause }); }
|
||||
|
||||
Reference in new issue
Block a user