Files
PaperClipAI/server/src/__tests__/environment-run-orchestrator.test.ts
T
DottaandClaude Opus 5.5 037c58bb22 fix(daytona): keep the provider auto-stop strictly after the warm runner idle window (#19199)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - A warm remote agent keeps its sandbox running after a turn. The host
keeps a warm runner for an idle window (`runnerIdleTimeoutMs`, or the
agent's `idleTimeoutMs`)
> - The Daytona provider also stops an idle sandbox after
`autoStopInterval` minutes. The default is 15 minutes
> - On staging, the warm idle window was 900,000 ms (15 minutes). The
two windows were equal
> - A follow-up turn at the end of the warm window resumed a sandbox
that Daytona was stopping. Daytona refused the resume in about 4
seconds, and the run failed at setup
> - #19195 added a bounded retry for that refusal. It did not remove the
cause
> - This pull request makes the two windows non-overlapping by
construction. Daytona derives its auto-stop for a warm reusable sandbox
from the same warm idle value that the host uses
> - The benefit is that a follow-up inside the warm window always finds
a running sandbox, and idle sandboxes still stop

## Linked Issues or Issue Description

Refs #19195 (bounded retry for a refused resume; this change removes the
common cause).

**What happened?**

On a staging soak, three follow-up runs failed at setup with a refused
sandbox resume. Each resume began 15 minutes 8 to 25 seconds after the
previous turn released the sandbox. The warm idle timeout was 900,000
ms. Daytona's default `autoStopInterval` is 15 minutes. Nothing in the
code kept these two values apart.

**Expected behavior**

A reusable sandbox that keeps a warm runner must not auto-stop inside
the warm idle window or at its edge. The provider backstop must fire
after the host's own idle handling, with a margin. Idle sandboxes must
still stop.

**Steps to reproduce**

1. Configure a Daytona sandbox environment with `reuseLease` and a warm
runner with `runnerIdleTimeoutMs: 900000` (or an agent with
`lifecycleMode: warm`, `idleTimeoutMs: 900000`).
2. Run a turn. Do not set `autoStopInterval`.
3. Before this change: the sandbox is created with `autoStopInterval:
15`, equal to the warm window. A follow-up near minute 15 races the
provider stop.

**Paperclip version or commit**

`c656b95b6b` (current `master`).

## What Changed

- Daytona plugin (`plugin.ts`): add `effectiveAutoStopIntervalMinutes`.
For a reusable lease that keeps a warm runner, the effective auto-stop
is at least `ceil(warm idle / 1 min) + 5` minutes. The plugin applies
the floor only when the host forwards `warmRunnerIdleTimeoutMs`, or when
the environment itself selects `runnerLifecycleMode: warm`. An explicit
`0` (disabled), a longer configured value, `inherit` with a per-turn
agent, per-turn, and non-reusable sandboxes keep the configured value.
- Daytona plugin: use the effective value at sandbox creation. On
resume, raise a shorter auto-stop on an existing sandbox. This update is
best effort and bounded to 5 seconds; the #19195 retry still covers a
refusal. The stale-handle refresh threshold keeps the configured
interval, so a cached handle is not trusted for longer before the
provider confirms the new interval. The warm window is not written to
the saved normalized config.
- Host (`runner-environment-lifecycle.ts`): add
`resolveWarmRunnerIdleTimeoutMsForRun`. It returns the run's effective
warm idle window (environment `runnerIdleTimeoutMs`, else the agent
`idleTimeoutMs`) for a warm `paperclip_runner` sandbox run. It shares
the lifecycle-mode resolution with `resolveRunnerEnvironmentForRun`.
- Host (`environment-run-orchestrator.ts`, `environment-runtime.ts`):
pass that value as `warmRunnerIdleTimeoutMs` to the sandbox-provider
driver on acquisition and on remote recovery resume. The driver adds it
to the provider worker config only. Lease identity, reuse matching, and
lease metadata are unchanged.
- Daytona manifest: describe the warm floor on `autoStopInterval`.
- `doc/execution-semantics.md`: document the contract.

## Verification

- Red (before the fix, same tests, with the floor disabled): 5 failures
in `plugin.test.ts` "warm idle window versus Daytona auto-stop". The
staging case failed with `expected 900000 to be greater than or equal to
1200000`. The resume case failed because `setAutostopInterval` was not
called.
- Red (host): the new `environment-runtime.test.ts` case first failed
because the provider worker config had no `runnerIdleTimeoutMs`. The
service wrapper dropped the field. The fix forwards it.
- Green: `npx vitest run --config vitest.config.ts` in
`packages/plugins/sandbox-providers/daytona`: 563 passed. This includes
a setter that never settles (resume completes after the 5-second bound)
and `inherit` with no forwarded window (explicit `autoStopInterval: 2`
stays 2).
- Green: `npx vitest run
src/services/runner-environment-lifecycle.test.ts
src/__tests__/environment-runtime.test.ts
src/__tests__/environment-run-orchestrator.test.ts` in `server/`: 242
passed. This includes recovery resume forwarding the admitted warm
window.
- The regression tests assert the relationship `autoStop >= warm idle +
margin` for several warm windows (125 s, 300 s default, 900 s, 1,800 s)
and for an explicit `autoStopInterval: 15`. They are not constant
checks.
- `node scripts/typecheck-low-memory.mjs` passes. `pnpm typecheck` in
the Daytona package passes.

## Risks

- A warm reusable Daytona sandbox now stays running up to the warm
window plus 5 minutes after its last activity, instead of 15 minutes.
With a 15-minute window this is 20 minutes. Cost is bounded and idle
sandboxes still stop.
- The resume-time `setAutostopInterval` call is best effort. If it
fails, the old interval stays and the #19195 retry still applies.
- Other providers are unchanged. They receive one extra host-owned
config key (`warmRunnerIdleTimeoutMs`) only for warm `paperclip_runner`
runs, and they ignore unknown keys.
- No lease identity change, so existing retained sandboxes are still
reused.

> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.

## Model Used

- Claude Opus 5.5 (Anthropic), model ID `claude-opus-5-5`, used through
Claude Code with tool use (shell, file edits, test execution).

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with 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 either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 01:10:19 -06:00

1042 lines
43 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from "vitest";
import { randomUUID } from "node:crypto";
import * as remoteWorkspaces from "../services/remote-workspaces.js";
// ---------------------------------------------------------------------------
// Hoisted mocks — must be declared before any imports that reference them
// ---------------------------------------------------------------------------
const mockResolveEnvironmentExecutionTarget = vi.hoisted(() => vi.fn());
const mockAdapterExecutionTargetToRemoteSpec = vi.hoisted(() => vi.fn());
const mockBuildWorkspaceRealizationRequest = vi.hoisted(() => vi.fn());
const mockUpdateLeaseMetadata = vi.hoisted(() => vi.fn());
const mockUpdateExecutionWorkspace = vi.hoisted(() => vi.fn());
const mockGetStoredWorkspace = vi.hoisted(() => vi.fn());
const mockLogActivity = vi.hoisted(() => vi.fn());
const mockLoggerInfo = vi.hoisted(() => vi.fn());
const mockGetEnvironment = vi.hoisted(() => vi.fn());
const mockGetLease = vi.hoisted(() => vi.fn());
vi.mock("../services/environment-execution-target.js", () => ({
resolveEnvironmentExecutionTarget: mockResolveEnvironmentExecutionTarget,
resolveEnvironmentExecutionTransport: vi.fn().mockResolvedValue(null),
}));
vi.mock("@paperclipai/adapter-utils/execution-target", () => ({
adapterExecutionTargetToRemoteSpec: mockAdapterExecutionTargetToRemoteSpec,
}));
vi.mock("../services/workspace-realization.js", () => ({
buildWorkspaceRealizationRequest: mockBuildWorkspaceRealizationRequest,
}));
vi.mock("../services/environments.js", () => ({
environmentService: vi.fn(() => ({
ensureLocalEnvironment: vi.fn(),
getById: mockGetEnvironment,
getLeaseById: mockGetLease,
acquireLease: vi.fn(),
releaseLease: vi.fn(),
updateLeaseMetadata: mockUpdateLeaseMetadata,
})),
}));
vi.mock("../services/execution-workspaces.js", () => ({
executionWorkspaceService: vi.fn(() => ({
update: mockUpdateExecutionWorkspace,
getStoredById: mockGetStoredWorkspace,
})),
}));
vi.mock("../services/activity-log.js", () => ({
logActivity: mockLogActivity,
}));
vi.mock("../middleware/logger.js", () => ({
logger: {
info: mockLoggerInfo,
warn: vi.fn(),
error: vi.fn(),
debug: vi.fn(),
},
}));
// ---------------------------------------------------------------------------
// Imports after mocks
// ---------------------------------------------------------------------------
import {
environmentRunOrchestrator,
EnvironmentRunError,
} from "../services/environment-run-orchestrator.ts";
import type { Environment, EnvironmentLease, ExecutionWorkspace } from "@paperclipai/shared";
import type { RealizedExecutionWorkspace } from "../services/workspace-runtime.ts";
import type { EnvironmentRuntimeService } from "../services/environment-runtime.ts";
// ---------------------------------------------------------------------------
// Fixtures
// ---------------------------------------------------------------------------
function makeEnvironment(driver: string = "local"): Environment {
return {
id: "env-1",
name: "Test Environment",
description: null,
driver: driver as Environment["driver"],
status: "active",
config: {},
envVars: {},
metadata: null,
createdAt: new Date(),
updatedAt: new Date(),
};
}
function makeLease(overrides: Partial<EnvironmentLease> = {}): EnvironmentLease {
return {
id: "lease-1",
companyId: "company-1",
environmentId: "env-1",
executionWorkspaceId: null,
issueId: null,
heartbeatRunId: "run-1",
status: "active",
leasePolicy: "ephemeral",
provider: "local",
providerLeaseId: null,
acquiredAt: new Date(),
lastUsedAt: new Date(),
expiresAt: null,
releasedAt: null,
failureReason: null,
cleanupStatus: null,
metadata: null,
createdAt: new Date(),
updatedAt: new Date(),
...overrides,
};
}
function makeExecutionWorkspace(cwd: string = "/workspace/project"): RealizedExecutionWorkspace {
return {
baseCwd: "/workspace",
source: "project_primary",
projectId: "project-1",
workspaceId: "ws-1",
repoUrl: null,
repoRef: null,
strategy: "project_primary",
cwd,
branchName: null,
worktreePath: null,
branchCreatedByRuntime: false,
warnings: [],
created: false,
};
}
function makePersistedExecutionWorkspace(
overrides: Partial<ExecutionWorkspace> = {},
): ExecutionWorkspace {
return {
id: "ew-1",
companyId: "company-1",
projectId: "project-1",
projectWorkspaceId: null,
sourceIssueId: null,
mode: "shared_workspace",
strategyType: "project_primary",
name: "workspace",
status: "active",
cwd: "/workspace/project",
repoUrl: null,
baseRef: null,
branchName: null,
providerType: "local_fs",
providerRef: null,
derivedFromExecutionWorkspaceId: null,
lastUsedAt: new Date(),
openedAt: new Date(),
closedAt: null,
cleanupEligibleAt: null,
cleanupReason: null,
config: null,
metadata: null,
deliveryState: "unknown",
createdAt: new Date(),
updatedAt: new Date(),
...overrides,
};
}
function makeRealizeInput(overrides: {
environment?: Environment;
lease?: EnvironmentLease;
persistedExecutionWorkspace?: ExecutionWorkspace | null;
} = {}): Parameters<ReturnType<typeof environmentRunOrchestrator>["realizeForRun"]>[0] {
return {
environment: overrides.environment ?? makeEnvironment("local"),
lease: overrides.lease ?? makeLease(),
adapterType: "claude_local",
companyId: "company-1",
issueId: null,
heartbeatRunId: "run-1",
executionWorkspace: makeExecutionWorkspace(),
effectiveExecutionWorkspaceMode: null,
persistedExecutionWorkspace: overrides.persistedExecutionWorkspace !== undefined
? overrides.persistedExecutionWorkspace
: null,
};
}
function makeMockRuntime(overrides: Partial<EnvironmentRuntimeService> = {}): EnvironmentRuntimeService {
return {
acquireRunLease: vi.fn(),
resumeRunLease: vi.fn(async ({ lease, requestedCwd }: Parameters<EnvironmentRuntimeService["resumeRunLease"]>[0]) => ({
providerLeaseId: lease.providerLeaseId,
metadata: { ...lease.metadata, remoteCwd: requestedCwd ?? lease.metadata?.remoteCwd },
})),
releaseRunLeases: vi.fn(),
execute: vi.fn().mockResolvedValue({
exitCode: 0,
signal: null,
timedOut: false,
stdout: "",
stderr: "",
}),
realizeWorkspace: vi.fn().mockResolvedValue({
cwd: "/workspace/project",
metadata: {
workspaceRealization: {
version: 1,
mode: "copy",
authoritativeRoot: "/workspace/project",
pathAliases: [],
outboundRestorePaths: [],
driver: "local",
cwd: "/workspace/project",
},
},
}),
...overrides,
} as unknown as EnvironmentRuntimeService;
}
// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------
describe("environmentRunOrchestrator — realizeForRun", () => {
const mockDb = {} as any;
beforeEach(() => {
vi.clearAllMocks();
mockBuildWorkspaceRealizationRequest.mockReturnValue({
version: 1,
adapterType: "claude_local",
companyId: "company-1",
environmentId: "env-1",
executionWorkspaceId: null,
issueId: null,
heartbeatRunId: "run-1",
requestedMode: null,
source: {
kind: "project_primary",
localPath: "/workspace/project",
projectId: null,
projectWorkspaceId: null,
repoUrl: null,
repoRef: null,
strategy: "project_primary",
branchName: null,
worktreePath: null,
},
runtimeOverlay: {
provisionCommand: null,
},
});
mockAdapterExecutionTargetToRemoteSpec.mockReturnValue({
kind: "local",
environmentId: "env-1",
leaseId: "lease-1",
});
mockUpdateLeaseMetadata.mockResolvedValue(null);
mockUpdateExecutionWorkspace.mockResolvedValue(null);
mockLogActivity.mockResolvedValue(undefined);
});
it.each([false, true])("only requests active-work cancellation for explicit Stop: %s", async (cancelActiveWork) => {
const releaseRunLeases = vi.fn().mockResolvedValue([]);
const runtime = makeMockRuntime({ releaseRunLeases });
const orchestrator = environmentRunOrchestrator(mockDb, { environmentRuntime: runtime });
await orchestrator.releaseForRun({
heartbeatRunId: "run-1", companyId: "company-1", agentId: "agent-1",
providerResourceDisposition: "stop_and_retain", cancelActiveWork,
});
expect(releaseRunLeases).toHaveBeenCalledWith(
"run-1", "released", expect.any(Function), "stop_and_retain",
...(cancelActiveWork ? [true] : []),
);
});
it("realizes an enrolled remote workspace in place and persists its authoritative remote root", async () => {
const environment = { ...makeEnvironment("sandbox"), id: randomUUID() };
const lease = makeLease({ id: randomUUID(), environmentId: environment.id, provider: "daytona", providerLeaseId: "sandbox-allocation" });
const workspace = makePersistedExecutionWorkspace({ id: randomUUID(), providerType: "cloud_sandbox", metadata: {
remoteWorkspace: { schema: "paperclip.remote-workspace.v1", epoch: 1, source: { kind: "empty" },
writerRunId: randomUUID(), dirty: false, snapshotId: null, location: null },
} });
const remoteRoot = `/sandbox/.paperclip-workspaces/${workspace.id}`;
const savedLease = { ...lease, metadata: { remoteCwd: remoteRoot } };
const target = { kind: "remote", transport: "sandbox", remoteCwd: "/sandbox", runner: { execute: vi.fn() } };
mockResolveEnvironmentExecutionTarget.mockResolvedValue(target);
mockGetStoredWorkspace.mockResolvedValue({ ...workspace, cwd: remoteRoot });
mockUpdateLeaseMetadata.mockResolvedValue(savedLease);
const prepare = vi.spyOn(remoteWorkspaces, "prepareRemoteWorkspace").mockResolvedValue({
workspace, lease: savedLease, remoteRoot, reference: {} as never,
} as never);
try {
const runtime = makeMockRuntime();
const input = { ...makeRealizeInput({ environment, lease, persistedExecutionWorkspace: workspace }), adapterType: "paperclip_runner" };
const result = await environmentRunOrchestrator(mockDb, { environmentRuntime: runtime }).realizeForRun(input);
expect(prepare).toHaveBeenCalledWith(expect.objectContaining({ workspace, target, source: { kind: "empty" } }));
expect(mockBuildWorkspaceRealizationRequest).not.toHaveBeenCalled();
expect(runtime.realizeWorkspace).not.toHaveBeenCalled();
expect(result.executionTarget).toMatchObject({ remoteCwd: remoteRoot,
workspaceRealization: { mode: "in_place", authoritativeRoot: remoteRoot, outboundRestorePaths: [] } });
expect(input.executionWorkspace.cwd).toBe(remoteRoot);
expect(mockUpdateLeaseMetadata).toHaveBeenCalledWith(lease.id, expect.objectContaining({ remoteCwd: remoteRoot }));
expect(result.lease.metadata?.remoteCwd).toBe(remoteRoot);
} finally { prepare.mockRestore(); }
});
it("happy path: returns lease, executionTarget, and remoteExecution on successful realization", async () => {
const executionTarget = { kind: "local", environmentId: "env-1", leaseId: "lease-1" };
const remoteExecution = { kind: "local", environmentId: "env-1", leaseId: "lease-1" };
mockResolveEnvironmentExecutionTarget.mockResolvedValue(executionTarget);
mockAdapterExecutionTargetToRemoteSpec.mockReturnValue(remoteExecution);
const runtime = makeMockRuntime();
const orchestrator = environmentRunOrchestrator(mockDb, { environmentRuntime: runtime });
const result = await orchestrator.realizeForRun(makeRealizeInput());
expect(result.lease).toBeDefined();
expect(result.executionTarget).toEqual({
...executionTarget,
workspaceRealization: {
mode: "copy",
authoritativeRoot: "/workspace/project",
pathAliases: [],
outboundRestorePaths: [],
},
});
expect(result.remoteExecution).toEqual(remoteExecution);
expect(result.workspaceRealization).toEqual(
expect.objectContaining({ version: 1, driver: "local" }),
);
expect(runtime.realizeWorkspace).toHaveBeenCalledOnce();
expect(mockResolveEnvironmentExecutionTarget).toHaveBeenCalledOnce();
});
it("uses an in-place authoritative root on the adapter execution target", async () => {
mockResolveEnvironmentExecutionTarget.mockResolvedValue({
kind: "remote",
transport: "sandbox",
remoteCwd: "/copied/workspace",
});
const runtime = makeMockRuntime({
realizeWorkspace: vi.fn().mockResolvedValue({
cwd: "/app",
metadata: {
workspaceRealization: {
version: 1,
mode: "in_place",
authoritativeRoot: "/app",
pathAliases: [],
outboundRestorePaths: [],
},
},
}),
});
const orchestrator = environmentRunOrchestrator(mockDb, { environmentRuntime: runtime });
const result = await orchestrator.realizeForRun(
makeRealizeInput({ environment: makeEnvironment("sandbox") }),
);
expect(result.executionTarget).toEqual(expect.objectContaining({
kind: "remote",
transport: "sandbox",
remoteCwd: "/app",
workspaceRealization: {
mode: "in_place",
authoritativeRoot: "/app",
pathAliases: [],
outboundRestorePaths: [],
},
}));
});
it("realization failure: runtime.realizeWorkspace throws → EnvironmentRunError with code workspace_realization_failed", async () => {
const runtime = makeMockRuntime({
realizeWorkspace: vi.fn().mockRejectedValue(new Error("sandbox unreachable")),
});
const orchestrator = environmentRunOrchestrator(mockDb, { environmentRuntime: runtime });
await expect(orchestrator.realizeForRun(makeRealizeInput())).rejects.toSatisfy(
(err: unknown) =>
err instanceof EnvironmentRunError &&
err.code === "workspace_realization_failed" &&
err.environmentId === "env-1" &&
err.driver === "local",
);
expect(mockResolveEnvironmentExecutionTarget).not.toHaveBeenCalled();
});
it("target resolution failure: resolveEnvironmentExecutionTarget throws → EnvironmentRunError with code transport_resolution_failed", async () => {
mockResolveEnvironmentExecutionTarget.mockRejectedValue(new Error("network error"));
const runtime = makeMockRuntime();
const orchestrator = environmentRunOrchestrator(mockDb, { environmentRuntime: runtime });
await expect(orchestrator.realizeForRun(makeRealizeInput())).rejects.toSatisfy(
(err: unknown) =>
err instanceof EnvironmentRunError &&
err.code === "transport_resolution_failed" &&
err.environmentId === "env-1",
);
});
it("non-sandbox driver skips workspace realization and goes straight to target resolution", async () => {
const environment = makeEnvironment("plugin" as Environment["driver"]);
const executionTarget = null;
mockResolveEnvironmentExecutionTarget.mockResolvedValue(executionTarget);
const runtime = makeMockRuntime();
const orchestrator = environmentRunOrchestrator(mockDb, { environmentRuntime: runtime });
const result = await orchestrator.realizeForRun(
makeRealizeInput({ environment }),
);
expect(runtime.realizeWorkspace).not.toHaveBeenCalled();
expect(result.workspaceRealization).toEqual({});
expect(result.executionTarget).toBeNull();
});
it("forwards authorized project IDs to task-only providers without staging directories", async () => {
const environment = makeEnvironment("plugin" as Environment["driver"]);
mockResolveEnvironmentExecutionTarget.mockResolvedValue({ kind: "remote", transport: "sandbox",
providerKey: "plugin", remoteCwd: "provider/project", runnerTask: vi.fn() });
const runtime = makeMockRuntime({ supportsTasks: vi.fn().mockResolvedValue(true) });
const orchestrator = environmentRunOrchestrator(mockDb, { environmentRuntime: runtime });
const input = { ...makeRealizeInput({ environment }), adapterType: "paperclip_runner",
runnerTaskProjectIds: ["anchor", "authorized-reference", "anchor"] };
input.executionWorkspace.projectId = "anchor";
const result = await orchestrator.realizeForRun(input);
expect(result.executionTarget).toMatchObject({ runnerTaskProjectIds: ["anchor", "authorized-reference"] });
expect(runtime.realizeWorkspace).not.toHaveBeenCalled();
expect(runtime.execute).not.toHaveBeenCalled();
});
it("persisted metadata is updated on lease and execution workspace after realization", async () => {
const persistedExecutionWorkspace = makePersistedExecutionWorkspace();
const updatedLease = makeLease({
metadata: { workspaceRealization: { version: 1, driver: "local", cwd: "/workspace/project" } },
});
const updatedEw = { ...persistedExecutionWorkspace, metadata: { workspaceRealizationRequest: {}, workspaceRealization: {} } };
mockUpdateLeaseMetadata.mockResolvedValue(updatedLease);
mockUpdateExecutionWorkspace.mockResolvedValue(updatedEw);
mockResolveEnvironmentExecutionTarget.mockResolvedValue({ kind: "local", environmentId: "env-1", leaseId: "lease-1" });
const runtime = makeMockRuntime();
const orchestrator = environmentRunOrchestrator(mockDb, { environmentRuntime: runtime });
const result = await orchestrator.realizeForRun(
makeRealizeInput({ persistedExecutionWorkspace }),
);
// Lease metadata should have been updated with workspaceRealization
expect(mockUpdateLeaseMetadata).toHaveBeenCalledOnce();
expect(mockUpdateLeaseMetadata).toHaveBeenCalledWith(
"lease-1",
expect.objectContaining({ workspaceRealization: expect.any(Object) }),
);
// Execution workspace metadata should have been updated
expect(mockUpdateExecutionWorkspace).toHaveBeenCalledOnce();
expect(mockUpdateExecutionWorkspace).toHaveBeenCalledWith(
"ew-1",
expect.objectContaining({
metadata: expect.objectContaining({
workspaceRealizationRequest: expect.any(Object),
workspaceRealization: expect.any(Object),
}),
}),
);
// The returned lease should reflect the updated value
expect(result.lease).toEqual(updatedLease);
expect(result.persistedExecutionWorkspace).toEqual(updatedEw);
});
it("runs a remote provision command after workspace realization when configured", async () => {
mockBuildWorkspaceRealizationRequest.mockReturnValue({
version: 1,
adapterType: "claude_local",
companyId: "company-1",
environmentId: "env-1",
executionWorkspaceId: null,
issueId: null,
heartbeatRunId: "run-1",
requestedMode: null,
source: {
kind: "project_primary",
localPath: "/workspace/project",
projectId: null,
projectWorkspaceId: null,
repoUrl: null,
repoRef: null,
strategy: "project_primary",
branchName: null,
worktreePath: null,
},
runtimeOverlay: {
provisionCommand: "npm install -g @anthropic-ai/claude-code",
},
});
mockResolveEnvironmentExecutionTarget.mockResolvedValue({
kind: "remote",
transport: "ssh",
remoteCwd: "/remote/workspace",
environmentId: "env-1",
leaseId: "lease-1",
spec: {
host: "ssh.example.test",
port: 22,
username: "ssh-user",
remoteCwd: "/remote/workspace",
remoteWorkspacePath: "/remote/workspace",
privateKey: null,
knownHosts: null,
strictHostKeyChecking: true,
},
});
const runtime = makeMockRuntime({
realizeWorkspace: vi.fn().mockResolvedValue({
cwd: "/remote/workspace",
metadata: {
workspaceRealization: {
version: 1,
transport: "ssh",
remote: { path: "/remote/workspace" },
},
},
}),
});
const orchestrator = environmentRunOrchestrator(mockDb, { environmentRuntime: runtime });
await orchestrator.realizeForRun(makeRealizeInput({
environment: makeEnvironment("ssh"),
}));
// The `ssh` driver runs the command on the remote host that shares the
// workspace path, so the configured provision command still runs there.
expect(runtime.execute).toHaveBeenCalledOnce();
expect(runtime.execute).toHaveBeenCalledWith(expect.objectContaining({
environment: expect.objectContaining({ driver: "ssh" }),
lease: expect.objectContaining({ id: "lease-1" }),
command: "bash",
args: ["-lc", "npm install -g @anthropic-ai/claude-code"],
cwd: "/remote/workspace",
env: {
SHELL: "/bin/bash",
},
}));
// The sandbox skip log is specific to the sandbox driver; ssh stays quiet.
expect(mockLoggerInfo).not.toHaveBeenCalled();
});
it("skips the host provision command for a sandbox environment and logs the skip", async () => {
mockBuildWorkspaceRealizationRequest.mockReturnValue({
version: 1,
adapterType: "claude_local",
companyId: "company-1",
environmentId: "env-1",
executionWorkspaceId: null,
issueId: null,
heartbeatRunId: "run-1",
requestedMode: null,
source: {
kind: "project_primary",
localPath: "/workspace/project",
projectId: null,
projectWorkspaceId: null,
repoUrl: null,
repoRef: null,
strategy: "project_primary",
branchName: null,
worktreePath: null,
},
runtimeOverlay: {
provisionCommand: "npm install -g @anthropic-ai/claude-code",
},
});
mockResolveEnvironmentExecutionTarget.mockResolvedValue({
kind: "remote",
transport: "sandbox",
providerKey: "e2b",
remoteCwd: "/remote/workspace",
environmentId: "env-1",
leaseId: "lease-1",
});
const runtime = makeMockRuntime({
realizeWorkspace: vi.fn().mockResolvedValue({
cwd: "/remote/workspace",
metadata: {
workspaceRealization: {
version: 1,
transport: "sandbox",
remote: { path: "/remote/workspace" },
},
},
}),
});
const orchestrator = environmentRunOrchestrator(mockDb, { environmentRuntime: runtime });
await orchestrator.realizeForRun(makeRealizeInput({
environment: makeEnvironment("sandbox"),
}));
// The sandbox receives the provisioned tree through the adapter stage.sync
// step, so the orchestrator must not run the host command in the sandbox.
expect(runtime.execute).not.toHaveBeenCalled();
// The skip is observable: exactly one log line records it with the driver.
expect(mockLoggerInfo).toHaveBeenCalledOnce();
expect(mockLoggerInfo).toHaveBeenCalledWith(
expect.objectContaining({ driver: "sandbox", environmentId: "env-1" }),
expect.stringContaining("Skip host provisionCommand"),
);
});
it("does not rerun the provision command during local environment realization", async () => {
mockBuildWorkspaceRealizationRequest.mockReturnValue({
version: 1,
adapterType: "claude_local",
companyId: "company-1",
environmentId: "env-1",
executionWorkspaceId: null,
issueId: null,
heartbeatRunId: "run-1",
requestedMode: null,
source: {
kind: "project_primary",
localPath: "/workspace/project",
projectId: null,
projectWorkspaceId: null,
repoUrl: null,
repoRef: null,
strategy: "project_primary",
branchName: null,
worktreePath: null,
},
runtimeOverlay: {
provisionCommand: "npm install -g @anthropic-ai/claude-code",
},
});
mockResolveEnvironmentExecutionTarget.mockResolvedValue({
kind: "local",
environmentId: "env-1",
leaseId: "lease-1",
});
const runtime = makeMockRuntime();
const orchestrator = environmentRunOrchestrator(mockDb, { environmentRuntime: runtime });
await orchestrator.realizeForRun(makeRealizeInput({
environment: makeEnvironment("local"),
}));
// Local workspace provisioning already ran the command before realizeForRun.
expect(runtime.execute).not.toHaveBeenCalled();
// The sandbox skip log is specific to the sandbox driver; local stays quiet.
expect(mockLoggerInfo).not.toHaveBeenCalled();
});
it("runs project-level provision commands for ssh environments", async () => {
mockBuildWorkspaceRealizationRequest.mockReturnValue({
version: 1,
adapterType: "gemini_local",
companyId: "company-1",
environmentId: "env-1",
executionWorkspaceId: null,
issueId: null,
heartbeatRunId: "run-1",
requestedMode: null,
source: {
kind: "project_primary",
localPath: "/workspace/project",
projectId: null,
projectWorkspaceId: null,
repoUrl: null,
repoRef: null,
strategy: "project_primary",
branchName: null,
worktreePath: null,
},
runtimeOverlay: {
provisionCommand: "npm install -g @google/gemini-cli",
},
});
mockResolveEnvironmentExecutionTarget.mockResolvedValue({
kind: "remote",
transport: "ssh",
remoteCwd: "/remote/workspace",
environmentId: "env-1",
leaseId: "lease-1",
spec: {
host: "ssh.example.test",
port: 22,
username: "ssh-user",
remoteCwd: "/remote/workspace",
remoteWorkspacePath: "/remote/workspace",
privateKey: null,
knownHosts: null,
strictHostKeyChecking: true,
},
});
const runtime = makeMockRuntime({
realizeWorkspace: vi.fn().mockResolvedValue({
cwd: "/remote/workspace",
metadata: {
workspaceRealization: {
version: 1,
transport: "ssh",
remote: { path: "/remote/workspace" },
},
},
}),
});
const orchestrator = environmentRunOrchestrator(mockDb, { environmentRuntime: runtime });
await orchestrator.realizeForRun(makeRealizeInput({
environment: makeEnvironment("ssh"),
lease: makeLease({
provider: "ssh",
metadata: {
driver: "ssh",
remoteCwd: "/remote/workspace",
remoteWorkspacePath: "/remote/workspace",
host: "ssh.example.test",
port: 22,
username: "ssh-user",
},
}),
}));
expect(runtime.execute).toHaveBeenCalledWith(expect.objectContaining({
command: "bash",
args: ["-lc", "npm install -g @google/gemini-cli"],
}));
expect(mockResolveEnvironmentExecutionTarget).toHaveBeenCalledOnce();
});
it("surfaces remote provision command failures before resolving the adapter target", async () => {
mockBuildWorkspaceRealizationRequest.mockReturnValue({
version: 1,
adapterType: "claude_local",
companyId: "company-1",
environmentId: "env-1",
executionWorkspaceId: null,
issueId: null,
heartbeatRunId: "run-1",
requestedMode: null,
source: {
kind: "project_primary",
localPath: "/workspace/project",
projectId: null,
projectWorkspaceId: null,
repoUrl: null,
repoRef: null,
strategy: "project_primary",
branchName: null,
worktreePath: null,
},
runtimeOverlay: {
provisionCommand: "install-tool",
},
});
const runtime = makeMockRuntime({
execute: vi.fn().mockResolvedValue({
exitCode: 127,
signal: null,
timedOut: false,
stdout: "",
stderr: "/bin/sh: install-tool: not found\n",
}),
});
const orchestrator = environmentRunOrchestrator(mockDb, { environmentRuntime: runtime });
await expect(orchestrator.realizeForRun(makeRealizeInput({
environment: makeEnvironment("ssh"),
}))).rejects.toSatisfy(
(err: unknown) =>
err instanceof EnvironmentRunError &&
err.code === "workspace_realization_failed" &&
String(err.message).includes("install-tool: not found"),
);
expect(mockResolveEnvironmentExecutionTarget).not.toHaveBeenCalled();
});
});
describe("native runner lifecycle changes before lease acquisition", () => {
it.each([undefined, "account:generation"])("uses a warm plugin lease only with a managed credential identity: %s", async managedAiCredentialIdentity => {
const environment = { ...makeEnvironment("plugin"), config: { pluginKey: "fixture", driverKey: "tasks", driverConfig: {} } };
mockGetEnvironment.mockResolvedValue(environment);
const acquireRunLease = vi.fn(async input => ({ environment: input.environment,
lease: makeLease({ leasePolicy: input.environment.config.runnerLifecycleMode === "warm" ? "reuse_by_environment" : "ephemeral" }), leaseContext: { executionWorkspaceId: null, executionWorkspaceMode: null } }));
const orchestrator = environmentRunOrchestrator({} as never, { environmentRuntime: makeMockRuntime({ acquireRunLease }) });
const result = await orchestrator.acquireForRun({ companyId: "company-1", selectedEnvironmentId: "env-1", localEnvironmentId: "local",
adapterType: "paperclip_runner", adapterConfig: { lifecycleMode: "warm" }, managedAiCredentialIdentity,
issueId: "task-1", heartbeatRunId: "run-1", agentId: "agent-1", persistedExecutionWorkspace: null, executionWorkspaceSettings: null });
expect(result.environment.config.runnerLifecycleMode).toBe(managedAiCredentialIdentity ? "warm" : "per_turn");
expect(result.lease.leasePolicy).toBe(managedAiCredentialIdentity ? "reuse_by_environment" : "ephemeral");
expect(environment.config).not.toHaveProperty("runnerLifecycleMode");
});
it("requests a reusable lease when an existing task switches from per-turn to warm", async () => {
const environment = { ...makeEnvironment("sandbox"), config: { provider: "daytona", reuseLease: false } };
mockGetEnvironment.mockResolvedValue(environment);
const acquireRunLease = vi.fn(async (input) => ({
environment: input.environment,
lease: makeLease({ leasePolicy: input.environment.config.reuseLease ? "reuse_by_environment" : "ephemeral" }),
leaseContext: { executionWorkspaceId: "existing-workspace", executionWorkspaceMode: "shared_workspace" as const },
}));
const orchestrator = environmentRunOrchestrator({} as never, { environmentRuntime: makeMockRuntime({ acquireRunLease }) });
const input = {
companyId: "company-1", selectedEnvironmentId: "env-1", localEnvironmentId: "local",
adapterType: "paperclip_runner", issueId: "existing-task", heartbeatRunId: "run-1", agentId: "agent-1",
runnerTaskProjectIds:["anchor","authorized-reference"],
persistedExecutionWorkspace: { id: "existing-workspace", mode: "shared_workspace" as const },
executionWorkspaceSettings: null,
};
const cold = await orchestrator.acquireForRun({ ...input, adapterConfig: { lifecycleMode: "per_turn" } });
expect(cold.lease.leasePolicy).toBe("ephemeral");
const warm = await orchestrator.acquireForRun({ ...input, heartbeatRunId: "run-2", adapterConfig: { lifecycleMode: "warm", idleTimeoutMs: 60_000 } });
expect(warm.lease.leasePolicy).toBe("reuse_by_environment");
expect(warm.environment.config).toEqual({ provider: "daytona", reuseLease: true });
expect(acquireRunLease.mock.calls[1][0].persistedExecutionWorkspace).toEqual(input.persistedExecutionWorkspace);
expect(acquireRunLease.mock.calls[1][0].configuredEnvironment).toBe(environment);
expect(acquireRunLease.mock.calls[1][0].runnerTaskProjectIds).toEqual(input.runnerTaskProjectIds);
// Run-scoped adaptation must not change the shared environment or other agents.
expect(environment.config).toEqual({ provider: "daytona", reuseLease: false });
const coldAgain = await orchestrator.acquireForRun({ ...input, heartbeatRunId: "run-3", adapterConfig: { lifecycleMode: "per_turn" } });
expect(coldAgain.lease.leasePolicy).toBe("ephemeral");
});
it.each([
["paperclip_runner", undefined, undefined, false],
["paperclip_runner", "inherit", "per_turn", false],
["paperclip_runner", "per_turn", "warm", false],
["paperclip_runner", "warm", "per_turn", true],
["paperclip_runner", "inherit", "warm", true],
["codex_local", "inherit", "warm", false],
])("respects environment precedence and adapter scope (%s, %s, %s)", async (adapterType, runnerLifecycleMode, lifecycleMode, expectedReuse) => {
const environment = { ...makeEnvironment("sandbox"), config: { provider: "daytona", reuseLease: false, runnerLifecycleMode } };
mockGetEnvironment.mockResolvedValue(environment);
const acquireRunLease = vi.fn(async (input) => ({ environment: input.environment, lease: makeLease(), leaseContext: { executionWorkspaceId: null, executionWorkspaceMode: null } }));
const orchestrator = environmentRunOrchestrator({} as never, { environmentRuntime: makeMockRuntime({ acquireRunLease }) });
await orchestrator.acquireForRun({
companyId: "company-1", selectedEnvironmentId: "env-1", localEnvironmentId: "local", adapterType,
adapterConfig: { lifecycleMode }, issueId: "task-1", heartbeatRunId: "run-1", agentId: "agent-1",
persistedExecutionWorkspace: null, executionWorkspaceSettings: null,
});
expect(acquireRunLease.mock.calls[0][0].environment.config.reuseLease).toBe(expectedReuse);
});
});
describe("admitted native lifecycle recovery", () => {
it.each([
["warm", "per_turn", "per_turn", true],
["per_turn", "warm", "warm", false],
] as const)("preserves admitted %s over changed agent/environment settings", async (admittedLifecycleMode, lifecycleMode, runnerLifecycleMode, expectedReuse) => {
const environment = { ...makeEnvironment("sandbox"), config: { provider: "daytona", reuseLease: false, runnerLifecycleMode } };
mockGetEnvironment.mockResolvedValue(environment);
const acquireRunLease = vi.fn(async (input) => ({ environment: input.environment, lease: makeLease(), leaseContext: { executionWorkspaceId: null, executionWorkspaceMode: null } }));
const orchestrator = environmentRunOrchestrator({} as never, { environmentRuntime: makeMockRuntime({ acquireRunLease }) });
await orchestrator.acquireForRun({
companyId: "company-1", selectedEnvironmentId: "env-1", localEnvironmentId: "local",
adapterType: "paperclip_runner", adapterConfig: { lifecycleMode }, admittedLifecycleMode,
issueId: "task-1", heartbeatRunId: "run-1", agentId: "agent-1",
persistedExecutionWorkspace: null, executionWorkspaceSettings: null,
});
expect(acquireRunLease.mock.calls[0][0].environment.config.reuseLease).toBe(expectedReuse);
expect(environment.config.reuseLease).toBe(false);
});
});
describe("live remote runner lease reattachment", () => {
const input = {
companyId: "company-1", selectedEnvironmentId: "env-1", localEnvironmentId: "local",
adapterType: "paperclip_runner", admittedLifecycleMode: "per_turn" as const,
issueId: "task-1", heartbeatRunId: "run-1", agentId: "agent-1",
persistedExecutionWorkspace: { id: "ew-1", mode: "shared_workspace" as const },
executionWorkspaceSettings: null,
reattachRemoteLease: { leaseId: "lease-1", providerLeaseId: "original-sandbox", remoteCwd: "/remote/workspace" },
};
const originalLease = () => makeLease({
provider: "daytona", providerLeaseId: "original-sandbox", issueId: "task-1", executionWorkspaceId: "ew-1",
metadata: { agentId: "agent-1", remoteCwd: "/remote/workspace",
sandboxLeaseAcquisition: { outcome: "created", providerLeaseId: "original-sandbox" } },
});
beforeEach(() => {
vi.clearAllMocks();
mockGetEnvironment.mockResolvedValue({ ...makeEnvironment("sandbox"), config: { provider: "daytona", reuseLease: false } });
mockGetLease.mockResolvedValue(originalLease());
});
it("readies the original active ephemeral lease without acquiring a replacement", async () => {
const savedLease = originalLease();
mockGetLease.mockResolvedValue(savedLease);
const runtime = makeMockRuntime();
const result = await environmentRunOrchestrator({} as never, { environmentRuntime: runtime }).acquireForRun(input);
expect(mockGetLease).toHaveBeenCalledWith("lease-1");
expect(runtime.acquireRunLease).not.toHaveBeenCalled();
expect(runtime.resumeRunLease).toHaveBeenCalledWith({
environment: result.environment, lease: savedLease, requestedCwd: "/remote/workspace",
});
expect(result.lease).toEqual(savedLease);
expect(result.leaseContext.executionWorkspaceId).toBe("ew-1");
expect(result.environment.config.reuseLease).toBe(false);
});
it("forwards the admitted warm idle window to the recovery resume", async () => {
const savedLease = originalLease();
mockGetLease.mockResolvedValue(savedLease);
const runtime = makeMockRuntime();
const result = await environmentRunOrchestrator({} as never, { environmentRuntime: runtime }).acquireForRun({
...input, admittedLifecycleMode: "warm", adapterConfig: { lifecycleMode: "warm", idleTimeoutMs: 1_800_000 },
});
expect(runtime.resumeRunLease).toHaveBeenCalledWith({
environment: result.environment, lease: savedLease, requestedCwd: "/remote/workspace",
warmRunnerIdleTimeoutMs: 1_800_000,
});
expect(runtime.acquireRunLease).not.toHaveBeenCalled();
});
it("does not return the retained sandbox to preparation while its lifecycle resume is pending", async () => {
let ready = false;
let release!: () => void;
const gate = new Promise<void>(resolve => { release = resolve; });
const savedLease = originalLease();
const before = structuredClone(savedLease);
mockGetLease.mockResolvedValue(savedLease);
const runtime = makeMockRuntime({ resumeRunLease: vi.fn(async () => {
await gate;
ready = true;
return { providerLeaseId: "original-sandbox", metadata: { remoteCwd: "/remote/workspace" } };
}) });
const pending = environmentRunOrchestrator({} as never, { environmentRuntime: runtime }).acquireForRun(input);
try {
const state = await Promise.race([
pending.then(() => "returned"),
new Promise<string>(resolve => setImmediate(() => resolve("pending"))),
]);
expect(state).toBe("pending");
expect(ready).toBe(false);
} finally { release(); }
expect((await pending).lease).toEqual(before);
expect(ready).toBe(true);
expect(runtime.acquireRunLease).not.toHaveBeenCalled();
expect(runtime.execute).not.toHaveBeenCalled();
expect(mockUpdateLeaseMetadata).not.toHaveBeenCalled();
});
it.each([
null,
{ providerLeaseId: "replacement", metadata: { remoteCwd: "/remote/workspace" } },
{ providerLeaseId: "original-sandbox", metadata: { expired: true } },
])("holds recovery when the retained lease resume cannot confirm its original allocation (%j)", async receipt => {
const runtime = makeMockRuntime({ resumeRunLease: vi.fn(async () => receipt) });
await expect(environmentRunOrchestrator({} as never, { environmentRuntime: runtime }).acquireForRun(input))
.rejects.toThrow("native_remote_recovery_lease_mismatch");
expect(runtime.acquireRunLease).not.toHaveBeenCalled();
expect(runtime.execute).not.toHaveBeenCalled();
expect(mockUpdateLeaseMetadata).not.toHaveBeenCalled();
});
it.each([undefined, { remoteCwd: "/allocation/root" }])(
"keeps the recorded workspace when resume returns allocation metadata (%j)", async metadata => {
const savedLease = originalLease();
mockGetLease.mockResolvedValue(savedLease);
const runtime = makeMockRuntime({ resumeRunLease: vi.fn(async () => ({
providerLeaseId: "original-sandbox", metadata,
})) });
const result = await environmentRunOrchestrator({} as never, { environmentRuntime: runtime }).acquireForRun(input);
expect(result.lease).toBe(savedLease);
expect(result.lease.metadata?.remoteCwd).toBe("/remote/workspace");
expect(runtime.resumeRunLease).toHaveBeenCalledOnce();
expect(runtime.acquireRunLease).not.toHaveBeenCalled();
expect(mockUpdateLeaseMetadata).not.toHaveBeenCalled();
},
);
it("preserves a retained sandbox startup failure without acquiring a replacement", async () => {
const error = new Error("retained sandbox startup timed out");
const runtime = makeMockRuntime({ resumeRunLease: vi.fn(async () => { throw error; }) });
await expect(environmentRunOrchestrator({} as never, { environmentRuntime: runtime }).acquireForRun(input))
.rejects.toBe(error);
expect(runtime.acquireRunLease).not.toHaveBeenCalled();
expect(runtime.execute).not.toHaveBeenCalled();
});
it.each(["active", "foreign-company", "revoked", "undispatched", "stopped"])("inspects the original plugin task lease (%s)", async state => {
mockGetEnvironment.mockResolvedValue(makeEnvironment("plugin"));
const lease = originalLease();
lease.metadata = { agentId: "agent-1", runnerTaskLease: true,
runnerTaskDispatched: state !== "undispatched", runnerTaskExecutionStopped: state === "stopped" };
if (state === "foreign-company") lease.companyId = "foreign";
if (state === "revoked") lease.status = "released";
mockGetLease.mockResolvedValue(lease);
const runtime = makeMockRuntime();
const acquired = environmentRunOrchestrator({} as never, { environmentRuntime: runtime }).acquireForRun({
...input, reattachRemoteLease: { kind: "plugin_task", leaseId: lease.id },
});
if (state === "active") await expect(acquired).resolves.toMatchObject({ lease });
else await expect(acquired).rejects.toThrow("native_remote_recovery_lease_mismatch");
expect(runtime.acquireRunLease).not.toHaveBeenCalled();
expect(runtime.resumeRunLease).not.toHaveBeenCalled();
});
it.each([
{ companyId: "foreign" }, { environmentId: "foreign" }, { heartbeatRunId: "another-run" },
{ issueId: "another-task" }, { executionWorkspaceId: "another-workspace" },
{ providerLeaseId: "replacement" }, { provider: "another-provider" },
{ status: "released" }, { expiresAt: new Date(0) }, { releasedAt: new Date() }, { cleanupStatus: "pending" },
{ metadata: { agentId: "another-agent", remoteCwd: "/remote/workspace" } },
{ metadata: { agentId: "agent-1", remoteCwd: "/other/workspace" } },
] as Partial<EnvironmentLease>[])("fails closed before provider acquisition for a mismatched lease %j", async (override) => {
mockGetLease.mockResolvedValue({ ...originalLease(), ...override });
const runtime = makeMockRuntime();
await expect(environmentRunOrchestrator({} as never, { environmentRuntime: runtime }).acquireForRun(input))
.rejects.toThrow("native_remote_recovery_lease_mismatch");
expect(runtime.acquireRunLease).not.toHaveBeenCalled();
expect(runtime.resumeRunLease).not.toHaveBeenCalled();
});
it("never replaces a missing original lease", async () => {
mockGetLease.mockResolvedValue(null);
const runtime = makeMockRuntime();
await expect(environmentRunOrchestrator({} as never, { environmentRuntime: runtime }).acquireForRun(input))
.rejects.toThrow("native_remote_recovery_lease_mismatch");
expect(runtime.acquireRunLease).not.toHaveBeenCalled();
});
});