diff --git a/doc/plans/2026-10-12-claude-runner-default.md b/doc/plans/2026-10-12-claude-runner-default.md index 6a8b8379b9c..884afe7c528 100644 --- a/doc/plans/2026-10-12-claude-runner-default.md +++ b/doc/plans/2026-10-12-claude-runner-default.md @@ -216,3 +216,38 @@ and [cloud image build](https://github.com/paperclipai/paperclip/actions/runs/38 retain source `557c3064a30779e86e1ae312eab3d1d42c3a8b8d`. Actual pack/image identity agreement remains required. Additional Claude defaults remain gated pending clean-install and execution evidence. No local Docker or Rust build. + +### Independent cloud pack comparison and explicit-asset compatibility + +The cloud image build `38293576503` passed. Its immutable image is +`ghcr.io/paperclipai/paperclip-daytona-runner@sha256:d9dbda3a9435e3e555f431bdeb0ba22e1241ae9103184e6c1646a200e26423e1`. +The image manifest, copied from a stopped disposable container on the cloud +host, exactly matches the independently downloaded Linux x64 producer manifest: +pack digest `sha256:97f44524d1b35ed6295c922f6b52ac376960213be0a0e09a3635c281db26aecf`; +Node digest `sha256:7fde7b8afa198da66257f42ee2001d874c7355631e6d1579a5fb5ef1f246df4c`; +provider-tree digest `sha256:83aafb7d6c951c1fa31c0d8bbbfd32c39b4d265b01607cd566c55c8d9b4a6143`. +Both bind source 557c3064a3. This is actual byte-identity proof, not task execution. + +The independently downloaded ARM producer archive has storage SHA256 +`7c219420b02149cdc3f44c6c5073bed40631d26c03d06f0b109a9ff36962c9c7`; +its pack digest is `sha256:51fe734d79211b474ee13f53080ac60f07cdc9315ebd33b703ecf4a7dc0f8ce1`. +Bounded extraction checked 7,244 members, all recorded artifact hashes and +absence of controller binaries. The shipped Claude admission probe passed on +the native cloud ARM host with zero provider turns and no credentials. +The prior installed pack remains available separately. + +Latest platform review found the automatic Linux restriction also affected +explicit Mac SSH assets. The restriction now applies only when selecting a +bundled pack; explicit assets retain their existing manifest integrity checks. +Eight targeted controls passed, including both Mac architectures and unchanged +automatic Linux selection. Final broad source checks run in the cloud on +reconciled `06c4f5e5a0`: [campaign 38296110502](https://github.com/paperclipai/paperclip/actions/runs/38296110502). +The behavior head `2874232d25` received 5/5 after its three settings fixes. + +Installed Mac proof is available as a Paperclip +[acceptance report](http://127.0.0.1:3105/api/attachments/61653e0a-3ae0-459c-b1d1-20b5d1db73d6/content?download=1) +and [screenshot](http://127.0.0.1:3105/api/attachments/bce37c56-3984-45b9-a995-b6a23cc9a85d/content?download=1), +with artifact work products on CLAA-2. The actual native SDK is the server's +nested 0.3.286 package, executable SHA256 +`75e3016e9d2570767b08e43a7467d4817a4f149232c169ca295f2c95fef21433`; +the top-level Legacy SDK is a separate dependency and was not this runtime. diff --git a/server/src/services/native-runtime/native-session-executor.test.ts b/server/src/services/native-runtime/native-session-executor.test.ts index 3a7d4650037..6d55a13594c 100644 --- a/server/src/services/native-runtime/native-session-executor.test.ts +++ b/server/src/services/native-runtime/native-session-executor.test.ts @@ -1124,6 +1124,33 @@ describe("native provider usage normalization", () => { }); describe("remote provider pack manifest", () => { + it.each(["arm64", "x64"].flatMap(architecture => [false, true].map(explicit => ({ architecture, explicit }))))( + "keeps explicit Mac SSH assets at their existing pack integrity gate ($architecture, explicit=$explicit)", async ({ architecture, explicit }) => { + const root = await mkdtemp(join(tmpdir(), "paperclip-explicit-mac-pack-")); + const daemonPath = join(root, "paperclip-runnerd"); + const daemon = Buffer.alloc(64); + daemon.writeUInt32LE(0xfeedfacf); + daemon.writeUInt32LE(architecture === "arm64" ? 0x0100000c : 0x01000007, 4); + await writeFile(daemonPath, daemon); + state.resolveRemoteRunnerBinary.mockResolvedValue(daemonPath); + state.createBackend.mockClear(); + const remoteExecution = { ...execution, + provider: { kind: "acpx", agent: "claude", model: "claude-opus-5-5" }, + workspace: { ...execution.workspace, cwd: root }, + session: { ...execution.session, normalizedSessionId: `explicit-mac-${randomUUID()}`, driverKind: "acpx_runtime" }, + } as unknown as NativeExecutionInputV1; + try { + await expect(createRunnerdBackend({ db: leaseDb(remoteExecution), execution: remoteExecution, runnerInstanceId: "runner", + runnerRemoteBinaryPath: daemonPath, + ...(explicit ? { runnerRemoteProviderPackPath: root } : {}), + runnerExecutionTarget: { kind: "remote", transport: "ssh", remoteCwd: "/workspace", + spec: { host: "runner.internal", port: 22, username: "runner", remoteWorkspacePath: "/workspace", remoteCwd: "/workspace", + privateKey: null, knownHosts: null, strictHostKeyChecking: true } }, + })).rejects.toThrow(explicit ? "provider-pack.json is unreadable (missing)" : "require a qualified Linux target"); + expect(state.createBackend).not.toHaveBeenCalled(); + } finally { await rm(root, { recursive: true, force: true }); } + }, + ); const canonical = (value: unknown): string => { if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`; if (value && typeof value === "object") { diff --git a/server/src/services/native-runtime/native-session-executor.ts b/server/src/services/native-runtime/native-session-executor.ts index 8db5037bce4..48a7022feab 100644 --- a/server/src/services/native-runtime/native-session-executor.ts +++ b/server/src/services/native-runtime/native-session-executor.ts @@ -11523,10 +11523,11 @@ async function createRunnerdBackendWithinSessionClaim( ? await resolveRemotePaperclipRunnerBinary({ runner: remoteCommandRunner!, remoteCwd: remoteTarget!.remoteCwd, configuredPath: input.runnerRemoteBinaryPath }) : null; const remoteClaudeTarget = remoteClaudeRunnerBinary ? runnerBinaryTarget(readFileSync(remoteClaudeRunnerBinary)) : null; - if (remoteClaude && remoteClaudeTarget !== "linux-x64" && remoteClaudeTarget !== "linux-arm64") { + const useBundledClaudePack = remoteClaude && !input.runnerRemoteProviderPackPath?.trim(); + if (useBundledClaudePack && remoteClaudeTarget !== "linux-x64" && remoteClaudeTarget !== "linux-arm64") { throw new Error("runner_remote_provider_artifact_incompatible: Claude remote provider packs require a qualified Linux target"); } - const bundledClaudeManifest = remoteClaudeTarget === "linux-x64" || remoteClaudeTarget === "linux-arm64" + const bundledClaudeManifest = useBundledClaudePack && (remoteClaudeTarget === "linux-x64" || remoteClaudeTarget === "linux-arm64") ? bundledRemoteProviderPackManifestPath(undefined, remoteClaudeTarget) : null; const bundledClaudeRoot = bundledClaudeManifest && existsSync(join(dirname(bundledClaudeManifest), "node_modules")) ? dirname(bundledClaudeManifest) : null;