Files
PaperClipAI/server/src/services/native-runtime/setup-readiness.test.ts
T
DottaandPaperclip adb69e8f09 feat: default new Codex agents to Paperclip Runner on Linux and macOS (#15683)
## Thinking Path

> - Paperclip is the open source control plane for agent companies.
> - People choose a harness when creating an agent.
> - The server should resolve that harness to a qualified runner.
> - Codex is the first harness in the staged default rollout.
> - Saved agents must retain their execution choices.
> - New Codex agents therefore use Paperclip Runner on qualified
macOS/Linux targets, with an explicit Legacy override.

## Linked Issues or Issue Description

Refs #16028, #15555, #15673, #15422, #15631, #15926, #15932, #16149.

The runtime/install prerequisite and release repair are merged. This
slice owns Codex creation, setup, configuration and preservation. Other
harness defaults remain unchanged. It adds no database migration;
incoming master migrations, required maps and regression coverage are
retained.

## What Changed

- Resolve `runner: auto | paperclip | legacy` at the common creation
boundary across onboarding, API/CLI, hires, built-ins, plugins and
imports. Freeze pending hires and export resolved execution choices.
- Preserve existing runners on upgrades, unrelated edits and import
updates, including compatible accounts, workspace settings, Codex effort
and intentional clears.
- Use the underlying harness picker and branding; put the runner
override in Advanced. Use the shared dropdown and update
production-component stories and creation contracts.
- Support qualified Apple Silicon, Intel Mac, Linux x64 and Linux ARM64.
Linux requires GNU libc 2.39+. Disabled, external, unsupported and
unqualified combinations retain their applicable Legacy execution.
- Test the selected runtime/account/model/environment. Ordinary Connect
probes eligible immutable managed targets and refreshes scoped
discovery. Creation/discovery do not provision. Auth, dependency,
incompatible-setting and cleanup failures remain actionable without
changing runners.
- Keep setup probes read-only, preserve local/SSH isolation and resolve
packaged assets before staging credentials. Bound cleanup reads by the
remaining absolute deadline; physically close every owned ingress socket
before endpoint release. Unconfirmed cleanup retains recovery state.
- Preserve validated completed-onboarding callbacks through startup
recovery. First file-producing work belongs to the child task and uses
the existing dependency/block handoff and task-scoped registration.
- Reuse existing E2E fixtures, artifact checks, reporting and controls.
Keep Docker/Cargo/native compilation remote. Paperclip tarballs contain
no Codex payloads; official optional npm platform dependencies remain.

## Verification

Current tested head: `1c03a8815672d82c264a9b08d4ea1984e8fae107`.
**Merge-ready: required CI, fresh review, package qualification, Mac
core acceptance and Test task/follow-up acceptance pass.** The existing
attachment and native-continuity oracles independently verify both Test
downloads, run attribution and unchanged native/provider/workspace
identities. The maintainer authorized a code-owner-review exception for
this PR only. The first Test setup failure and normal Board
completion-review friction are retained below. Actual post-merge
packages and private Cloud distribution remain separate qualification
gates.

- [Current
CI](https://github.com/paperclipai/paperclip/actions/runs/38564241625):
all 48 ordinary jobs and required verify/E2E pass. Attempt 1 retained
six simultaneous host-shutdown cancellations. One failed-only retry
passes those six lanes and two aggregates, preserving 40 successes.
Actual integration checkout `53104c1` combines this head with master
`c80`; it is not relabeled as the branch tree. Fresh exact-head Greptile
5/5, 30 resolved threads, no actionable findings.
- [Exact-source package
qualification](https://github.com/paperclipai/paperclip/actions/runs/38565287239):
nine applicable jobs pass, nine conditional jobs skip. Four remote
native producers; Linux x64 clean install; Intel Mac, Apple Silicon and
Linux ARM64 native startup/physical cleanup controls. All 18
tarballs/four daemon source/hash/manifest bindings and 343 migrations
through 0344 verify. No Codex payload is packed. These hosted tarballs
are not a published npm canary.
- Normal Apple Silicon installation and credential-free native
startup/cleanup pass using the packaged daemon, with no source build or
provider turn. Fresh installed DB initialization passes. Upgrade
651a→1c03 preserves all three complete execution configs, accounts, ten
recorded run profiles, paused budgets, active incidents, cost summaries
and two historical unpriced events. Historical recovery state is
retained.
- Cleanup regressions: four delayed-read baseline failures become 15
passing deadline/authority controls. No-acknowledgement and prior-error
socket ownership regressions become 12 passing controls. Direct
runtime/server types, maps: 35 areas / 176 IDs, diff checks and
independent reviews pass. Permission/auth/reconnect policy and the
10-second setup budget remain intact.
- Existing source coverage includes creation/selection/imports, pending
approvals, recovery, account/lifecycle handling, mounted forms,
dropdowns and E2E support. Full typechecks/tests/build/token and
applicable Storybook checks pass in the recorded current CI; no
duplicate suite or weakened grader.
- Literal 9546 live Mac proof: ordinary Auto Codex onboarding, useful
document, real completed-child callback/reply, seven successful native
runs, manual follow-up and independently downloaded1,101-byte child
file. Existing Legacy title edit/reload preserves execution; two real
Legacy runs produce independently checked15/34-byte files. These remain
historical source records.
- Current literal 1c03 Mac core acceptance passes independent checks:
ordinary Auto native Codex onboarding without runner override; only
timeout 180 seconds/concurrency 1 saved; six successful native runs and
six serialized turns (maximum overlap 1); both same-task continuity
checks and five mechanical callback checks; actual rendered parent reply
and exact child downloads. The useful handoff advances from revision 1
to 2. Both files are 48 bytes, SHA-256
`8b742bd4e8123a7c078f0a8d9f3449fbfa8a320d7d90be641a2818db3c53d92c` and
`f42db9774fa06e804ab7d596fc8b922a2b638921c418b87d0111151d290a6afe`;
exactly one byte changes and old history remains. The agent is paused
with zero active runs/errors. Both artifact work products have
`isPrimary=false`, and descriptive titles fail a stricter title-helper
assumption; these retained results are scoped editorial follow-up, not
new merge gates. Six source-bound screenshots are uploaded and verified
in the private primary QA report.
- [Literal 9546 managed
proof](https://d1p6rlowie26tp.cloudfront.net/runner-e2e/campaigns/gha-38485488621-1/index.html):
original 19 assertions and completed judge pass, four native turns,
child-bound240-byte file and session/workspace continuity. Checkout
reports a verified generated target-lock mutation; full final porcelain
was not retained. Signed execution-image identity remains separate from
Core source.
- Actual 651a Test deployment succeeds on one owned/pinned target. One
ordinary Auto API-key Connect then fails native cleanup before hire.
Controller turn/drain/suspend completion and service-recorded
environment cleanup are observed; they do not certify physical deletion
or native authority retirement. Failure/recovery/unknown holds are
retained. No Legacy switch or unchanged paid retry. The two source
repairs above require actual treatment.
- [Current immutable Core
preview](https://github.com/paperclipai/paperclip/actions/runs/38570365725)
and matching DB/shared artifacts pass. One owned, pinned Test target
serves exact `1c03`; identical migration history correctly skips the
migrator. Ordinary Auto Codex Connect succeeds and hires native; saved
native selection and QA bounds survive reload. The initial child
preparation attempt fails before provider work, with actual stdout
identifying `renewal_deadline`; its trigger remains unknown and the
failure is retained. Normal Board **Try again** and **Resume** recover
the child to Done, producing a browser-verified 44-byte file, SHA-256
`0a8458b1efcc2d7a0d815689458c4caab039a46c7c7a3e5805de6bc8b1a96ecc`. The
actual parent callback reports the completed child document/file. The
existing validator requires a parent-owned attachment, so recovery
enters normal Review; after byte checking, normal **Approve completion**
reaches parent Done. No forced status or duplicate parent file is used.
The validator is unchanged from integrated and current master.
Autonomous initial callback-to-Done is not proven. The same-child
follow-up completes document revision 2 and publishes a second
independently downloaded 44-byte file, SHA-256
`22022257300fae762784632d08d3bbe0bab61e2ce81f91864a14dcd7aa464591`; only
the revision digit changes. The unchanged existing native-continuity and
attachment oracles pass. All ten recorded runs are terminal: nine native
successes and the retained pre-provider setup failure. The QA agent is
paused with zero active runs. The pre-existing parent evidence scope
mismatch is a separate follow-up, with ordinary completion review
exercised rather than a forced status or relaxed validation. This
unsigned Core preview remains distinct from post-merge private Cloud
composition.

## Risks

- Saved agents are not migrated. Explicit runner changes use normal
configuration/session rules; historical runs retain their recorded
execution.
- Native readiness depends on verified assets and server-owned target
qualification. A Legacy probe, HTTP200 or successful account save cannot
prove native readiness.
- Recovery exceptions remain restricted to validated
completed-onboarding reports; process-loss, ownership and terminal-state
safeguards remain active. Unexecuted live journeys stay open.
- Linux ARM paid tasks, Windows, Alpine and 32-bit ARM remain deferred.
Cold Cargo cache misses are recorded; no warm-cache speedup is claimed.
- Historical invoices/physical cleanup remain unconfirmed and held.
After merge, exact published npm canary, signed standard image/migrator,
private composition and serving/live acceptance still require
qualification. Production deployment/default promotion is not
authorized.

## Model Used

OpenAI Codex, GPT-6 family, with reasoning, tools, code execution and
parallel agents. Exact serving snapshot and context-window size are not
exposed.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used with available version and
capability details
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have linked public issues or PRs above
- [x] I have not referenced internal/instance-local Paperclip issues or
links
- [x] My branch name describes the change and contains no internal
ticket id
- [x] I have run the stated focused tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation
- [x] I have considered and documented risks
- [x] All Paperclip CI gates are green on the current candidate
- [x] Greptile is 5/5 with no open findings on the current candidate
- [x] I will address all reviewer comments before requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-10-14 11:33:02 -05:00

529 lines
43 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { chmod, lstat, mkdir, mkdtemp, readFile, readlink, readdir, realpath, rm, stat, symlink, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join, posix } from "node:path";
import { ADAPTER_AUTH_MISSING_CHECK_CODE } from "@paperclipai/shared";
import * as localCredentialFile from "../local-ai-credential-file.js";
import * as codexCredentials from "@paperclipai/adapter-codex-local/server";
const { prepareIsolatedCodexHome } = await import(new URL("../../../../packages/paperclip-runner/src/drivers/runtime-context-materializer.ts", import.meta.url).href);
const { execute, nativeProbe, selectedRunner, selectedRemoteRunner, sshExecute, sshRunner, nativeArtifacts, remoteLauncher, registerPrp, ingressConnect } = vi.hoisted(() => ({ execute: vi.fn(), nativeProbe: vi.fn(), selectedRunner: vi.fn(), selectedRemoteRunner: vi.fn(), sshExecute: vi.fn(), sshRunner: vi.fn(), nativeArtifacts: vi.fn(), remoteLauncher: vi.fn(), registerPrp: vi.fn(), ingressConnect: vi.fn() }));
const nativeProbeRuntimeDirectories = vi.hoisted(() => [] as string[]);
vi.mock("node:fs/promises", async original => {
const filesystem = await original<typeof import("node:fs/promises")>();
return { ...filesystem, mkdtemp: async (...args: Parameters<typeof filesystem.mkdtemp>) => {
const directory = await filesystem.mkdtemp(...args);
if (typeof args[0] === "string" && args[0].endsWith("paperclip-native-setup-") && typeof directory === "string") nativeProbeRuntimeDirectories.push(directory);
return directory;
} };
});
vi.mock("@paperclipai/adapter-utils/execution-target", () => ({ runAdapterExecutionTargetShellCommand: execute }));
vi.mock("./native-ssh-command-runner.js", () => ({ createNativeSshCommandRunner: sshRunner }));
vi.mock("../../realtime/runner-prp-ws.js", () => ({ registerRunnerPrpAuthority: registerPrp }));
vi.mock("../../realtime/runner-prp-outbound.js", () => ({ connectRunnerPrpIngress: ingressConnect }));
vi.mock("./native-codex-runner.js", () => ({ resolvePaperclipRunnerBinary: selectedRunner, resolveRemotePaperclipRunnerBinary: selectedRemoteRunner }));
vi.mock("../../vendor/paperclip-runner/index.js", async original => ({ ...await original<typeof import("../../vendor/paperclip-runner/index.js")>(), probeNativeRunnerEnvironment: nativeProbe }));
vi.mock("./native-session-executor.js", async original => ({ ...await original<typeof import("./native-session-executor.js")>(), createRemoteNativeArtifactPreparation: nativeArtifacts, createRemoteRunnerProcessLauncher: remoteLauncher }));
import { testNativeRunnerAuthentication } from "./setup-readiness.js";
const context = {
companyId: "company", adapterType: "paperclip_runner", config: { externallySandboxed: false, env: { PAPERCLIP_RUNNER_EXTERNAL_SANDBOX: "false" } },
executionTarget: { kind: "remote" as const, transport: "sandbox" as const, providerKey: "test", remoteCwd: "/workspace", runner: { execute: vi.fn() } },
};
describe("Codex selected native account verification", () => {
const receipt = (provider: "codex", model: string | null) => ({ provider, effectiveModel: model, providerDriver: "codex_app_server", helloProbePassed: true, cleanupConfirmed: true });
beforeEach(() => {
execute.mockReset(); nativeProbe.mockReset(); nativeProbeRuntimeDirectories.splice(0);
sshExecute.mockReset().mockResolvedValue({ exitCode: 0, timedOut: false, stdout: "", stderr: "" });
sshRunner.mockReset().mockReturnValue({ execute: sshExecute });
nativeArtifacts.mockReset().mockReturnValue({ prepare: vi.fn().mockResolvedValue(undefined) });
remoteLauncher.mockReset().mockReturnValue(vi.fn());
registerPrp.mockReset().mockImplementation(async () => ({ release: vi.fn() }));
vi.stubEnv("PAPERCLIP_RUNNER_PUBLIC_URL", "wss://paperclip.example.test");
vi.stubEnv("PAPERCLIP_RUNNER_CA_BUNDLE_PATH", "");
vi.stubEnv("PAPERCLIP_RUNNER_REMOTE_CODEX_PATH", "");
vi.stubEnv("PAPERCLIP_RUNNER_REMOTE_CODEX_NPM_SPEC", "");
selectedRunner.mockReset().mockReturnValue("/fixture/bin/paperclip-runnerd");
selectedRemoteRunner.mockReset().mockResolvedValue("/fixture/bin/paperclip-runnerd");
vi.stubEnv("PAPERCLIP_RUNNER_REMOTE_PROVIDER_PACK_PATH", "");
vi.stubEnv("PAPERCLIP_RUNNER_REMOTE_BINARY_PATH", "");
});
afterEach(async () => {
vi.unstubAllGlobals();
vi.unstubAllEnvs();
for (const root of nativeProbeRuntimeDirectories) await rm(root, { recursive: true, force: true });
});
it.each([["codex", "gpt-6.1-sol", "OPENAI_API_KEY"]] as const)("requires the native %s host and only the selected credential", async (provider, model, key) => {
vi.stubEnv(key, "ambient-account");
vi.stubEnv("ANTHROPIC_API_KEY", "ambient-anthropic");
nativeProbe.mockImplementation(async input => { await input.onCleanupConfirmed(); return receipt(provider, model); });
const result = await testNativeRunnerAuthentication({ companyId: "company", adapterType: "paperclip_runner", config: { externallySandboxed: true, env: { [key]: "bound-account", PAPERCLIP_RUNNER_EXTERNAL_SANDBOX: "true" }, modelReasoningEffort: "low" } }, provider, model);
expect(result.status).toBe("pass");
const input = nativeProbe.mock.calls[0][0];
expect(input).toMatchObject({ provider, model, timeoutMs: 45_000, environment: { [key]: "bound-account" } });
expect(input.transportOptions.runnerBinary).toBe("/fixture/bin/paperclip-runnerd");
expect(input.transportOptions.externallySandboxed).toBe(false);
expect(selectedRunner).toHaveBeenCalledOnce();
expect(input.environment.ANTHROPIC_API_KEY).toBeUndefined();
if (provider === "codex") expect(input.reasoningEffort).toBe("low");
else expect(input.reasoningEffort).toBeUndefined();
await expect(stat(input.runtimeDirectory)).rejects.toThrow();
expect(execute).not.toHaveBeenCalled();
});
it("rejects a missing local daemon before creating private credential staging", async () => {
const home = await realpath(await mkdtemp(join(tmpdir(), "native-preflight-daemon-")));
const auth = '{"tokens":{"account_id":"fixture-account","access_token":"fixture-access"}}';
const readCredential = vi.spyOn(localCredentialFile, "readLocalAiCredentialFile");
try {
await writeFile(join(home, "account-store.json"), auth, { mode: 0o600 });
await symlink(join(home, "account-store.json"), join(home, "auth.json"));
selectedRunner.mockImplementation(() => { throw new Error("native executable is missing"); });
const result = await testNativeRunnerAuthentication({ companyId: "company", adapterType: "paperclip_runner", managedAiCredentialHome: home, config: {} }, "codex", "gpt-6.1-sol");
expect(result.status).toBe("fail");
expect(result.checks[0].message).toBe("native executable is missing");
expect(readCredential).toHaveBeenCalledOnce();
expect(nativeProbeRuntimeDirectories).toEqual([]);
expect(nativeProbe).not.toHaveBeenCalled();
expect(await readFile(join(home, "account-store.json"), "utf8")).toBe(auth);
expect(await readlink(join(home, "auth.json"))).toBe(join(home, "account-store.json"));
expect((await readdir(home)).sort()).toEqual(["account-store.json", "auth.json"]);
} finally { readCredential.mockRestore(); await rm(home, { recursive: true, force: true }); }
});
it.each(["local", "remote"])("cleans its own private root when %s credential staging fails before probe handoff", async transport => {
const home = await realpath(await mkdtemp(join(tmpdir(), "native-preflight-staging-")));
const auth = '{"tokens":{"account_id":"fixture-account","access_token":"fixture-access"}}';
try {
await writeFile(join(home, "account-store.json"), auth, { mode: 0o600 });
await symlink(join(home, "account-store.json"), join(home, "auth.json"));
await writeFile(join(home, "config.toml"), "x".repeat(1024 * 1024 + 1), { mode: 0o600 });
const selectedContext = transport === "remote" ? sshContext : { companyId: "company", adapterType: "paperclip_runner", config: {} };
const result = await testNativeRunnerAuthentication({ ...selectedContext, managedAiCredentialHome: home }, "codex", "gpt-6.1-sol");
expect(result.status).toBe("fail");
expect(result.checks[0].message).toContain("configuration exceeds its size limit");
expect(nativeProbe).not.toHaveBeenCalled();
expect(sshExecute).not.toHaveBeenCalled();
expect(nativeProbeRuntimeDirectories).toHaveLength(1);
await expect(stat(nativeProbeRuntimeDirectories[0]!)).rejects.toMatchObject({ code: "ENOENT" });
expect(await readFile(join(home, "account-store.json"), "utf8")).toBe(auth);
expect((await stat(join(home, "config.toml"))).size).toBe(1024 * 1024 + 1);
expect(await readlink(join(home, "auth.json"))).toBe(join(home, "account-store.json"));
} finally { await rm(home, { recursive: true, force: true }); }
});
it.each(["host-home", "host-codex-home", "configured-home", "managed-home"] as const)("reuses the task's authorized local Codex home before isolation (%s)", async mode => {
vi.stubEnv("HOME", "/host-home");
vi.stubEnv("CODEX_HOME", mode === "host-home" ? "" : "/host-codex");
vi.stubEnv("OPENAI_API_KEY", "ambient-key-is-not-authorized");
nativeProbe.mockResolvedValue(receipt("codex", "observed-model"));
const config = mode === "configured-home" || mode === "managed-home" ? { env: { CODEX_HOME: "/configured-codex" } } : {};
expect((await testNativeRunnerAuthentication({ companyId: "company", adapterType: "paperclip_runner", config,
...(mode === "managed-home" ? { managedAiCredentialHome: "/managed-codex" } : {}) }, "codex", null)).status).toBe("pass");
const input = nativeProbe.mock.calls[0][0];
expect(input.transportOptions.sourceCodexHome).toBe({ "host-home": "/host-home/.codex", "host-codex-home": "/host-codex", "configured-home": "/configured-codex", "managed-home": "/managed-codex" }[mode]);
expect(input.environment.OPENAI_API_KEY).toBeUndefined();
expect(input.environment.HOME).toBeUndefined();
expect(input.runtimeDirectory).not.toContain("host-home");
});
it.each(["same-account", "another-account", "bound-key", "invalid-handoff"] as const)("preserves the server-selected login refresh before local cleanup (%s)", async mode => {
const home = await realpath(await mkdtemp(join(tmpdir(), "native-local-login-")));
const auth = (account: string, marker: string, age: number) => JSON.stringify({ tokens: { account_id: account, id_token: `id-${marker}`, access_token: `access-${marker}`, refresh_token: `refresh-${marker}` }, last_refresh: new Date(Date.now() - age).toISOString() });
const store = join(home, "account.json");
try {
await writeFile(store, auth("qa-account", "old", 120_000), { mode: 0o600 });
await symlink(store, join(home, "auth.json"));
nativeProbe.mockImplementation(async input => {
const privateHome = join(input.runtimeDirectory, "codex-home");
await mkdir(privateHome, { mode: 0o700 });
const filename = join(privateHome, "auth.json");
await writeFile(filename, auth(mode === "another-account" ? "other-account" : "qa-account", "new", 60_000), { mode: 0o600 });
if (mode === "bound-key") expect(input.onCodexCredentialRefresh).toBeUndefined();
else await input.onCodexCredentialRefresh(mode === "invalid-handoff" ? store : filename);
await input.onCleanupConfirmed();
return receipt("codex", "gpt-6.1-sol");
});
const result = await testNativeRunnerAuthentication({ companyId: "company", adapterType: "paperclip_runner", managedAiCredentialHome: home, config: { env: { CODEX_HOME: home, ...(mode === "bound-key" ? { OPENAI_API_KEY: "selected-key" } : {}) } } }, "codex", "gpt-6.1-sol");
expect(result.status).toBe(mode === "invalid-handoff" ? "fail" : "pass");
expect(JSON.parse(await readFile(store, "utf8")).tokens.access_token).toBe(mode === "same-account" ? "access-new" : "access-old");
expect((await stat(store)).mode & 0o777).toBe(0o600);
expect(await realpath(join(home, "auth.json"))).toBe(store);
expect(JSON.stringify(result)).not.toContain("access-new");
if (mode !== "invalid-handoff") await expect(stat(nativeProbe.mock.calls[0][0].runtimeDirectory)).rejects.toMatchObject({ code: "ENOENT" });
} finally { await rm(home, { recursive: true, force: true }); }
});
const sshContext = { companyId: "company", adapterType: "paperclip_runner", config: {}, executionTarget: {
kind: "remote" as const, transport: "ssh" as const, remoteCwd: "/qa-workspace", spec: { host: "qa-host", username: "qa-user", port: 22, remoteCwd: "/qa-workspace", remoteWorkspacePath: "/qa-workspace", privateKey: null, knownHosts: null, strictHostKeyChecking: true },
} };
it.each(["local", "remote"].flatMap(transport => ["configured", "operator"].flatMap(source => [false, true].map(linked => ({ transport, source, linked })))))
("matches actual task auth seeding before $transport setup (source=$source, linked=$linked)", async ({ transport, source, linked }) => {
const home = await realpath(await mkdtemp(join(tmpdir(), "native-unbound-auth-")));
const auth = '{"tokens":{"account_id":"fixture-unbound-account","access_token":"fixture-unbound-access","refresh_token":"fixture-unbound-refresh","id_token":"fixture-unbound-id"}}';
try {
const stored = join(home, linked ? "account-store.json" : "auth.json");
await writeFile(stored, auth, { mode: 0o600 });
if (linked) await symlink(stored, join(home, "auth.json"));
const taskHome = join(home, "actual-task-home");
await prepareIsolatedCodexHome({ context: null, codexHome: taskHome, sourceCodexHome: home });
const taskAuth = await readFile(join(taskHome, "auth.json"), "utf8").catch(() => null);
expect(taskAuth).toBe(linked ? null : auth);
vi.stubEnv("CODEX_HOME", source === "operator" ? home : "");
nativeProbe.mockImplementation(async input => {
const probeHome = join(input.runtimeDirectory, "codex-home");
await prepareIsolatedCodexHome({ context: null, codexHome: probeHome, sourceCodexHome: input.transportOptions.sourceCodexHome });
expect(await readFile(join(probeHome, "auth.json"), "utf8")).toBe(auth);
await input.onCleanupConfirmed();
return receipt("codex", "gpt-6.1-sol");
});
const testContext = transport === "remote" ? sshContext : { companyId: "company", adapterType: "paperclip_runner", config: {} };
const result = await testNativeRunnerAuthentication({ ...testContext, config: {
managedAiCredentialHome: home,
env: source === "configured" ? { CODEX_HOME: home } : {},
} }, "codex", "gpt-6.1-sol");
expect(result.status).toBe(linked ? "fail" : "pass");
if (linked) {
expect(nativeProbe).not.toHaveBeenCalled();
expect(sshExecute).not.toHaveBeenCalled();
expect(result.checks[0].message).toContain(source === "configured" ? "env.CODEX_HOME" : "operator CODEX_HOME");
expect(result.checks[0].message).toContain("Legacy runner");
expect(result.checks[0].message).toContain("managed Codex Connection");
} else expect(nativeProbe).toHaveBeenCalledOnce();
expect(await readFile(stored, "utf8")).toBe(auth);
expect(JSON.stringify(result)).not.toContain("fixture-unbound-access");
} finally { await rm(home, { recursive: true, force: true }); }
});
it.each(["local", "remote"].flatMap(transport => ["OPENAI_API_KEY", "CODEX_API_KEY"].flatMap(key => ["valid", "dangling", "invalid-store", "probe-error"].map(authState => ({ transport, key, authState })))))
("keeps the configured $key task credential authoritative over $authState linked auth during $transport setup", async ({ transport, key, authState }) => {
const home = await realpath(await mkdtemp(join(tmpdir(), "native-key-linked-auth-")));
const auth = '{"tokens":{"account_id":"unused-subscription","access_token":"unused-subscription-access"}}';
const stored = join(home, "account-store.json");
const readCredential = vi.spyOn(localCredentialFile, "readLocalAiCredentialFile");
const copyBack = vi.spyOn(codexCredentials, "copyBackCodexAuth");
try {
if (authState === "valid" || authState === "probe-error") await writeFile(stored, auth, { mode: 0o600 });
else if (authState === "invalid-store") await mkdir(stored, { mode: 0o700 });
await symlink(stored, join(home, "auth.json"));
const taskHome = join(home, "actual-key-task-home");
await prepareIsolatedCodexHome({ context: null, codexHome: taskHome, sourceCodexHome: home, apiKey: "configured-fixture-key" });
expect(JSON.parse(await readFile(join(taskHome, "auth.json"), "utf8"))).toEqual({ OPENAI_API_KEY: "configured-fixture-key" });
nativeProbe.mockImplementation(async input => {
expect(input.onCodexCredentialRefresh).toBeUndefined();
expect(input.transportOptions.sourceCodexHome).toBe(home);
const probeHome = join(input.runtimeDirectory, "codex-home");
await prepareIsolatedCodexHome({ context: null, codexHome: probeHome, sourceCodexHome: input.transportOptions.sourceCodexHome,
apiKey: input.environment.CODEX_API_KEY ?? input.environment.OPENAI_API_KEY });
expect(JSON.parse(await readFile(join(probeHome, "auth.json"), "utf8"))).toEqual({ OPENAI_API_KEY: "configured-fixture-key" });
await input.onCleanupConfirmed();
if (authState === "probe-error") throw new Error("authentication failed: configured-fixture-key");
return receipt("codex", "gpt-6.1-sol");
});
const testContext = transport === "remote" ? sshContext : { companyId: "company", adapterType: "paperclip_runner", config: {} };
const result = await testNativeRunnerAuthentication({ ...testContext, config: { env: { CODEX_HOME: home, [key]: "configured-fixture-key" } } }, "codex", "gpt-6.1-sol");
expect(result.status).toBe(authState === "probe-error" ? "fail" : "pass");
if (authState === "probe-error") expect(result.checks[0]).toMatchObject({ code: "codex_hello_probe_auth_required", message: "authentication failed: [REDACTED]" });
expect(nativeProbe).toHaveBeenCalledOnce();
expect(readCredential).not.toHaveBeenCalled();
expect(copyBack).not.toHaveBeenCalled();
expect((await lstat(join(home, "auth.json"))).isSymbolicLink()).toBe(true);
expect(await readlink(join(home, "auth.json"))).toBe(stored);
if (authState === "valid" || authState === "probe-error") expect(await readFile(stored, "utf8")).toBe(auth);
else if (authState === "invalid-store") expect(await readdir(stored)).toEqual([]);
else await expect(lstat(stored)).rejects.toMatchObject({ code: "ENOENT" });
expect(JSON.stringify(result)).not.toContain("configured-fixture-key");
} finally { readCredential.mockRestore(); copyBack.mockRestore(); await rm(home, { recursive: true, force: true }); }
});
it.each(["local", "remote"].flatMap(transport => [false, true].map(linked => ({ transport, linked }))))
("seeds the selected subscription before isolated $transport setup (linked=$linked)", async ({ transport, linked }) => {
const home = await realpath(await mkdtemp(join(tmpdir(), "native-selected-auth-")));
const auth = JSON.stringify({ tokens: { account_id: "fixture-account", id_token: "fixture-id", access_token: "fixture-access", refresh_token: "fixture-refresh" }, last_refresh: "2026-01-01T00:00:00.000Z" });
const stored = join(home, linked ? "account-store.json" : "auth.json");
let seeded = false;
try {
await writeFile(stored, auth, { mode: 0o600 });
if (linked) await symlink(stored, join(home, "auth.json"));
await writeFile(join(home, "config.toml"), 'cli_auth_credentials_store = "file"\n[mcp_servers.host_only]\ncommand = "untrusted-host-tool"\n', { mode: 0o600 });
await writeFile(join(home, "unassigned-private-file"), "must-not-copy");
nativeProbe.mockImplementation(async input => {
expect(input.environment._PAPERCLIP_NATIVE_SETUP_CODEX_AUTH_JSON_SECRET).toBeUndefined();
const codexHome = join(input.runtimeDirectory, "codex-home");
await prepareIsolatedCodexHome({ context: null, codexHome, sourceCodexHome: input.transportOptions.sourceCodexHome,
apiKey: input.environment.CODEX_API_KEY ?? input.environment.OPENAI_API_KEY });
const privateAuth = await readFile(join(codexHome, "auth.json"), "utf8").catch(() => null);
seeded = privateAuth === auth;
expect((await readFile(join(codexHome, "config.toml"), "utf8"))).not.toContain("untrusted-host-tool");
if (!seeded) throw new Error("authentication required");
if (linked) {
const source = join(input.runtimeDirectory, "codex-credential-source");
expect(input.transportOptions.sourceCodexHome).toBe(source);
expect((await stat(source)).mode & 0o777).toBe(0o700);
expect((await stat(join(source, "auth.json"))).mode & 0o777).toBe(0o600);
expect((await readdir(source)).sort()).toEqual(["auth.json", "config.toml"]);
}
if (transport === "remote") {
await remoteLauncher.mock.calls[0][0].ensureArtifact();
const upload = sshExecute.mock.calls.find(([value]) => value.args?.[1]?.includes("/codex-home/auth.json"))?.[0];
expect(upload).toBeDefined();
expect(Buffer.from(upload!.stdin, "base64").toString("utf8")).toBe(auth);
expect(upload!.args[1]).toContain("chmod 600");
}
await input.onCleanupConfirmed();
return receipt("codex", "gpt-6.1-sol");
});
const testContext = transport === "remote" ? sshContext : { companyId: "company", adapterType: "paperclip_runner", config: {} };
const result = await testNativeRunnerAuthentication({ ...testContext, config: { env: { CODEX_HOME: "/untrusted-configured-home", _PAPERCLIP_NATIVE_SETUP_CODEX_AUTH_JSON_SECRET: "spoofed-auth" } }, managedAiCredentialHome: home }, "codex", "gpt-6.1-sol");
expect(result.status).toBe("pass");
expect(seeded).toBe(true);
expect(await readFile(stored, "utf8")).toBe(auth);
expect(JSON.stringify(result)).not.toContain("fixture-access");
await expect(stat(nativeProbe.mock.calls[0][0].runtimeDirectory)).rejects.toMatchObject({ code: "ENOENT" });
expect(execute).not.toHaveBeenCalled();
} finally { await rm(home, { recursive: true, force: true }); }
});
it.each(["local", "remote"].flatMap(transport => ["dangling", "foreign-owner", "unsafe-mode", "oversize"].map(mode => ({ transport, mode }))))
("rejects $mode linked subscription before $transport provider admission", async ({ transport, mode }) => {
const home = await realpath(await mkdtemp(join(tmpdir(), "native-invalid-link-")));
const stored = join(home, "account-store.json");
let owner: ReturnType<typeof vi.spyOn> | undefined;
try {
if (mode !== "dangling") await writeFile(stored, mode === "oversize" ? "x".repeat(65_537) : '{"tokens":{"account_id":"fixture-account"}}', { mode: 0o600 });
await symlink(stored, join(home, "auth.json"));
if (mode === "unsafe-mode") await chmod(stored, 0o644);
if (mode === "foreign-owner") owner = vi.spyOn(process, "getuid").mockReturnValue(process.getuid!() + 1);
nativeProbe.mockResolvedValue(receipt("codex", "gpt-6.1-sol"));
const testContext = transport === "remote" ? sshContext : { companyId: "company", adapterType: "paperclip_runner", config: {} };
const result = await testNativeRunnerAuthentication({ ...testContext, managedAiCredentialHome: home }, "codex", "gpt-6.1-sol");
expect(result.status).toBe("fail");
expect(nativeProbe).not.toHaveBeenCalled();
expect(sshExecute).not.toHaveBeenCalled();
if (mode === "dangling") expect(result.checks.some(check => check.code === ADAPTER_AUTH_MISSING_CHECK_CODE)).toBe(true);
} finally { owner?.mockRestore(); await rm(home, { recursive: true, force: true }); }
});
it.each(["local", "remote"])("redacts checked file credentials from %s native probe errors without exporting them to env", async transport => {
const home = await realpath(await mkdtemp(join(tmpdir(), "native-file-redaction-")));
const auth = JSON.stringify({ tokens: { account_id: "fixture-account", id_token: "fixture-id", access_token: "fixture-access", refresh_token: "fixture-refresh" } });
try {
await writeFile(join(home, "account-store.json"), auth, { mode: 0o600 });
await symlink(join(home, "account-store.json"), join(home, "auth.json"));
nativeProbe.mockImplementation(async input => {
expect(input.environment._PAPERCLIP_NATIVE_SETUP_CODEX_AUTH_JSON_SECRET).toBeUndefined();
throw new Error("authentication failed: " + auth + " fixture-access fixture-refresh");
});
const testContext = transport === "remote" ? sshContext : { companyId: "company", adapterType: "paperclip_runner", config: {} };
const result = await testNativeRunnerAuthentication({ ...testContext, managedAiCredentialHome: home }, "codex", "gpt-6.1-sol");
expect(result.status).toBe("fail");
expect(JSON.stringify(result)).not.toMatch(/fixture-account|fixture-access|fixture-refresh|fixture-id/);
expect(result.checks[0].message).toContain("[REDACTED]");
} finally { await rm(home, { recursive: true, force: true }); }
});
it.each(["local", "remote"].flatMap(transport => [false, true].map(confirmed => ({ transport, confirmed }))))
("settles private linked credentials only after confirmed canceled $transport setup (confirmed=$confirmed)", async ({ transport, confirmed }) => {
const home = await realpath(await mkdtemp(join(tmpdir(), "native-linked-cancel-")));
const auth = '{"tokens":{"account_id":"fixture-account","access_token":"fixture-access"}}';
try {
await writeFile(join(home, "account-store.json"), auth, { mode: 0o600 });
await symlink(join(home, "account-store.json"), join(home, "auth.json"));
nativeProbe.mockImplementation(async input => {
expect(await readFile(join(input.transportOptions.sourceCodexHome, "auth.json"), "utf8")).toBe(auth);
if (confirmed) await input.onCleanupConfirmed();
throw new Error("Native provider setup canceled");
});
const testContext = transport === "remote" ? sshContext : { companyId: "company", adapterType: "paperclip_runner", config: {} };
expect((await testNativeRunnerAuthentication({ ...testContext, managedAiCredentialHome: home }, "codex", "gpt-6.1-sol")).status).toBe("fail");
const root = nativeProbe.mock.calls[0][0].runtimeDirectory;
if (confirmed) await expect(stat(root)).rejects.toMatchObject({ code: "ENOENT" });
else expect(await readFile(join(root, "codex-credential-source", "auth.json"), "utf8")).toBe(auth);
expect(await readFile(join(home, "account-store.json"), "utf8")).toBe(auth);
} finally { await rm(home, { recursive: true, force: true }); }
});
it.each(["linux-arm64", "linux-x64"])("uses task staging and the selected %s SSH daemon without a provider pack", async target => {
const selectedBinary = `/fixture/bin/${target}/paperclip-runnerd`;
selectedRemoteRunner.mockResolvedValue(selectedBinary);
let released = false;
registerPrp.mockResolvedValue({ release: async () => { released = true; } });
nativeProbe.mockImplementation(async input => {
const registration = await input.transportOptions.controlPlaneRegistration({ marker: "native-authority" });
expect(registration.connection).toMatchObject({ mode: "connect", connectUrl: expect.stringMatching(/^wss:\/\/paperclip\.example\.test\/api\/runner\/v1\/connect\//) });
await registration.release();
await input.onCleanupConfirmed();
return receipt("codex", "gpt-6.1-sol");
});
expect((await testNativeRunnerAuthentication({ ...sshContext, config: { externallySandboxed: true, env: { OPENAI_API_KEY: "company-account", PAPERCLIP_RUNNER_EXTERNAL_SANDBOX: "true" }, modelReasoningEffort: "low" } }, "codex", "gpt-6.1-sol")).status).toBe("pass");
const input = nativeProbe.mock.calls[0][0];
expect(input).toMatchObject({ reasoningEffort: "low", environment: { OPENAI_API_KEY: "company-account" }, workingDirectory: expect.stringMatching(/^\/qa-workspace\/.paperclip-runtime\/paperclip-native-setup-.*\/filesystem\/workspace$/) });
expect(input.transportOptions).toMatchObject({ runnerBinary: selectedBinary, codexCommand: expect.stringMatching(/\/bin\/codex$/), runnerFilesystemRoot: expect.stringMatching(/\/filesystem$/), externallySandboxed: false });
expect(nativeArtifacts).toHaveBeenCalledWith(expect.objectContaining({ controllerRunnerBinary: selectedBinary, model: "gpt-6.1-sol", targetPlatformArtifactVerified: true }));
expect(selectedRemoteRunner).toHaveBeenCalledExactlyOnceWith({ runner: { execute: sshExecute }, remoteCwd: "/qa-workspace", configuredPath: null });
expect(selectedRunner).not.toHaveBeenCalled();
expect(nativeArtifacts.mock.results[0]!.value.prepare).toHaveBeenCalledWith("dial_wss");
expect(registerPrp).toHaveBeenCalledWith({ companyId: "company", runId: input.transportOptions.prpIdentity.runId, authority: { marker: "native-authority" } });
expect(remoteLauncher).toHaveBeenCalledWith(expect.objectContaining({ runnerInstanceId: input.transportOptions.prpIdentity.runnerInstanceId }));
expect(released).toBe(true);
expect(sshExecute.mock.calls.at(-1)![0]).toMatchObject({ command: "rm", args: ["-rf", "--", expect.stringContaining("paperclip-native-setup-")] });
expect(sshExecute.mock.calls.some(([value]) => JSON.stringify(value).includes("company-account"))).toBe(false);
expect(execute).not.toHaveBeenCalled();
});
it.each(["runtime missing", "authentication failed: selected-key", "no native completion", "model mismatch"])("fails SSH native setup without a legacy fallback (%s)", async message => {
nativeProbe.mockRejectedValue(new Error(message));
const result = await testNativeRunnerAuthentication({ ...sshContext, config: { env: { OPENAI_API_KEY: "selected-key" } } }, "codex", "gpt-6.1-sol");
expect(result.status).toBe("fail"); expect(JSON.stringify(result)).not.toContain("selected-key");
expect(nativeProbe).toHaveBeenCalledOnce(); expect(execute).not.toHaveBeenCalled();
expect(sshExecute.mock.calls.some(([value]) => value.command === "rm")).toBe(false);
expect((await stat(nativeProbe.mock.calls[0][0].runtimeDirectory)).isDirectory()).toBe(true);
});
it("fails SSH transport eligibility before staging or opening the native provider", async () => {
vi.stubEnv("PAPERCLIP_RUNNER_PUBLIC_URL", "");
expect((await testNativeRunnerAuthentication(sshContext, "codex", null)).status).toBe("fail");
expect(sshExecute).not.toHaveBeenCalled(); expect(nativeArtifacts).not.toHaveBeenCalled(); expect(nativeProbe).not.toHaveBeenCalled();
});
it("reports an unavailable SSH target daemon before probing credentials or switching runtimes", async () => {
selectedRemoteRunner.mockRejectedValue(new Error("runner_remote_artifact_unavailable: install the matching linux-arm64 Paperclip release"));
const result = await testNativeRunnerAuthentication(sshContext, "codex", null);
expect(result.status).toBe("fail"); expect(result.checks[0].message).toContain("matching linux-arm64 Paperclip release");
expect(nativeArtifacts).not.toHaveBeenCalled(); expect(nativeProbe).not.toHaveBeenCalled();
expect(selectedRunner).not.toHaveBeenCalled(); expect(execute).not.toHaveBeenCalled();
});
it.each([true, false])("removes SSH probe state only after confirmed teardown (%s)", async confirmed => {
nativeProbe.mockImplementation(async input => {
const registration = await input.transportOptions.controlPlaneRegistration({});
await registration.release();
if (confirmed) await input.onCleanupConfirmed();
throw new Error("authentication failed");
});
expect((await testNativeRunnerAuthentication(sshContext, "codex", null)).status).toBe("fail");
expect(sshExecute.mock.calls.some(([value]) => value.command === "rm")).toBe(confirmed);
const root = nativeProbe.mock.calls[0][0].runtimeDirectory;
if (confirmed) await expect(stat(root)).rejects.toThrow(); else expect((await stat(root)).isDirectory()).toBe(true);
});
it.each(["same-account", "another-account", "read-failure", "uncertain-close"] as const)("preserves managed SSH account refresh and confirmed cleanup (%s)", async mode => {
const home = await mkdtemp(join(tmpdir(), "native-ssh-managed-account-"));
const auth = (account: string, marker: string, age: number) => JSON.stringify({ tokens: { account_id: account, id_token: `id-${marker}`, access_token: `access-${marker}`, refresh_token: `refresh-${marker}` }, last_refresh: new Date(Date.now() - age).toISOString() });
try {
await writeFile(join(home, "account-store.json"), auth("qa-account", "old", 120_000), { mode: 0o600 });
await symlink(join(home, "account-store.json"), join(home, "auth.json"));
const refreshed = auth(mode === "another-account" ? "different-account" : "qa-account", "new", 60_000);
sshExecute.mockImplementation(async input => ({ exitCode: input.args?.[2] === "paperclip-native-refresh" && mode === "read-failure" ? 1 : 0, timedOut: false,
stdout: input.args?.[2] === "paperclip-native-refresh" ? Buffer.from(refreshed).toString("base64") : "", stderr: "" }));
nativeProbe.mockImplementation(async input => {
const registration = await input.transportOptions.controlPlaneRegistration({});
await registration.release();
const original = new Error("authentication failed in native SSH" + (mode === "uncertain-close" ? "; daemon teardown was not confirmed" : ""));
try { await input.onCodexCredentialRefresh(posix.join(input.transportOptions.runnerFilesystemRoot, "codex-home", "auth.json")); }
catch (error) { throw new AggregateError([original, error], original.message + "; " + (error as Error).message, { cause: original }); }
if (mode !== "uncertain-close") await input.onCleanupConfirmed();
throw original;
});
const result = await testNativeRunnerAuthentication({ ...sshContext, managedAiCredentialHome: home }, "codex", "gpt-6.1-sol");
expect(result.status).toBe("fail"); expect(result.checks[0].message).toContain("authentication failed in native SSH");
expect(JSON.parse(await readFile(join(home, "auth.json"), "utf8")).tokens.access_token).toBe(mode === "same-account" || mode === "uncertain-close" ? "access-new" : "access-old");
expect(sshExecute.mock.calls.some(([input]) => input.command === "rm")).toBe(mode === "same-account" || mode === "another-account");
expect(JSON.stringify(result)).not.toContain("access-new");
const read = sshExecute.mock.calls.find(([input]) => input.args?.[2] === "paperclip-native-refresh")![0];
expect(read.command).toBe("sh"); expect(read.args[1]).toContain("head -c 65537"); expect(read.args[1]).toContain("test ! -L");
expect(nativeProbe.mock.calls[0][0].transportOptions.sourceCodexHome).toBe(join(nativeProbe.mock.calls[0][0].runtimeDirectory, "codex-credential-source"));
if (mode === "read-failure") expect(result.checks[0].message).toContain("refresh handoff unavailable");
if (mode === "uncertain-close") expect(result.checks[0].message).toContain("teardown was not confirmed");
} finally { await rm(home, { recursive: true, force: true }); }
});
it.each([
["authentication failed: bound-account", "auth_required"], ["Native provider hello probe timed out.", "timeout"],
["Model is unavailable", "failed"], ["native executable is missing", "failed"], ["native provider returned no response", "failed"],
])("fails closed and redacts native failures (%s)", async (message, code) => {
nativeProbe.mockRejectedValue(new Error(message));
const result = await testNativeRunnerAuthentication({ companyId: "company", adapterType: "paperclip_runner", config: { env: { OPENAI_API_KEY: "bound-account" } } }, "codex", "gpt-6.1-sol");
expect(result.status).toBe("fail");
expect(result.checks[0]).toMatchObject({ code: `codex_hello_probe_${code}`, level: "error" });
expect(result.checks.some(check => check.code === ADAPTER_AUTH_MISSING_CHECK_CODE)).toBe(code === "auth_required");
expect(JSON.stringify(result)).not.toContain("bound-account");
expect(nativeProbe).toHaveBeenCalledOnce();
expect(execute).not.toHaveBeenCalled();
});
it.each([
["authentication required: selected-account", true],
["Model is unavailable", false],
["native executable is missing", false],
])("projects the canonical sandbox login check only for authentication failures (%s)", async (message, authentication) => {
nativeProbe.mockRejectedValue(new Error(message));
const result = await testNativeRunnerAuthentication(context, "codex", "gpt-6.1-sol");
expect(result.status).toBe("fail");
expect(result.checks[0].code).toBe(`codex_hello_probe_${authentication ? "auth_required" : "failed"}`);
expect(result.checks.some(check => check.code === ADAPTER_AUTH_MISSING_CHECK_CODE)).toBe(authentication);
if (authentication) expect(result.checks).toContainEqual(expect.objectContaining({ code: ADAPTER_AUTH_MISSING_CHECK_CODE, level: "error" }));
expect(nativeProbe).toHaveBeenCalledOnce();
expect(execute).not.toHaveBeenCalled();
});
it.each([
{ helloProbePassed: undefined }, { effectiveModel: "another-model" }, { effectiveModel: null }, { effectiveModel: "" }, { provider: "opencode" }, { providerDriver: "codex_cli" },
])("rejects installation-only, legacy, or mismatched receipts", async override => {
nativeProbe.mockResolvedValue({ ...receipt("codex", "gpt-6.1-sol"), ...override });
expect((await testNativeRunnerAuthentication({ companyId: "company", adapterType: "paperclip_runner", config: {} }, "codex", "gpt-6.1-sol")).status).toBe("fail");
});
it("accepts an automatic Codex model only when the native provider observed a nonempty model", async () => {
nativeProbe.mockResolvedValue(receipt("codex", "gpt-6.1-sol"));
expect((await testNativeRunnerAuthentication({ companyId: "company", adapterType: "paperclip_runner", config: {} }, "codex", null)).status).toBe("pass");
expect(nativeProbe.mock.calls[0][0].model).toBeNull();
nativeProbe.mockResolvedValue(receipt("codex", null));
expect((await testNativeRunnerAuthentication({ companyId: "company", adapterType: "paperclip_runner", config: {} }, "codex", null)).status).toBe("fail");
nativeProbe.mockResolvedValue(receipt("codex", "unknown"));
expect((await testNativeRunnerAuthentication({ companyId: "company", adapterType: "paperclip_runner", config: {} }, "codex", null)).status).toBe("fail");
});
it.each([true, false])("removes failed local probe state only after confirmed teardown (%s)", async confirmed => {
nativeProbe.mockImplementation(async input => {
if (confirmed) await input.onCleanupConfirmed();
throw new Error("authentication failed");
});
expect((await testNativeRunnerAuthentication({ companyId: "company", adapterType: "paperclip_runner", config: {} }, "codex", null)).status).toBe("fail");
const root = nativeProbe.mock.calls[0][0].runtimeDirectory;
if (confirmed) await expect(stat(root)).rejects.toThrow();
else expect((await stat(root)).isDirectory()).toBe(true);
});
it.each(["arm64", "x64"].flatMap(controllerArch => ["arm64", "x64"].flatMap(targetArch => [false, true].map(explicit => ({ controllerArch, targetArch, explicit })))))
("uses target $targetArch artifacts and controller PRP from Mac $controllerArch for sandbox setup (override=$explicit)", async ({ controllerArch, targetArch, explicit }) => {
vi.stubGlobal("process", { ...process, platform: "darwin", arch: controllerArch });
const selectedBinary = `/controller/artifacts/linux-${targetArch}/paperclip-runnerd`;
selectedRemoteRunner.mockResolvedValue(selectedBinary);
if (explicit) vi.stubEnv("PAPERCLIP_RUNNER_REMOTE_BINARY_PATH", selectedBinary);
nativeProbe.mockImplementation(async input => {
const registration = await input.transportOptions.controlPlaneRegistration({});
expect(registration.connection.mode).toBe("connect");
await registration.release(); await input.onCleanupConfirmed();
return receipt("codex", "gpt-6.1-sol");
});
const target = { ...context.executionTarget, runner: { execute: sshExecute } };
expect((await testNativeRunnerAuthentication({ ...context, executionTarget: target }, "codex", "gpt-6.1-sol")).status).toBe("pass");
expect(sshRunner).not.toHaveBeenCalled();
expect(selectedRemoteRunner).toHaveBeenCalledExactlyOnceWith({ runner: target.runner, remoteCwd: target.remoteCwd, configuredPath: explicit ? selectedBinary : null });
expect(selectedRunner).not.toHaveBeenCalled();
expect(nativeArtifacts).toHaveBeenCalledWith(expect.objectContaining({ target, runner: target.runner, controllerRunnerBinary: selectedBinary, targetPlatformArtifactVerified: true }));
expect(nativeProbe.mock.calls[0][0].transportOptions.runnerBinary).toBe(selectedBinary);
expect(nativeProbe.mock.calls[0][0].transportOptions.externallySandboxed).toBe(true);
expect(nativeArtifacts.mock.results[0].value.prepare).toHaveBeenCalledWith("dial_wss");
expect(execute).not.toHaveBeenCalled();
});
it.each(["runner_remote_artifact_unavailable", "runner_remote_artifact_platform_mismatch"])("fails sandbox %s before setup or a controller fallback", async code => {
selectedRemoteRunner.mockRejectedValue(new Error(code));
const target = { ...context.executionTarget, runner: { execute: sshExecute } };
const result = await testNativeRunnerAuthentication({ ...context, executionTarget: target }, "codex", "gpt-6.1-sol");
expect(result.status).toBe("fail");
expect(result.checks[0].message).toContain(code);
expect(selectedRunner).not.toHaveBeenCalled(); expect(nativeArtifacts).not.toHaveBeenCalled();
expect(nativeProbe).not.toHaveBeenCalled(); expect(sshExecute).not.toHaveBeenCalled();
expect(execute).not.toHaveBeenCalled();
});
it("prepares listener artifacts before acquiring sandbox ingress and owns its release", async () => {
const order: string[] = [];
const close = vi.fn();
const endpoint = { kind: "authenticated_websocket" as const, websocketUrl: "wss://ingress.test", secretHeaders: [], generation: "generation", refresh: vi.fn(), close };
const getRunnerIngressEndpoint = vi.fn(async () => { order.push("ingress"); return endpoint; });
nativeArtifacts.mockReturnValue({ prepare: vi.fn(async mode => { expect(mode).toBe("listen_ws"); order.push("artifacts"); }) });
const connectionClose = vi.fn();
ingressConnect.mockImplementation(() => { order.push("connect"); return { ready: Promise.resolve(), close: connectionClose }; });
nativeProbe.mockImplementation(async input => {
const registration = await input.transportOptions.controlPlaneRegistration({});
expect(registration.connection.mode).toBe("listen");
remoteLauncher.mock.calls[0][0].onRunnerProcessSpawned();
await registration.activate(); await registration.ready(); await registration.release();
await input.onCleanupConfirmed(); return receipt("codex", "gpt-6.1-sol");
});
const target = { ...context.executionTarget, providerKey: "daytona", runner: { execute: sshExecute }, leaseId: "lease", effectiveCapabilities: { reusableLeases: false, nativeSyncIn: false, nativeSyncOut: false, persistentProcessSessions: false, independentControlCommands: true, incrementalSessionOutput: false, concurrentSyncOperations: false, duplexCommandStream: false, runnerWebSocketIngress: true }, getRunnerIngressEndpoint };
expect((await testNativeRunnerAuthentication({ ...context, executionTarget: target }, "codex", "gpt-6.1-sol")).status).toBe("pass");
expect(order).toEqual(["artifacts", "ingress", "connect"]);
expect(nativeProbe.mock.calls[0][0].transportOptions.externallySandboxed).toBe(true);
expect(connectionClose).toHaveBeenCalledOnce(); expect(registerPrp).not.toHaveBeenCalled();
});
});