feat(runner): import verified Linux companions for normal Pi execution

Add explicit public CLI setup and full async source/profile/byte admission for an operator-pinned Linux companion. Preserve existing remote integrity checks and explicit overrides.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-10-02 00:09:04 -05:00
1 parent 8ced6f9285
commit f96195a7a6
7 files changed
+464 -9

No files matched your search

+16 -3
View File
@@ -1,14 +1,14 @@
import { mkdtemp, mkdir, rm, symlink, writeFile } from "node:fs/promises";
import { mkdtemp, mkdir, realpath, rm, symlink, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { pathToFileURL } from "node:url";
import { Command } from "commander";
import { afterEach, expect, it } from "vitest";
import { registerRuntimeCommands, resolvePiProvisioner } from "../commands/runtime.js";
import { registerRuntimeCommands, resolvePiProvisioner, resolveRemoteCompanionImporter } from "../commands/runtime.js";
const roots: string[] = [];
afterEach(async () => { await Promise.all(roots.splice(0).map(root => rm(root, { recursive: true, force: true }))); });
async function fixture() {
const root = await mkdtemp(join(tmpdir(), "paperclip-cli-runtime-")); roots.push(root);
const root = await realpath(await mkdtemp(join(tmpdir(), "paperclip-cli-runtime-"))); roots.push(root);
const cli = join(root, "dist/vendor/paperclip-runner/cli"); await mkdir(cli, { recursive: true });
await writeFile(join(root, "package.json"), '{"name":"@paperclipai/server"}');
await writeFile(join(root, "dist/index.js"), "throw new Error('server must not start during setup resolution')");
@@ -30,3 +30,16 @@ it("provides an explicit Pi-only operator command and rejects other providers be
const program = new Command(); registerRuntimeCommands(program);
await expect(program.parseAsync(["node", "paperclipai", "runtime", "setup", "untrusted"])).rejects.toThrow("Supported explicit runtime setup");
});
it("resolves the companion importer only inside the actual public server tar layout", async () => {
const f = await fixture(); const path = join(f.root, "dist/services/native-runtime/remote-pi-companion.js");
await mkdir(join(f.root, "dist/services/native-runtime"), { recursive: true });
await writeFile(path, "throw new Error('resolution must not execute importer')");
expect(await resolveRemoteCompanionImporter(f.url)).toBe(path);
const other = await fixture(); await rm(path); await symlink(join(other.cli, "provision-pi.cjs"), path);
await expect(resolveRemoteCompanionImporter(f.url)).rejects.toThrow("escapes");
});
it("requires an explicit digest for the companion import command", async () => {
const program = new Command(); program.exitOverride().configureOutput({ writeErr() {} }); registerRuntimeCommands(program);
await expect(program.parseAsync(["node", "paperclipai", "runtime", "import-remote", "/unused"])).rejects.toThrow("sha256");
});
+26 -3
View File
@@ -1,7 +1,7 @@
import { spawn } from "node:child_process";
import { lstat, readFile, realpath } from "node:fs/promises";
import { dirname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { fileURLToPath, pathToFileURL } from "node:url";
import type { Command } from "commander";
/** Resolve the public server dependency, without importing/starting the server. */
@@ -19,6 +19,14 @@ export async function resolvePiProvisioner(serverUrl: string): Promise<string> {
return provisioner;
}
export async function resolveRemoteCompanionImporter(serverUrl: string): Promise<string> {
const provisioner = await resolvePiProvisioner(serverUrl);
const serverRoot = resolve(dirname(provisioner), "../../../..");
const modulePath = join(serverRoot, "dist/services/native-runtime/remote-pi-companion.js");
if (await realpath(modulePath) !== modulePath || !(await lstat(modulePath)).isFile()) throw new Error("Remote companion importer escapes its installed server");
return modulePath;
}
export async function setupPiRuntime(): Promise<void> {
const provisioner = await resolvePiProvisioner(import.meta.resolve("@paperclipai/server"));
// Only the explicit setup command can download pinned public dependencies.
@@ -42,8 +50,23 @@ export async function setupPiRuntime(): Promise<void> {
}
export function registerRuntimeCommands(program: Command): void {
program.command("runtime").description("Manage explicitly installed agent runtimes")
.command("setup <provider>")
const runtime = program.command("runtime").description("Manage explicitly installed agent runtimes");
runtime.command("import-remote <directory>")
.description("Import a verified Linux Pi companion into the installed server (no downloads)")
.requiredOption("--sha256 <digest>", "SHA256 of companion.json from the trusted release")
.action(async (directory: string, options: { sha256: string }) => {
const modulePath = await resolveRemoteCompanionImporter(import.meta.resolve("@paperclipai/server"));
const importer = await import(pathToFileURL(modulePath).href) as { importRemotePiCompanion(input: { directory: string; sha256: string; checkCancelled: () => void }): Promise<unknown> };
let cancelled = false;
const cancel = () => { cancelled = true; };
process.on("SIGINT", cancel); process.on("SIGTERM", cancel);
try {
const result = await importer.importRemotePiCompanion({ directory, sha256: options.sha256,
checkCancelled: () => { if (cancelled) throw new Error("Remote companion import cancelled"); } });
console.log(JSON.stringify(result));
} finally { process.off("SIGINT", cancel); process.off("SIGTERM", cancel); }
});
runtime.command("setup <provider>")
.description("Install and verify the pinned Pi runtime for this host (public downloads; no model calls)")
.action(async (provider: string) => {
if (provider !== "pi") throw new Error("Supported explicit runtime setup: paperclipai runtime setup pi");
@@ -75,6 +75,76 @@ Pi's published CLI mode union is `text | json | rpc`; no native ACP mode is
present in this release. The reviewed `pi-acp` wrapper remains necessary.
## Explicit Linux companion setup for a Mac controller
A Mac controller needs independently verified Linux runner bytes for Daytona.
Its host Pi installation is not a Linux provider pack. The operator imports a
release companion once, before launching agents:
```sh
paperclipai runtime import-remote /path/to/release/linux-x64 --sha256 MANIFEST_SHA256
```
Obtain `MANIFEST_SHA256` from the trusted release channel, separately from the
copied directory. The directory contains `companion.json`,
`bin/paperclip-runnerd`, and the complete `provider-pack/`. Import verifies the
installed server's exact build commit, qualified Pi profile, Linux x64 ELF,
manifest digest, and every file, mode, directory and contained symbolic link.
External hardlinks, escaping links, extra or modified files, and unsafe modes
are rejected. This command performs no network requests or model calls and does
not run the Linux executable on the Mac.
The destination is the private `remote-companions/linux-x64` directory beneath
the actual installed `@paperclipai/server` package. It must be writable by the
operator, outside the task workspace, and protected with the same host access
controls as the server installation. It is not an agent workspace or a remote
image's self-reported authority. An existing different or damaged installation
is never replaced automatically. Stop all runs before an operator removes or
upgrades that cache. Verification and copying use asynchronous 1 MiB chunks,
yield between chunks, and enforce the same full inventory and ten-minute bound.
Directory entries must retain owner read/write/search permissions so failed imports
can drain their owned cleanup. SIGINT/SIGTERM are deferred through owned filesystem
phases and drain cleanup;
an uncatchable kill or host failure leaves the import unadmitted until the
operator resolves its retained lock or incomplete directory. Do not move the
cache into a task workspace to work around permissions.
Normal remote Pi resolves this authority without binary or pack environment
overrides. The controller re-verifies its complete inventory and uses the exact
Linux daemon identity. Existing remote verification compares image bytes against
that local authority; a mismatch takes the existing verified upload path.
Controller restart and warm-session recovery retain the original Runner artifact
identity checks. Cursor, Copilot and explicit operator overrides keep their
existing admission rules.
For release maintainers, assemble the Linux daemon and default provider pack
for the **same final commit** as the public server/CLI packages. A reused daemon
requires complete native source/config/protocol input equality and retains its
original compiler/build provenance; the manifest does not claim recompilation. In a fresh
release directory place those outputs at `bin/paperclip-runnerd` and
`provider-pack/`, preserving the pack's relative links and modes. Then run the
source-owned manifest generator after building the matching server:
```sh
node scripts/create-runner-remote-companion.mjs /path/to/release/linux-x64 FINAL_SOURCE_SHA
```
Publish the complete companion directory (or distribute it through the normal
trusted release channel) together with the printed manifest SHA256 and the
source/profile/platform provenance. The import command accepts an already
extracted directory, not an archive or URL. The release companion must remain
available with that release; a short-lived qualification artifact is not a
release distribution. No release publication is implied by the tests here.
Build/publish the Daytona image from those same daemon/pack outputs and configure
its immutable OCI digest through the ordinary Daytona environment `image` field.
The image digest and companion manifest are output metadata, not new source
constants, so publication does not require another source commit. The final live
proof must use the installed public CLI, this import command, and ordinary image
configuration without E2E remote binary/provider-pack overrides. That installed
and paid proof remains pending.
Historical v10 qualification checkpoint (2026-09-30): **Pi profile v10 remains unqualified.** The capped-key/login prerequisite remains blocked. Native USD is unknown. The optional private budget helper is not integrated; Cursor's account-cycle cap does not establish Pi's provider spending bound. V10 keeps the native wrapper and closures unchanged, binds the shared ACPX patch under a new digest, and rejects v9 sessions. Fresh exact-runtime admission and final controller verification remain pending.
Historical v9 checkpoint (2026-09-30): **Pi profile v9 remains unqualified**. Current runtime source is `5b8e4454ef0bf12d0bb068c2e41d8c9df9356a1c`; controller/Product harness source is `40064d28522de25fea85c1297f35b41bb8a8897a`. Runtime builds for macOS ARM64/x64 and Linux x64 are complete. No paid profile-v9 pass is claimed. A credential with a verifiable spend limit is still needed for the remaining paid qualification. The optional transport-budget candidate is frozen on a separate branch and is not integrated or a live spending guarantee. See the [comparative capability report](runner-rich-acp-capabilities.md) for current qualification gates and the field audit. The dated observations below retain their original profile identities.
@@ -0,0 +1,14 @@
#!/usr/bin/env node
/** Run after the final server build and Linux daemon/provider-pack assembly. */
import { createHash } from 'node:crypto';
import { writeFile, realpath } from 'node:fs/promises';
import { resolve } from 'node:path';
import { pathToFileURL } from 'node:url';
const [directory, sourceRevision, ...extra] = process.argv.slice(2);
if (!directory || !/^[a-f0-9]{40}$/.test(sourceRevision ?? '') || extra.length) throw new Error('Usage: node scripts/create-runner-remote-companion.mjs DIRECTORY SOURCE_SHA');
const root = await realpath(directory);
const { createRemotePiCompanionManifest } = await import(pathToFileURL(resolve('server/dist/services/native-runtime/remote-pi-companion.js')).href);
const manifest = await createRemotePiCompanionManifest(root, sourceRevision);
const bytes = JSON.stringify(manifest, null, 2) + '\n';
await writeFile(resolve(root, 'companion.json'), bytes, { flag: 'wx', mode: 0o644 });
console.log(JSON.stringify({ directory: root, sourceRevision, target: manifest.target, manifestSha256: createHash('sha256').update(bytes).digest('hex'), providerPackDigest: manifest.providerPackDigest, daemonSha256: manifest.daemonSha256 }));
@@ -21,6 +21,7 @@ import { nativeCompletionFeedback } from "./native-completion-feedback.js";
import { hasAcknowledgedNativeReassignmentStopIntent, hasAcknowledgedNativeStopIntent } from "../acknowledged-native-stop.js";
import { stoppedCodexTurnIsTextOnly } from "./stopped-codex-turn.js";
import { prepareVerifiedRemoteProviderPack } from "./remote-provider-pack.js";
import { selectRemotePiCompanion } from "./remote-pi-companion.js";
import { readNativeLocalProcessStop, PROCESS_START_REQUESTED } from "../native-local-process-stop.js";
import { remoteLeaseCleanupScope } from "../remote-execution-termination.js";
import { resolveConnectorAssignments, isConnectorSkill } from "../connector-runtime.js";
@@ -10979,8 +10980,16 @@ async function createRunnerdBackendWithinSessionClaim(
remoteTarget !== null &&
(input.execution.provider.kind === "opencode" ||
input.execution.provider.kind === "acpx");
// Pi's explicit operator import supplies the target-platform authority. Never
// substitute a Mac controller daemon or trust an image's self-reported hashes.
// Existing explicit overrides and every other provider keep their old path.
const remotePiCompanion = await selectRemotePiCompanion({
provider: input.execution.provider, remote: remoteTarget !== null,
binaryOverride: input.runnerRemoteBinaryPath, packOverride: input.runnerRemoteProviderPackPath,
workspaceRoot: input.execution.workspace.cwd,
});
const configuredProviderPackRoot =
input.runnerRemoteProviderPackPath?.trim() || null;
input.runnerRemoteProviderPackPath?.trim() || remotePiCompanion?.providerPack || null;
let expectedProviderPackManifest: RemoteProviderPackManifest | null = null;
if (requiresRemoteProviderPack) {
if (
@@ -11009,7 +11018,7 @@ async function createRunnerdBackendWithinSessionClaim(
// When an explicit remote artifact is configured, prepareRemoteRunner stages
// these exact bytes at remoteBinary before launch.
const controllerRunnerBinary = remoteTarget
? input.runnerRemoteBinaryPath?.trim() || resolvePaperclipRunnerBinary()
? input.runnerRemoteBinaryPath?.trim() || remotePiCompanion?.runnerBinary || resolvePaperclipRunnerBinary()
: resolvePaperclipRunnerBinary();
const explicitRemoteCodex = input.runnerRemoteCodexPath?.trim() || null;
const remoteCodexNpmSpec = input.runnerRemoteCodexNpmSpec?.trim() || null;
@@ -11386,7 +11395,7 @@ async function createRunnerdBackendWithinSessionClaim(
if (!existsSync(sourceBinary)) {
throw new Error("runner_remote_artifact_unavailable");
}
if (!explicitRemoteBinary) {
if (!explicitRemoteBinary && !remotePiCompanion) {
const platform = await remoteCommandRunner.execute({
command: "sh",
args: ["-c", "uname -s; uname -m"],
@@ -0,0 +1,163 @@
import { createHash } from "node:crypto";
import { chmodSync, linkSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, expect, it, vi } from "vitest";
const hooks = vi.hoisted(() => ({ mkdir: undefined as undefined | ((path: string) => void), rename: undefined as undefined | ((source: string, destination: string) => void), open: undefined as undefined | ((path: string) => void), read: undefined as undefined | ((path: string, bytes: number) => void) }));
vi.mock("node:fs/promises", async (original) => {
const fs = await original<typeof import("node:fs/promises")>();
return { ...fs,
mkdir: async (...args: Parameters<typeof fs.mkdir>) => { hooks.mkdir?.(String(args[0])); return fs.mkdir(...args); },
rename: async (...args: Parameters<typeof fs.rename>) => { hooks.rename?.(String(args[0]), String(args[1])); return fs.rename(...args); },
open: async (...args: Parameters<typeof fs.open>) => { hooks.open?.(String(args[0])); const file = await fs.open(...args); const read = file.read.bind(file); file.read = (async (...readArgs: any[]) => { const result = await (read as any)(...readArgs); hooks.read?.(String(args[0]), result.bytesRead); return result; }) as typeof file.read; return file; },
};
});
vi.mock("../../vendor/paperclip-runner/index.js", async () => await import("../../../../packages/paperclip-runner/src/drivers/acpx/qualified-profiles.js"));
import { QUALIFIED_ACPX_PROFILES } from "../../../../packages/paperclip-runner/src/drivers/acpx/qualified-profiles.js";
import { createRemotePiCompanionManifest, importRemotePiCompanion, inventoryRemoteCompanion, resolveRemotePiCompanion, selectRemotePiCompanion } from "./remote-pi-companion.js";
const roots: string[] = [];
afterEach(() => { hooks.mkdir = undefined; hooks.rename = undefined; hooks.open = undefined; hooks.read = undefined; for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true }); vi.restoreAllMocks(); });
const hash = (bytes: string | Buffer) => createHash("sha256").update(bytes).digest("hex");
const canonical = (v: any): string => Array.isArray(v) ? `[${v.map(canonical).join(",")}]` : v && typeof v === "object" ? `{${Object.keys(v).sort().map(k => `${JSON.stringify(k)}:${canonical(v[k])}`).join(",")}}` : JSON.stringify(v);
async function fixture() {
const root = realpathSync(mkdtempSync(join(tmpdir(), "paperclip-companion-"))); roots.push(root);
const source = "a".repeat(40); const serverRoot = join(root, "server"); const directory = join(root, "release");
mkdirSync(join(serverRoot, "dist"), { recursive: true }); writeFileSync(join(serverRoot, "package.json"), '{"name":"@paperclipai/server"}'); writeFileSync(join(serverRoot, "dist/build-info.json"), JSON.stringify({ commit: source }));
mkdirSync(join(directory, "bin"), { recursive: true }); mkdirSync(join(directory, "provider-pack"));
// Synthetic ELF header; these tests never launch it or claim native qualification.
const elf = Buffer.alloc(128); Buffer.from([127, 69, 76, 70, 2, 1]).copy(elf); elf.writeUInt16LE(62, 18); writeFileSync(join(directory, "bin/paperclip-runnerd"), elf, { mode: 0o755 });
const payload = { runnerSourceRevision: source, target: { platform: "linux", architecture: "x64" }, candidateProviders: { pi: { qualification: "qualified", profileDigest: QUALIFIED_ACPX_PROFILES.pi.commandDigest, path: "provider-assets/pi/linux-x64" } } };
writeFileSync(join(directory, "provider-pack/provider-pack.json"), JSON.stringify({ schema: "paperclip-runner/remote-provider-pack/v1", digest: `sha256:${hash(canonical(payload))}`, payload }));
const manifest = await createRemotePiCompanionManifest(directory, source); const bytes = JSON.stringify(manifest); writeFileSync(join(directory, "companion.json"), bytes); return { root, directory, serverRoot, sha256: hash(bytes), manifest, source };
}
it("imports the release-generated closure and resolves target bytes without environment overrides", async () => {
const f = await fixture(); const result = await importRemotePiCompanion(f); expect(result.status).toBe("imported_verified");
expect(result.runnerBinary).toBe(join(f.serverRoot, "remote-companions/linux-x64/bin/paperclip-runnerd"));
expect(readFileSync(result.runnerBinary!)).toEqual(readFileSync(join(f.directory, "bin/paperclip-runnerd")));
expect((await importRemotePiCompanion(f)).status).toBe("verified_existing"); expect((await resolveRemotePiCompanion({ serverRoot: f.serverRoot }))?.manifestSha256).toBe(f.sha256);
expect(readdirSync(join(f.serverRoot, "remote-companions"))).toEqual(["linux-x64"]);
});
it.each(["sha", "source", "profile", "target", "daemon", "extra", "mode", "outside-link", "outside-hardlink", "pack"])("rejects %s before publishing", async kind => {
const f = await fixture();
if (kind === "sha") f.sha256 = "b".repeat(64);
if (kind === "source") writeFileSync(join(f.serverRoot, "dist/build-info.json"), JSON.stringify({ commit: "b".repeat(40) }));
if (kind === "profile" || kind === "target") { Object.assign(f.manifest, kind === "profile" ? { profileDigest: "sha256:" + "b".repeat(64) } : { target: "darwin-arm64" }); const bytes = JSON.stringify(f.manifest); writeFileSync(join(f.directory, "companion.json"), bytes); f.sha256 = hash(bytes); }
if (kind === "daemon") writeFileSync(join(f.directory, "bin/paperclip-runnerd"), "changed");
if (kind === "extra") writeFileSync(join(f.directory, "extra"), "unlisted");
if (kind === "mode") chmodSync(join(f.directory, "bin/paperclip-runnerd"), 0o777);
if (kind === "outside-link") symlinkSync("../../server/package.json", join(f.directory, "provider-pack/escape"));
if (kind === "outside-hardlink") linkSync(join(f.directory, "bin/paperclip-runnerd"), join(f.root, "alias"));
if (kind === "pack") writeFileSync(join(f.directory, "provider-pack/provider-pack.json"), "{}");
await expect(importRemotePiCompanion(f)).rejects.toThrow(); expect(() => readdirSync(join(f.serverRoot, "remote-companions/linux-x64"))).toThrow();
});
it("copies contained symlinks and breaks only fully owned internal hardlinks", async () => {
const f = await fixture(); linkSync(join(f.directory, "bin/paperclip-runnerd"), join(f.directory, "bin/alias")); symlinkSync("paperclip-runnerd", join(f.directory, "bin/link"));
f.manifest = await createRemotePiCompanionManifest(f.directory, f.source); const bytes = JSON.stringify(f.manifest); writeFileSync(join(f.directory, "companion.json"), bytes); f.sha256 = hash(bytes);
expect((await importRemotePiCompanion(f)).status).toBe("imported_verified");
});
it("fails closed on cache corruption, foreign authority and workspace-contained cache", async () => {
const f = await fixture(); const result = await importRemotePiCompanion(f);
await expect(resolveRemotePiCompanion({ serverRoot: f.serverRoot, workspaceRoot: f.root })).rejects.toThrow("workspace");
await expect(resolveRemotePiCompanion({ serverRoot: f.serverRoot, workspaceRoot: join(result.root!, "provider-pack") })).rejects.toThrow("workspace");
writeFileSync(result.runnerBinary!, "corrupted"); await expect(resolveRemotePiCompanion({ serverRoot: f.serverRoot })).rejects.toThrow("inventory");
await expect(importRemotePiCompanion(f)).rejects.toThrow();
});
it("preserves an existing empty destination and releases only its import lock", async () => {
const f = await fixture(); mkdirSync(join(f.serverRoot, "remote-companions/linux-x64"), { recursive: true, mode: 0o700 }); chmodSync(join(f.serverRoot, "remote-companions"), 0o700);
await expect(importRemotePiCompanion(f)).rejects.toThrow(); expect(readdirSync(join(f.serverRoot, "remote-companions/linux-x64"))).toEqual([]); expect(readdirSync(join(f.serverRoot, "remote-companions"))).toEqual(["linux-x64"]);
});
it("rejects an unsafe cache parent and leaves it intact", async () => {
const f = await fixture(); const outside = join(f.root, "outside"); mkdirSync(outside); symlinkSync(outside, join(f.serverRoot, "remote-companions"));
await expect(importRemotePiCompanion(f)).rejects.toThrow(); expect(readdirSync(outside)).toEqual([]);
});
it("reports missing installation without manufacturing a companion", async () => { const f = await fixture(); expect(await resolveRemotePiCompanion({ serverRoot: f.serverRoot })).toBeNull(); });
it("selects only normal remote Pi, preserving explicit overrides and C/C admission", async () => {
const f = await fixture(); await importRemotePiCompanion(f); const workspaceRoot = join(f.root, "workspace"); mkdirSync(workspaceRoot);
const input = { serverRoot: f.serverRoot, workspaceRoot, remote: true, provider: { kind: "acpx", agent: "pi" } };
expect((await selectRemotePiCompanion(input))?.target).toBe("linux-x64");
for (const patch of [{ remote: false }, { provider: { kind: "acpx", agent: "cursor" } }, { provider: { kind: "acpx", agent: "copilot" } }, { provider: { kind: "codex" } }, { binaryOverride: "/operator/runnerd" }, { packOverride: "/operator/pack" }]) expect(await selectRemotePiCompanion({ ...input, ...patch })).toBeNull();
});
it("rejects an appeared empty destination without replacing it", async () => {
const f = await fixture(); const output = join(f.serverRoot, "remote-companions/linux-x64");
hooks.mkdir = path => { if (path !== output) return; hooks.mkdir = undefined; mkdirSync(output, { mode: 0o700 }); };
await expect(importRemotePiCompanion(f)).rejects.toThrow(); expect(readdirSync(output)).toEqual([]);
expect(readdirSync(join(f.serverRoot, "remote-companions"))).toEqual(["linux-x64"]);
});
it("drains owned output, staging and lock cleanup after publication failure", async () => {
const f = await fixture(); hooks.rename = () => { hooks.rename = undefined; throw new Error("injected publication failure"); };
await expect(importRemotePiCompanion(f)).rejects.toThrow("injected publication failure");
expect(readdirSync(join(f.serverRoot, "remote-companions"))).toEqual([]);
});
it("preserves a replaced staging root while draining the other owned cleanup", async () => {
const f = await fixture(); let replaced = "";
hooks.rename = source => {
hooks.rename = undefined; replaced = source.slice(0, source.lastIndexOf("/"));
renameSync(replaced, replaced + "-original"); mkdirSync(replaced, { mode: 0o700 }); writeFileSync(join(replaced, "foreign"), "preserve");
throw new Error("replaced staging");
};
await expect(importRemotePiCompanion(f)).rejects.toThrow("cleanup incomplete"); expect(readFileSync(join(replaced, "foreign"), "utf8")).toBe("preserve");
expect(readdirSync(join(f.serverRoot, "remote-companions"))).not.toContain(".import-linux-x64.lock");
expect(readdirSync(join(f.serverRoot, "remote-companions"))).not.toContain("linux-x64");
});
it.each(["new", "existing"])("defers actual SIGTERM on the %s import path and drains owned cleanup", async (mode) => {
const f = await fixture(); if (mode === "existing") await importRemotePiCompanion(f);
const { build } = await import("esbuild");
const { execFile } = await import("node:child_process");
const { promisify } = await import("node:util");
const bundle = join(f.root, "companion.mjs");
await build({ entryPoints: [new URL("./remote-pi-companion.ts", import.meta.url).pathname], outfile: bundle,
platform: "node", format: "esm", target: "node24", bundle: true, logLevel: "silent",
plugins: [{ name: "source-owned-profile-only", setup(builder) {
builder.onResolve({ filter: /vendor\/paperclip-runner\/index\.js$/ }, () => ({ path: new URL("../../../../packages/paperclip-runner/src/drivers/acpx/qualified-profiles.ts", import.meta.url).pathname }));
} }],
});
const script = join(f.root, "cancel.mjs");
writeFileSync(script, `import {importRemotePiCompanion} from './companion.mjs';
import {readdirSync,existsSync} from 'node:fs';
let cancelled=false, checkpoints=0,sent=false; const cancel=()=>{cancelled=true}; process.on('SIGTERM',cancel);
try { await importRemotePiCompanion({...JSON.parse(process.argv[2]),checkCancelled(){ checkpoints++; if(!sent&&existsSync(process.argv[3]+'/.import-linux-x64.lock')){sent=true;process.kill(process.pid,'SIGTERM');} if(cancelled)throw Error('owned cancellation'); }}); throw Error('unexpected success'); }
catch(error){ if(error.message!=='owned cancellation')throw error; console.log(JSON.stringify({cancelled,checkpoints,remaining:readdirSync(process.argv[3])})); }
finally {process.off('SIGTERM',cancel);}
`);
const { stdout } = await promisify(execFile)(process.execPath, [script, JSON.stringify({ directory: f.directory, sha256: f.sha256, serverRoot: f.serverRoot }), join(f.serverRoot, "remote-companions")], { env: { PATH: "/usr/bin:/bin", LANG: "C.UTF-8" }, timeout: 10_000, maxBuffer: 65536 });
expect(JSON.parse(stdout)).toMatchObject({ cancelled: true, remaining: mode === "existing" ? ["linux-x64"] : [] });
});
it("honors deadline expiry after re-verifying an existing cache without deleting it", async () => {
const f = await fixture(); await importRemotePiCompanion(f); let expired = false;
hooks.open = path => { if(path === join(f.serverRoot, "remote-companions/linux-x64/companion.json")) expired = true; };
vi.spyOn(Date, "now").mockImplementation(() => expired ? 600_001 : 0);
await expect(importRemotePiCompanion(f)).rejects.toThrow("deadline exceeded");
expect(readdirSync(join(f.serverRoot, "remote-companions"))).toEqual(["linux-x64"]);
});
async function largeFixture() {
const f = await fixture(); writeFileSync(join(f.directory, "provider-pack/large"), Buffer.alloc(8 * 1024 ** 2, 0x61));
f.manifest = await createRemotePiCompanionManifest(f.directory, f.source);
const bytes = JSON.stringify(f.manifest); writeFileSync(join(f.directory, "companion.json"), bytes); f.sha256 = hash(bytes); return f;
}
it("yields to unrelated event-loop work between chunks of a real cache file", async () => {
const f = await largeFixture(); await importRemotePiCompanion(f);
const file = join(f.serverRoot, "remote-companions/linux-x64/provider-pack/large");
let reads = 0; let serviced = false; let servicedBeforeNextRead = false;
hooks.read = (path, bytes) => { if(path !== file || !bytes) return; reads++; if(reads === 1) setImmediate(() => { serviced = true; }); else servicedBeforeNextRead ||= serviced; };
expect((await resolveRemotePiCompanion({serverRoot:f.serverRoot}))?.manifestSha256).toBe(f.sha256);
expect(reads).toBeGreaterThanOrEqual(8); expect(servicedBeforeNextRead).toBe(true);
});
it("cancels during a large existing-cache file before reading the whole file and preserves it", async () => {
const f = await largeFixture(); await importRemotePiCompanion(f);
const file = join(f.serverRoot, "remote-companions/linux-x64/provider-pack/large"); let readBytes = 0;
hooks.read = (path, bytes) => { if(path === file) readBytes += bytes; };
await expect(importRemotePiCompanion({...f, checkCancelled(){ if(readBytes) throw Error("cancel during cache bytes"); }})).rejects.toThrow("cancel during cache bytes");
expect(readBytes).toBeGreaterThan(0); expect(readBytes).toBeLessThan(8 * 1024 ** 2);
expect(readdirSync(join(f.serverRoot, "remote-companions"))).toEqual(["linux-x64"]);
});
it("rejects read-only directory modes before claiming a staging or output path", async () => {
const f = await fixture(); const dir = join(f.directory, "provider-pack"); chmodSync(dir, 0o500);
try {
await expect(importRemotePiCompanion({...f, checkCancelled(){}})).rejects.toThrow("owner read/write/search");
expect(() => readdirSync(join(f.serverRoot, "remote-companions"))).toThrow();
} finally { chmodSync(dir, 0o755); }
});
@@ -0,0 +1,163 @@
/** Explicit operator-imported Linux authority. No downloads or executable probes. */
import { setImmediate as yieldToSignals } from "node:timers/promises";
import { createHash } from "node:crypto";
import { type Stats, constants } from "node:fs";
import * as fs from "node:fs/promises";
import { dirname, isAbsolute, join, relative, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { resolveQualifiedAcpxProfile } from "../../vendor/paperclip-runner/index.js";
const SERVER_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "../../..");
const MODEL = "openrouter/deepseek/deepseek-v4-flash-0731";
const MAX_BYTES = 4 * 1024 ** 3;
const MAX_FILE = 512 * 1024 ** 2;
const MANIFEST = "companion.json";
const AUTHORITY = ".import-authority.json";
type Entry = { path: string; mode: number; kind: "directory" } | { path: string; mode: number; kind: "file"; size: number; sha256: string } | { path: string; mode: number; kind: "symlink"; target: string };
export interface RemotePiCompanionManifest { schema: "paperclip.remote-pi-companion/v1"; sourceRevision: string; target: "linux-x64"; profileDigest: string; providerPackDigest: string; daemonSha256: string; entries: Entry[] }
function require(value: unknown, reason: string): asserts value { if (!value) throw new Error(`runner_remote_companion_invalid: ${reason}`); }
const digest = (value: Buffer | string) => createHash("sha256").update(value).digest("hex");
const safe = (path: string) => path.length > 0 && path.length < 4096 && !isAbsolute(path) && !/[\\\x00-\x1f\x7f]/u.test(path) && path.split("/").every(p => p !== "" && p !== "." && p !== "..");
const inside = (root: string, path: string) => path === root || (!relative(root, path).startsWith("..") && !isAbsolute(relative(root, path)));
const same = (a: Stats, b: Stats) => a.dev === b.dev && a.ino === b.ino && a.size === b.size && a.mode === b.mode && a.mtimeMs === b.mtimeMs && a.ctimeMs === b.ctimeMs && a.nlink === b.nlink;
type Checkpoint = () => Promise<void>;
function checkpoints(cancel?: () => void): Checkpoint {
const deadline = Date.now() + 600_000;
return async () => { await yieldToSignals(); cancel?.(); require(Date.now() < deadline, "import deadline exceeded"); };
}
async function directory(path: string) { const st = await fs.lstat(path); require(st.isDirectory() && !st.isSymbolicLink() && await fs.realpath(path) === path, "directory is linked or noncanonical"); return st; }
/** One descriptor and 1MiB buffer; no whole executable allocation or sync hashing. */
async function readStable(path: string, maximum: number, privateFile: boolean, check: Checkpoint, consume?: (chunk: Buffer) => Promise<void>) {
require(await fs.realpath(dirname(path)) === dirname(path), "linked parent");
const file = await fs.open(path, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
try {
const before = await file.stat(); require(before.isFile() && before.size <= maximum && (!privateFile || before.nlink === 1), "file type, link or byte bound");
const hash = createHash("sha256"); let size = 0; const buffer = Buffer.alloc(1024 * 1024); let header = Buffer.alloc(0);
for (;;) {
await check(); const { bytesRead } = await file.read(buffer); if (!bytesRead) break;
size += bytesRead; require(size <= maximum && size <= before.size, "file grew beyond bound");
const chunk = buffer.subarray(0, bytesRead); hash.update(chunk); if (!header.length) header = Buffer.from(chunk.subarray(0, 64));
await consume?.(chunk);
}
require(size === before.size && same(before, await file.stat()) && same(before, await fs.lstat(path)), "file changed during read");
return { size, sha256: hash.digest("hex"), header, stat: before };
} finally { await file.close(); }
}
async function readOwned(path: string, maximum: number, check: Checkpoint, privateFile = false): Promise<Buffer> {
const chunks: Buffer[] = []; await readStable(path, maximum, privateFile, check, async chunk => { chunks.push(Buffer.from(chunk)); }); return Buffer.concat(chunks);
}
/** Complete no-follow inventory, including modes and contained symbolic links. */
export async function inventoryRemoteCompanion(root: string, check = checkpoints()): Promise<Entry[]> {
await directory(root); const entries: Entry[] = []; const links = new Map<string, { count: number; links: number }>(); let total = 0;
const visit = async (path: string): Promise<void> => {
const before = await directory(path);
for (const name of (await fs.readdir(path)).sort()) {
await check();
if (path === root && [MANIFEST, AUTHORITY].includes(name)) continue;
const file = join(path, name); const rel = relative(root, file); require(safe(rel), "unsafe path");
const st = await fs.lstat(file); const mode = st.mode & 0o7777;
require(entries.length < 100_000 && (st.isSymbolicLink() || (mode & 0o7022) === 0), "entry count or unsafe mode");
if (st.isDirectory()) { require((mode & 0o700) === 0o700, "directory requires owner read/write/search for owned cleanup"); entries.push({ path: rel, mode, kind: "directory" }); await visit(file); }
else if (st.isFile()) {
total += st.size; require(total <= MAX_BYTES, "tree byte bound"); const read = await readStable(file, MAX_FILE, false, check);
require(same(st, read.stat), "discovered file changed");
entries.push({ path: rel, mode, kind: "file", size: read.size, sha256: read.sha256 });
const key = `${st.dev}:${st.ino}`; const group = links.get(key) ?? { count: 0, links: st.nlink }; group.count++; require(group.links === st.nlink, "hardlink identity drift"); links.set(key, group);
} else if (st.isSymbolicLink()) {
const target = await fs.readlink(file); require(!isAbsolute(target) && !/[\x00-\x1f\x7f]/u.test(target) && inside(root, resolve(dirname(file), target)) && inside(root, await fs.realpath(file)), "escaping symbolic link");
require(same(st, await fs.lstat(file)), "link changed"); entries.push({ path: rel, mode, kind: "symlink", target });
} else throw new Error("runner_remote_companion_invalid: special entry");
}
const after = await directory(path); require(before.dev === after.dev && before.ino === after.ino && before.mtimeMs === after.mtimeMs, "directory changed");
};
await visit(root); require([...links.values()].every(g => g.count === g.links), "external hardlink");
return entries.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
}
async function installedIdentity(serverRoot: string, check: Checkpoint) {
require(process.platform === "darwin" || process.platform === "linux", "companion import supports macOS and Linux controllers");
await directory(serverRoot); const pkg = JSON.parse((await readOwned(join(serverRoot, "package.json"), 65536, check, true)).toString()); require(pkg.name === "@paperclipai/server", "public server package required");
const source = JSON.parse((await readOwned(join(serverRoot, "dist/build-info.json"), 65536, check, true)).toString()).commit;
require(typeof source === "string" && /^[a-f0-9]{40}$/u.test(source), "installed build source is missing");
const profile = resolveQualifiedAcpxProfile("pi", MODEL); require(profile.qualificationStatus !== "pending", "Pi is not qualified");
return { source, profileDigest: profile.commandDigest };
}
async function validate(root: string, manifest: RemotePiCompanionManifest, identity: Awaited<ReturnType<typeof installedIdentity>>, check: Checkpoint) {
require(manifest.schema === "paperclip.remote-pi-companion/v1" && manifest.target === "linux-x64" && manifest.sourceRevision === identity.source && manifest.profileDigest === identity.profileDigest, "source/profile/target mismatch");
require(JSON.stringify(await inventoryRemoteCompanion(root, check)) === JSON.stringify(manifest.entries), "complete inventory mismatch");
const daemon = manifest.entries.find(e => e.path === "bin/paperclip-runnerd");
require(daemon?.kind === "file" && daemon.size > 64 && (daemon.mode & 0o111) !== 0 && daemon.sha256 === manifest.daemonSha256, "daemon identity");
const elf = (await readStable(join(root, daemon.path), MAX_FILE, false, check)).header; require(elf.subarray(0, 4).equals(Buffer.from([127, 69, 76, 70])) && elf[4] === 2 && elf[5] === 1 && elf.readUInt16LE(18) === 62, "Linux x64 ELF required");
const pack = JSON.parse((await readOwned(join(root, "provider-pack/provider-pack.json"), 16 * 1024 ** 2, check)).toString());
const canonical = (v: unknown): string => Array.isArray(v) ? `[${v.map(canonical).join(",")}]` : v && typeof v === "object" ? `{${Object.keys(v).sort().map(k => `${JSON.stringify(k)}:${canonical((v as Record<string, unknown>)[k])}`).join(",")}}` : JSON.stringify(v);
require(pack.schema === "paperclip-runner/remote-provider-pack/v1" && pack.digest === manifest.providerPackDigest && pack.digest === `sha256:${digest(canonical(pack.payload))}`, "pack manifest digest");
require(pack.payload.runnerSourceRevision === identity.source && pack.payload.target.platform === "linux" && pack.payload.target.architecture === "x64", "pack source/target");
const pi = pack.payload.candidateProviders?.pi; require(pi?.qualification === "qualified" && pi.profileDigest === identity.profileDigest && pi.path === "provider-assets/pi/linux-x64", "normal Pi pack required");
}
/** Release-side command uses this same inventory contract before publication. */
export async function createRemotePiCompanionManifest(root: string, sourceRevision: string): Promise<RemotePiCompanionManifest> {
const check = checkpoints();
const profile = resolveQualifiedAcpxProfile("pi", MODEL); const pack = JSON.parse((await readOwned(join(root, "provider-pack/provider-pack.json"), 16 * 1024 ** 2, check)).toString()); const entries = await inventoryRemoteCompanion(root, check);
const daemon = entries.find(e => e.path === "bin/paperclip-runnerd"); require(daemon?.kind === "file", "daemon missing");
const manifest: RemotePiCompanionManifest = { schema: "paperclip.remote-pi-companion/v1", sourceRevision, target: "linux-x64", profileDigest: profile.commandDigest, providerPackDigest: pack.digest, daemonSha256: daemon.sha256, entries };
require(/^[a-f0-9]{40}$/u.test(sourceRevision), "release source"); await validate(root, manifest, { source: sourceRevision, profileDigest: profile.commandDigest }, check); return manifest;
}
function cacheParent(serverRoot: string) { return join(serverRoot, "remote-companions"); }
async function removeOwned(path: string, owned: Stats) { const st = await fs.lstat(path); require(st.dev === owned.dev && st.ino === owned.ino && !st.isSymbolicLink(), "cleanup root ownership changed"); await fs.rm(path, { recursive: true }); }
export async function importRemotePiCompanion(input: { directory: string; sha256: string; serverRoot?: string; checkCancelled?: () => void }) {
const checkpoint = checkpoints(input.checkCancelled);
await checkpoint();
const serverRoot = input.serverRoot ?? SERVER_ROOT; const identity = await installedIdentity(serverRoot, checkpoint); const source = await fs.realpath(input.directory); require(source === resolve(input.directory), "linked import root"); await directory(source);
require(/^[a-f0-9]{64}$/u.test(input.sha256), "expected manifest SHA256 required"); const bytes = await readOwned(join(source, MANIFEST), 32 * 1024 ** 2, checkpoint); require(digest(bytes) === input.sha256, "operator manifest digest mismatch");
const manifest = JSON.parse(bytes.toString()) as RemotePiCompanionManifest; await validate(source, manifest, identity, checkpoint); await checkpoint();
const parent = cacheParent(serverRoot); try { await fs.mkdir(parent, { mode: 0o700 }); } catch (error) { if ((error as NodeJS.ErrnoException).code !== "EEXIST") throw new Error("Remote companion setup requires a writable installed server package", { cause: error }); }
const parentInfo = await directory(parent); require((parentInfo.mode & 0o077) === 0 && parentInfo.uid === process.getuid?.(), "cache must be private and operator-owned");
const lockPath = join(parent, ".import-linux-x64.lock"); const lock = await fs.open(lockPath, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY | constants.O_NOFOLLOW, 0o600); const lockInfo = await lock.stat();
let staging: string | undefined; let stagingInfo: Stats | undefined; let outputInfo: Stats | undefined; let committed = false; const output = join(parent, "linux-x64");
try {
await checkpoint();
try { await fs.lstat(output); const existing = await resolveRemotePiCompanion({ serverRoot, checkpoint }); require(existing?.manifestSha256 === input.sha256, "existing companion differs; remove only after stopping all runs"); await checkpoint(); return { status: "verified_existing", ...existing }; } catch (error) { if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error; }
staging = await fs.mkdtemp(join(parent, ".import-")); stagingInfo = await fs.lstat(staging);
for (const entry of manifest.entries.filter(e => e.kind === "directory").sort((a, b) => a.path.split("/").length - b.path.split("/").length)) await fs.mkdir(join(staging, entry.path), { mode: 0o700 });
for (const entry of manifest.entries) {
if (entry.kind !== "file") continue;
const file = join(staging, entry.path); const outputFile = await fs.open(file, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY | constants.O_NOFOLLOW, 0o600);
try {
const read = await readStable(join(source, entry.path), MAX_FILE, false, checkpoint, async chunk => {
let offset = 0; while (offset < chunk.length) { await checkpoint(); offset += (await outputFile.write(chunk, offset)).bytesWritten; }
});
require(read.size === entry.size && read.sha256 === entry.sha256, "source changed while copied");
} finally { await outputFile.close(); }
await fs.chmod(file, entry.mode); await checkpoint();
}
for (const entry of manifest.entries) if (entry.kind === "symlink") await fs.symlink(entry.target, join(staging, entry.path));
for (const entry of manifest.entries.filter(e => e.kind === "directory").reverse()) await fs.chmod(join(staging, entry.path), entry.mode);
await fs.writeFile(join(staging, MANIFEST), bytes, { flag: "wx", mode: 0o600 }); await validate(staging, manifest, identity, checkpoint); await validate(source, manifest, identity, checkpoint); await checkpoint();
// Claim the final path exclusively; no rename may replace a concurrent directory.
await fs.mkdir(output, { mode: 0o700 }); outputInfo = await fs.lstat(output);
for (const name of await fs.readdir(staging)) { const now = await directory(output); require(now.dev === outputInfo.dev && now.ino === outputInfo.ino, "publication ownership changed"); await fs.rename(join(staging, name), join(output, name)); }
await fs.writeFile(join(output, AUTHORITY), JSON.stringify({ sha256: input.sha256 }) + "\n", { flag: "wx", mode: 0o600 });
const result = await resolveRemotePiCompanion({ serverRoot, checkpoint }); require(result?.manifestSha256 === input.sha256, "publication verification"); await checkpoint(); committed = true; return { status: "imported_verified", ...result };
} finally {
const failures: unknown[] = []; const cleanup = async (fn: () => Promise<void>) => { try { await fn(); } catch (error) { failures.push(error); } };
if (outputInfo && !committed) await cleanup(() => removeOwned(output, outputInfo!));
if (staging && stagingInfo) await cleanup(() => removeOwned(staging!, stagingInfo!));
await cleanup(async () => { const now = await fs.lstat(lockPath); require(now.dev === lockInfo.dev && now.ino === lockInfo.ino, "lock ownership changed"); await fs.unlink(lockPath); }); await lock.close();
if (failures.length) throw new AggregateError(failures, "Remote companion cleanup incomplete; inspect owned paths before retrying");
}
}
export async function resolveRemotePiCompanion(input: { serverRoot?: string; workspaceRoot?: string; checkpoint?: Checkpoint } = {}) {
const check = input.checkpoint ?? checkpoints();
const serverRoot = input.serverRoot ?? SERVER_ROOT; const parent = cacheParent(serverRoot); const root = join(parent, "linux-x64");
try { await fs.lstat(root); } catch (error) { if ((error as NodeJS.ErrnoException).code === "ENOENT") return null; throw error; }
const parentInfo = await directory(parent); require((parentInfo.mode & 0o077) === 0 && parentInfo.uid === process.getuid?.(), "cache privacy"); await directory(root);
if (input.workspaceRoot) { const workspace = await fs.realpath(input.workspaceRoot); require(!inside(workspace, root) && !inside(root, workspace), "companion cache cannot overlap a workspace"); }
const authority = JSON.parse((await readOwned(join(root, AUTHORITY), 65536, check, true)).toString()); const bytes = await readOwned(join(root, MANIFEST), 32 * 1024 ** 2, check, true); require(digest(bytes) === authority.sha256, "installed authority changed");
const manifest = JSON.parse(bytes.toString()) as RemotePiCompanionManifest; await validate(root, manifest, await installedIdentity(serverRoot, check), check);
return { root, runnerBinary: join(root, "bin/paperclip-runnerd"), providerPack: join(root, "provider-pack"), manifestSha256: authority.sha256 as string, sourceRevision: manifest.sourceRevision, target: manifest.target };
}
/** Explicit legacy overrides remain authoritative; C/C never gain this Pi path. */
export async function selectRemotePiCompanion(input: { provider: { kind: string; agent?: string }; remote: boolean; binaryOverride?: string | null; packOverride?: string | null; workspaceRoot: string; serverRoot?: string }) {
if (!input.remote || input.provider.kind !== "acpx" || input.provider.agent !== "pi" || input.binaryOverride?.trim() || input.packOverride?.trim()) return null;
return resolveRemotePiCompanion({ serverRoot: input.serverRoot, workspaceRoot: input.workspaceRoot });
}