mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-15 20:53:23 +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
@@ -78,7 +78,26 @@ These corrections update server setup/execution and guide source. The qualified
|
||||
`a200495` package startup and historical task evidence retain their literal
|
||||
component boundaries; no new managed live proof or paid turn is claimed.
|
||||
|
||||
Next action: require fresh CI/review on the corrected integrated candidate, refresh the
|
||||
Fresh review at `e79254b` verifies the preceding fixes and reports one valid
|
||||
development override edge. The common remote resolver now preserves
|
||||
`PAPERCLIP_RUNNER_BINARY` only when the measured target OS/CPU matches the
|
||||
controller. Explicit remote overrides retain priority; every selected artifact
|
||||
still requires a matching native header. Cross-target selection ignores the
|
||||
controller override. Seven new baseline failures reproduce the defect; 133
|
||||
target/setup/backend/retained/version controls and server typecheck pass after
|
||||
the correction. Receipt:
|
||||
`/private/tmp/codex-default-same-host-override-fix-receipt.json`.
|
||||
|
||||
The `e79254b` preview restart preserves all four configurations and both
|
||||
historical native runs. Its actual served guide now reports the shared GNU floor
|
||||
and qualified Mac/ARM targets. No native build, Docker build, or provider call
|
||||
runs locally. Main CI's selector remains unallocated; other PRs share this queue.
|
||||
No environment approval, owned concurrency conflict, or routing failure was
|
||||
found. Org capacity versus GitHub allocation is unconfirmed. Receipt:
|
||||
`/private/tmp/pr2-e792-selector-queue-investigation.json`. Required final CI
|
||||
aggregates and fresh review remain pending, not passed.
|
||||
|
||||
Next action: require fresh CI/review on the final corrected candidate, refresh the
|
||||
matched preview, and publish final conclusions in the canonical PR's evidence
|
||||
index. Human code-owner approval, managed staging onboarding, and actual
|
||||
post-merge Cloud composition remain outstanding. No merge or production
|
||||
|
||||
@@ -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