mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-26 02:00:52 +01:00
## Thinking Path > - Paperclip manages agents and their execution authority. > - A plugin can host Runner tasks on a persistent execution host. > - Admission checks must observe current lifecycle state and credentials. > - The current runtime reader also resolves files, skills, and connections for each check. > - Some providers also repeat the same project authentication check during submit. > - This pull request adds a compact fresh reader and an explicit submit-time validation contract. > - Providers can remove repeated work while Core keeps lease and scope checks. ## Linked Issues or Issue Description **What existing behavior does this improve?** Plugin Runner startup before task submission. **Current behavior** One measured startup made 30 full runtime reads during bootstrap. These reads took 5.759 seconds. Each guard can resolve instructions, skills, and connection assignments even when it only needs current identity and lifecycle state. Providers that already enforce project authentication in submit must also perform a separate Core preflight. **Proposed behavior** Use a lifecycle-owned runtime identity reader for guards. Keep the full runtime reader for synchronization inputs. A trusted provider can declare `projectAuthCheckAtSubmit: true` in its prepared result. Core pins the exact configuration, provider, and project set on the lease. The provider must reject authentication conflicts before credential installation or task start. Providers that omit the flag retain the existing preflight. **Reason and benefit** Estimated savings with a paired consumer: 4–5 seconds from compact runtime checks, plus roughly 0.5–0.9 seconds from removing the duplicate authentication operation. These estimates overlap and are not a measured deployed result. Refs #17367 for the separate run-log HEAD optimization; this PR does not duplicate it. **Breaking changes** No migration. The SDK adds a host method and an optional prepared-result field. Consumers that use them require this Core host version. Older providers retain their existing flow. ## What Changed - Add `agents.getRuntimeIdentity` across SDK, RPC, host service, and test fixtures. - Reuse the existing company, plugin ownership, readiness, and capability checks. Read lifecycle state and protected instance secrets fresh on every call. - Skip instruction, organization, skill, and connection hydration in the compact reader. - Allow explicit submit-time authentication enforcement, with prepared scope pinned and checked again at dispatch. - Emit fixed preparation, project-auth, submit, and connection timing phases. ## Verification - 72 targeted SDK capability, plugin task-owner, and environment-task mediator tests passed. - 33 real embedded-Postgres lifecycle and secret tests passed, including pause, content revision, company isolation, plugin disablement, and secret rotation. - SDK and server TypeScript checks passed. Local Runner/eval TypeScript outputs were built to satisfy server type resolution. - Local Postgres required missing library aliases in ignored dependencies; no product source was changed for that repair. - Full repository suites are delegated to GitHub CI. No live rollout or speedup claim. ## Risks The submit-time flag delegates enforcement to a trusted provider. It is not proof that prepare already checked authentication. The provider must check before VM credential installation or task admission. Core still checks exact prepared scope, active lease, run identity, credential type, and dispatch fencing. Existing providers keep the separate check. Roll back a paired consumer before rolling back this host. ## Model Used OpenAI GPT-6 (Codex), with code execution and repository tools. Exact deployment build 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 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 described the issue in-PR following the relevant issue template - [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 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 Co-authored-by: Paperclip <noreply@paperclip.ing>
372 lines
31 KiB
TypeScript
372 lines
31 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
import { PgDialect } from "drizzle-orm/pg-core";
|
|
import { executionWorkspaces, type Db } from "@paperclipai/db";
|
|
import { environmentTaskOperationSchema, parseEnvironmentTaskResult } from "@paperclipai/plugin-sdk";
|
|
import { executeEnvironmentTask } from "../services/environment-task-runtime.js";
|
|
|
|
const state = vi.hoisted(() => ({ plugin: {} as any, release: vi.fn() }));
|
|
vi.mock("../services/provider-fresh-workspace.js", () => ({
|
|
stageProviderFreshWorkspace: async (input: { existing: unknown }) => input.existing,
|
|
releaseProviderRecoveryTransfers: state.release,
|
|
}));
|
|
vi.mock("../services/plugin-registry.js", () => ({ pluginRegistryService: () => ({ getById: vi.fn(async () => state.plugin) }) }));
|
|
const leaseId = "10000000-0000-4000-8000-000000000001";
|
|
const projectId = "10000000-0000-4000-8000-000000000002";
|
|
const secondProjectId = "10000000-0000-4000-8000-000000000003";
|
|
const row = () => ({
|
|
lease: { id: leaseId, companyId: "company", environmentId: "environment", providerLeaseId: "attempt-1", heartbeatRunId: "run", issueId: "issue", status: "active", expiresAt: null as Date | null,
|
|
metadata: { driver: "plugin", pluginId: "original", driverKey: "tasks", runnerTaskProjectAuthValidation: { configurationId: "configuration", provider: "openai", projectIds: [projectId] } } },
|
|
environment: { id: "environment", config: { pluginKey: "test.provider", driverKey: "tasks", driverConfig: {} } } as { id: string; config: Record<string, unknown> } | null,
|
|
run: { id: "run", agentId: "agent", status: "running" } as { id: string; agentId: string; status: string } | null,
|
|
});
|
|
function database(value: ReturnType<typeof row> | null = row(), projectRows = [{ id: projectId }], workspaceRows: unknown[] = []) {
|
|
let selectedRows: unknown[] = projectRows;
|
|
const results = [value ? [value] : [], [{ id: "issue" }], value ? [value] : []];
|
|
const query: any = { from: (table: unknown) => { selectedRows = table === executionWorkspaces ? workspaceRows : projectRows; return query; }, leftJoin: () => query, where: vi.fn(() => query), limit: () => Promise.resolve(results.shift()), then: (resolve: (rows: typeof projectRows) => unknown) => Promise.resolve(selectedRows).then(resolve as (rows: unknown[]) => unknown) };
|
|
const sets: Record<string, unknown>[] = [];
|
|
const update: any = { set: (value: Record<string, unknown>) => { sets.push(value); return update; }, where: () => update,
|
|
returning: async () => [{ id: leaseId }], then: (resolve: (value: unknown) => unknown) => Promise.resolve([]).then(resolve) };
|
|
return { db: { select: () => query, update: () => update } as unknown as Db, query, sets };
|
|
}
|
|
function worker(result: unknown = { kind: "accepted", taskId: "attempt-1" }) {
|
|
return { getWorker: vi.fn(() => ({ supportedMethods: ["environmentTask"] })), call: vi.fn(async () => result) };
|
|
}
|
|
const submit = { kind: "submit" as const, preparedLaunchId: "configuration", projectIds: [projectId], runner: { protocolMin: 1, protocolMax: 2, harness: "codex", runnerId: "runner", leaseId, runId: "run", sessionId: "session", turnId: "turn", itemId: "item" }, bootstrapTicket: "transient-test-ticket" };
|
|
|
|
beforeEach(() => {
|
|
state.release.mockReset();
|
|
state.plugin = { id: "original", pluginKey: "test.provider", status: "ready", manifestJson: { capabilities: ["environment.drivers.register"], environmentDrivers: [{ driverKey: "tasks", supportsTasks: true }] } };
|
|
});
|
|
describe("plugin-provided Runner execution", () => {
|
|
it("forwards a retained fresh directory intent on later launch calls and checks provider support", async () => {
|
|
const value = row();
|
|
value.lease.metadata.runnerTaskProjectAuthValidation.projectIds = [];
|
|
const fresh = { attemptId: leaseId, sourceRunId: secondProjectId, previousWorkspaceId: null, recoverySnapshots: [] };
|
|
Object.assign(value.lease.metadata, { freshWorkspace: fresh });
|
|
Object.assign(state.plugin.manifestJson.environmentDrivers[0], { freshWorkspaceVersion: 1 });
|
|
const workers = worker();
|
|
await executeEnvironmentTask(database(value, []).db, workers as never, { companyId: "company", leaseId, operation: { ...submit, projectIds: [] } });
|
|
expect(workers.call).toHaveBeenCalledWith("original", "environmentTask", expect.objectContaining({ fresh }), 15_000);
|
|
delete state.plugin.manifestJson.environmentDrivers[0].freshWorkspaceVersion;
|
|
const denied = worker();
|
|
await expect(executeEnvironmentTask(database(value, []).db, denied as never, { companyId: "company", leaseId,
|
|
operation: { ...submit, projectIds: [] } })).rejects.toThrow("fresh workspace preparation unavailable");
|
|
expect(denied.call).not.toHaveBeenCalled();
|
|
});
|
|
it("releases fresh transfer copies only after confirmed task completion", async () => {
|
|
const value = row();
|
|
const fresh = { attemptId: leaseId, sourceRunId: secondProjectId, previousWorkspaceId: null, recoverySnapshots: [] };
|
|
Object.assign(value.lease.metadata, { freshWorkspace: fresh });
|
|
const workers = worker();
|
|
await executeEnvironmentTask(database(value).db, workers as never, { companyId: "company", leaseId, operation: { kind: "complete" } });
|
|
expect(state.release).not.toHaveBeenCalled();
|
|
Object.assign(value.lease.metadata, { runnerTaskExecutionStopped: true });
|
|
await executeEnvironmentTask(database(value).db, workers as never, { companyId: "company", leaseId, operation: { kind: "complete" } });
|
|
expect(state.release).toHaveBeenCalledWith("company", fresh);
|
|
});
|
|
it("passes only the workspace binding pinned to this lease and provider", async () => {
|
|
const value = row(); value.lease.metadata.runnerTaskProjectAuthValidation.projectIds = [];
|
|
const binding = { pluginId: leaseId, projectId, executionWorkspaceId: secondProjectId, projectWorkspaceId: projectId,
|
|
sourceRevision: "revision", workspaceRef: "provider:checkout" };
|
|
state.plugin.id = binding.pluginId;
|
|
Object.assign(value.lease, { executionWorkspaceId: binding.executionWorkspaceId });
|
|
Object.assign(value.lease.metadata, { pluginId: binding.pluginId, workspaceProvider: binding });
|
|
const workspace = { id: binding.executionWorkspaceId, companyId: "company", providerType: "plugin", providerRef: binding.workspaceRef,
|
|
status: "active", metadata: { workspaceProvider: binding } };
|
|
const workers = worker();
|
|
await executeEnvironmentTask(database(value, [], [workspace]).db, workers as never, { companyId: "company", leaseId, operation: { ...submit, projectIds: [] } });
|
|
expect(workers.call).toHaveBeenCalledWith(binding.pluginId, "environmentTask", expect.objectContaining({ workspace: binding }), 15_000);
|
|
const denied = worker(); workspace.providerRef = "foreign";
|
|
await expect(executeEnvironmentTask(database(value, [], [workspace]).db, denied as never, { companyId: "company", leaseId, operation: { ...submit, projectIds: [] } })).rejects.toThrow("binding changed");
|
|
expect(denied.call).not.toHaveBeenCalled();
|
|
});
|
|
it("accepts artifact identity without arbitrary filesystem or execution settings", () => {
|
|
const operation = environmentTaskOperationSchema.parse({ kind: "describe" });
|
|
const description = { kind: "description", taskId: "attempt-1", runner: { version: "fixture", digest: `sha256:${"a".repeat(64)}` } };
|
|
expect(parseEnvironmentTaskResult(operation, "attempt-1", description)).toEqual(description);
|
|
for (const field of ["resources", "environment", "connection", "codexCommand"]) {
|
|
expect(() => parseEnvironmentTaskResult(operation, "attempt-1", { ...description, [field]: {} })).toThrow();
|
|
}
|
|
for (const kind of ["checkpoint", "read_file"]) {
|
|
expect(environmentTaskOperationSchema.safeParse({ kind }).success).toBe(false);
|
|
}
|
|
});
|
|
it("bounds file reads to the active task identity and validated project scope", async () => {
|
|
const value=row();
|
|
const identity={runnerInstanceId:"runner",environmentLeaseId:leaseId,runId:"run",normalizedSessionId:"session",turnId:"turn",itemId:"item"};
|
|
Object.assign(value.lease.metadata,{runnerTaskIdentity:{...submit.runner}});
|
|
const operation={kind:"read_file" as const,identity,projectIds:[projectId],path:"proof.txt",maxBytes:1024};
|
|
const result={kind:"file",taskId:"attempt-1",base64:"",byteSize:0,sha256:"a".repeat(64)};
|
|
const workers=worker(result);
|
|
await expect(executeEnvironmentTask(database(value).db,workers as never,{companyId:"company",leaseId,operation})).resolves.toEqual(result);
|
|
workers.call.mockClear();
|
|
for(const invalid of [{...operation,projectIds:[]},{...operation,identity:{...identity,itemId:"foreign"}}]) {
|
|
await expect(executeEnvironmentTask(database(value).db,workers as never,{companyId:"company",leaseId,operation:invalid})).rejects.toThrow();
|
|
}
|
|
value.lease.expiresAt=new Date(0);
|
|
await expect(executeEnvironmentTask(database(value).db,workers as never,{companyId:"company",leaseId,operation})).rejects.toThrow("not active");
|
|
expect(workers.call).not.toHaveBeenCalled();
|
|
value.lease.expiresAt=null;
|
|
workers.call.mockImplementation(async()=>{value.run!.status="cancelled";return result;});
|
|
await expect(executeEnvironmentTask(database(value).db,workers as never,{companyId:"company",leaseId,operation})).rejects.toThrow("operation unavailable");
|
|
for(const path of ["//etc/passwd","../escape","a/../b","a\\b","https://example.test/file"]) expect(environmentTaskOperationSchema.safeParse({...operation,path}).success).toBe(false);
|
|
expect(environmentTaskOperationSchema.safeParse({...operation,maxBytes:11*1024*1024}).success).toBe(false);
|
|
expect(()=>parseEnvironmentTaskResult(operation,"attempt-1",{...result,taskId:"other"})).toThrow();
|
|
});
|
|
it("reads only the current task's saved identity and rejects inactive checkpoint operations", async () => {
|
|
const value = row();
|
|
const identity = { runnerInstanceId: "runner", environmentLeaseId: "logical-lease", runId: "run",
|
|
normalizedSessionId: "session", turnId: "turn", itemId: "item" };
|
|
Object.assign(value.lease.metadata, { runnerTaskIdentity: { ...submit.runner, leaseId: identity.environmentLeaseId } });
|
|
const operation = { kind: "read_state" as const, identity };
|
|
const result = { kind: "state", taskId: "attempt-1", state: { schema: "paperclip.runner.durable.state.v1", ...identity, lifecycle: "suspended" } };
|
|
const workers = worker(result);
|
|
await expect(executeEnvironmentTask(database(value).db, workers as never, { companyId: "company", leaseId, operation })).resolves.toEqual(result);
|
|
expect(workers.call).toHaveBeenCalledOnce(); workers.call.mockClear();
|
|
await expect(executeEnvironmentTask(database(value).db, workers as never, { companyId: "company", leaseId,
|
|
operation: { ...operation, identity: { ...identity, itemId: "different" } } })).rejects.toThrow("identity mismatch");
|
|
value.lease.status = "released";
|
|
await expect(executeEnvironmentTask(database(value).db, workers as never, { companyId: "company", leaseId, operation })).rejects.toThrow("not active");
|
|
await expect(executeEnvironmentTask(database(value).db, workers as never, { companyId: "company", leaseId,
|
|
operation: { kind: "archive_state", priorIdentity: identity, archiveKey: "a".repeat(24), resumeFrom: "prior" } })).rejects.toThrow("not active");
|
|
expect(workers.call).not.toHaveBeenCalled();
|
|
expect(() => parseEnvironmentTaskResult(operation, "attempt-1", { ...result, taskId: "other" })).toThrow();
|
|
expect(environmentTaskOperationSchema.safeParse({ ...operation, path: "/arbitrary" }).success).toBe(false);
|
|
expect(environmentTaskOperationSchema.safeParse({ kind: "archive_state", priorIdentity: identity, archiveKey: "../escape", resumeFrom: "prior" }).success).toBe(false);
|
|
});
|
|
it("requires project validation for the exact configuration, provider, and projects before credential dispatch", async () => {
|
|
const operation = { ...submit, preparedLaunchId: "configuration", accountIdentity: "account",
|
|
credential: { type: "codex-auth-json" as const, authJson: '{"OPENAI_API_KEY":"never-leak"}' } };
|
|
const proof = { configurationId: "configuration", provider: "openai", projectIds: [projectId] };
|
|
for (const invalid of [undefined, { ...proof, configurationId: "other" }, { ...proof, provider: "anthropic" }, { ...proof, projectIds: [] }]) {
|
|
const value = row(); Object.assign(value.lease.metadata, { runnerTaskProjectAuthValidation: invalid });
|
|
const workers = worker();
|
|
await expect(executeEnvironmentTask(database(value).db, workers as never, { companyId: "company", leaseId, operation })).rejects.toThrow("validation unavailable");
|
|
expect(workers.call).not.toHaveBeenCalled();
|
|
}
|
|
const value = row(); Object.assign(value.lease.metadata, { runnerTaskProjectAuthValidation: proof });
|
|
await expect(executeEnvironmentTask(database(value).db, worker() as never, { companyId: "company", leaseId, operation })).resolves.toMatchObject({ kind: "accepted" });
|
|
await expect(executeEnvironmentTask(database(value).db, worker() as never, { companyId: "company", leaseId,
|
|
operation: { ...operation, credential: { type: "claude-env", environment: { ANTHROPIC_API_KEY: "never-leak" } } } })).rejects.toThrow("validation unavailable");
|
|
});
|
|
it.each(["allowed", "denied", "wrong-configuration", "wrong-provider", "timeout"])("records only a matching successful project validation (%s)", async mode => {
|
|
const operation = { kind: "check_project_auth" as const, configurationId: "configuration", provider: "openai" as const, projectIds: [projectId] };
|
|
const result = { kind: "project_auth", taskId: "attempt-1", configurationId: mode === "wrong-configuration" ? "other" : "configuration",
|
|
provider: mode === "wrong-provider" ? "anthropic" : "openai", allowed: mode !== "denied" };
|
|
const workers = worker(result); if (mode === "timeout") workers.call.mockRejectedValueOnce(new Error("timeout with private credential"));
|
|
const { db, sets } = database();
|
|
const checked = executeEnvironmentTask(db, workers as never, { companyId: "company", leaseId, operation });
|
|
if (["allowed", "denied"].includes(mode)) await expect(checked).resolves.toEqual(result);
|
|
else await expect(checked).rejects.toThrow("operation unavailable");
|
|
expect(sets).toHaveLength(mode === "allowed" ? 2 : 1);
|
|
expect(new PgDialect().sqlToQuery(sets[0].metadata as any).sql).toContain("- 'runnerTaskProjectAuthValidation'");
|
|
if (mode === "allowed") expect(JSON.parse(new PgDialect().sqlToQuery(sets[1].metadata as any).params[0] as string))
|
|
.toEqual({ runnerTaskProjectAuthValidation: { configurationId: "configuration", provider: "openai", projectIds: [projectId] } });
|
|
const denied = worker(result);
|
|
await expect(executeEnvironmentTask(database(row(), []).db, denied as never, { companyId: "company", leaseId, operation })).rejects.toThrow("project unavailable");
|
|
expect(denied.call).not.toHaveBeenCalled();
|
|
});
|
|
it.each([false, true])("prepares scoped configuration with submit-time auth %s", async atSubmit => {
|
|
const operation = { kind: "prepare" as const, projectIds: [projectId], requirements: {
|
|
schema: "paperclip.runner.launch-requirements.v1" as const,
|
|
identity: { runnerInstanceId: "runner", environmentLeaseId: leaseId, runId: "run",
|
|
normalizedSessionId: "session", turnId: "turn", itemId: "item" },
|
|
provider: "codex" as const, model: "selected", instructions: "Core instructions", runtimeContext: null,
|
|
settings: {}, approvalPolicy: "on-request" as const, readOnly: false,
|
|
preparedConversation: false, includeCollaborationModeInstructions: true,
|
|
} };
|
|
const result = { kind: "prepared", taskId: "attempt-1", ...(atSubmit ? {projectAuthCheckAtSubmit: true} : {}), launch: { configurationId: "configuration-1",
|
|
requirementsDigest: `sha256:${"a".repeat(64)}`, provider: { kind: "codex" } } };
|
|
const workers = worker(result), store = database();
|
|
await expect(executeEnvironmentTask(store.db, workers as never, { companyId: "company", leaseId, operation }))
|
|
.resolves.toEqual(result);
|
|
expect(workers.call).toHaveBeenCalledWith("original", "environmentTask", expect.objectContaining({
|
|
companyId: "company", runId: "run", agentId: "agent", projectIds: [projectId], operation,
|
|
}), 15_000);
|
|
expect(store.sets).toHaveLength(atSubmit ? 2 : 1);
|
|
if (atSubmit) expect(new PgDialect().sqlToQuery(store.sets[1].metadata as never).params).toContain(JSON.stringify({
|
|
runnerTaskProjectAuthValidation: { configurationId: "configuration-1", provider: "openai", projectIds: [projectId], atSubmit: true },
|
|
}));
|
|
const denied = worker(result);
|
|
const inactive = row(); inactive.lease.status = "released";
|
|
for (const value of [null, inactive]) {
|
|
await expect(executeEnvironmentTask(database(value).db, denied as never, { companyId: "company", leaseId, operation })).rejects.toThrow();
|
|
}
|
|
await expect(executeEnvironmentTask(database().db, denied as never, { companyId: "company", leaseId,
|
|
operation: { ...operation, requirements: { ...operation.requirements, identity: { ...operation.requirements.identity, runId: "other" } } } }))
|
|
.rejects.toThrow("identity mismatch");
|
|
await expect(executeEnvironmentTask(database(row(), []).db, denied as never, { companyId: "company", leaseId, operation }))
|
|
.rejects.toThrow("project unavailable");
|
|
expect(denied.call).not.toHaveBeenCalled();
|
|
expect(() => parseEnvironmentTaskResult(operation, "attempt-1", { ...result, taskId: "other" })).toThrow();
|
|
expect(environmentTaskOperationSchema.safeParse({ ...operation, command: "sh" }).success).toBe(false);
|
|
});
|
|
it("allows only the private, unexpired setup identity on an ad-hoc lease", async () => {
|
|
const value = row();
|
|
Object.assign(value.lease, { heartbeatRunId: null, issueId: null }); value.run = null;
|
|
value.lease.metadata.runnerTaskProjectAuthValidation.projectIds = [];
|
|
Object.assign(value.lease.metadata, { runnerTaskProbe: { runId: "probe-run", sessionId: "probe-session", expiresAtMs: Date.now() + 60_000 } });
|
|
const operation = { ...submit, projectIds: [], runner: { ...submit.runner, runId: "probe-run", sessionId: "probe-session" },
|
|
launch: { runnerVersion: "fixture", runnerDigest: `sha256:${"a".repeat(64)}`,
|
|
maxOutboxBytes: 262144, p0ReserveBytes: 65536, maxRuntimeMs: 0, maxLifetimeMs: 0,
|
|
lifecyclePolicy: { mode: "per_turn" as const, idleTimeoutMs: null },
|
|
} };
|
|
const workers = worker();
|
|
await expect(executeEnvironmentTask(database(value).db, workers as never, { companyId: "company", leaseId, operation })).resolves.toMatchObject({ kind: "accepted" });
|
|
expect(workers.call).toHaveBeenCalledWith("original", "environmentTask", expect.objectContaining({ runId: "probe-run", projectIds: [] }), 15_000);
|
|
const lifetime = (workers.call.mock.calls[0] as unknown as [string, string, { operation: typeof operation }])[2].operation.launch.maxRuntimeMs;
|
|
expect(lifetime).toBeGreaterThan(0); expect(lifetime).toBeLessThanOrEqual(60_000);
|
|
expect((workers.call.mock.calls[0] as unknown as [string, string, { operation: typeof operation }])[2].operation.launch).not.toHaveProperty("maxLifetimeMs");
|
|
for (const invalid of [{ ...operation, projectIds: [projectId] }, { ...operation, runner: { ...operation.runner, sessionId: "other" } }, submit]) {
|
|
await expect(executeEnvironmentTask(database(value).db, workers as never, { companyId: "company", leaseId, operation: invalid })).rejects.toThrow("identity mismatch");
|
|
}
|
|
const metadataOperation = { kind: "copilot_metadata" as const, token: "never-persist-token", model: "selected" };
|
|
const receipt = { kind: "copilot_metadata", taskId: "attempt-1", metadata: {}, executionStopped: true };
|
|
const metadataDatabase = database(value);
|
|
await expect(executeEnvironmentTask(metadataDatabase.db, worker(receipt) as never, { companyId: "company", leaseId, operation: metadataOperation })).resolves.toEqual(receipt);
|
|
const savedParams = JSON.stringify(metadataDatabase.sets.flatMap(set => new PgDialect().sqlToQuery((set as any).metadata).params));
|
|
expect(savedParams).toContain("runnerTaskDispatched");
|
|
expect(savedParams).not.toContain("never-persist-token");
|
|
await expect(executeEnvironmentTask(database().db, worker(receipt) as never, { companyId: "company", leaseId, operation: metadataOperation })).rejects.toThrow("private setup lease");
|
|
expect(() => parseEnvironmentTaskResult(metadataOperation, "attempt-1", { ...receipt, executionStopped: false })).toThrow();
|
|
Object.assign(value.lease.metadata, { runnerTaskProbe: { runId: "probe-run", sessionId: "probe-session", expiresAtMs: Date.now() - 1 } });
|
|
await expect(executeEnvironmentTask(database(value).db, workers as never, { companyId: "company", leaseId, operation })).rejects.toThrow("not active");
|
|
await expect(executeEnvironmentTask(database(value).db, workers as never, { companyId: "company", leaseId, operation: metadataOperation })).rejects.toThrow("not active");
|
|
await expect(executeEnvironmentTask(database(value).db, workers as never, { companyId: "company", leaseId, operation: { kind: "stop" } })).resolves.toMatchObject({ kind: "accepted" });
|
|
});
|
|
it("dispatches with host-derived scope and a persisted attempt identity", async () => {
|
|
const { db, query } = database(); const workers = worker();
|
|
await expect(executeEnvironmentTask(db, workers as never, { companyId: "company", leaseId, operation: submit })).resolves.toEqual({ kind: "accepted", taskId: "attempt-1" });
|
|
const scope = new PgDialect().sqlToQuery(query.where.mock.calls[0][0]);
|
|
expect(scope.sql).toContain('"environment_leases"."company_id" =');
|
|
expect(scope.sql).toContain('"environment_leases"."id" =');
|
|
expect(scope.params).toEqual([leaseId, "company"]);
|
|
expect(workers.call).toHaveBeenCalledWith("original", "environmentTask", expect.objectContaining({
|
|
taskId: "attempt-1", companyId: "company", agentId: "agent", projectIds: [projectId], runId: "run", operation: submit,
|
|
}), 15_000);
|
|
});
|
|
it("validates every requested project in the task company before dispatch", async () => {
|
|
const operation = { ...submit, projectIds: [projectId, secondProjectId] };
|
|
const value = row(); value.lease.metadata.runnerTaskProjectAuthValidation.projectIds = operation.projectIds;
|
|
const { db, query } = database(value, [{ id: projectId }, { id: secondProjectId }]);
|
|
const workers = worker();
|
|
await executeEnvironmentTask(db, workers as never, { companyId: "company", leaseId, operation });
|
|
expect(workers.call).toHaveBeenCalledWith("original", "environmentTask", expect.objectContaining({ projectIds: operation.projectIds }), 15_000);
|
|
const scope = new PgDialect().sqlToQuery(query.where.mock.calls[2][0]);
|
|
expect(scope.sql).toContain('"projects"."company_id" =');
|
|
expect(scope.params).toEqual(["company", projectId, secondProjectId]);
|
|
const rejected = worker();
|
|
await expect(executeEnvironmentTask(database(row(), [{ id: projectId }]).db, rejected as never, { companyId: "company", leaseId, operation })).rejects.toThrow("project unavailable");
|
|
expect(rejected.call).not.toHaveBeenCalled();
|
|
});
|
|
it("validates unique project IDs and allows tasks with no project mounts", async () => {
|
|
for (const projectIds of [[projectId, projectId], ["invalid"], Array(65).fill(projectId)]) {
|
|
expect(environmentTaskOperationSchema.safeParse({ ...submit, projectIds }).success).toBe(false);
|
|
}
|
|
const workers = worker();
|
|
const value = row(); value.lease.metadata.runnerTaskProjectAuthValidation.projectIds = [];
|
|
await executeEnvironmentTask(database(value).db, workers as never, { companyId: "company", leaseId, operation: { ...submit, projectIds: [] } });
|
|
expect(workers.call).toHaveBeenCalledWith("original", "environmentTask", expect.objectContaining({ projectIds: [] }), 15_000);
|
|
});
|
|
it("rejects an absent or cross-company lease before calling a worker", async () => {
|
|
const workers = worker();
|
|
await expect(executeEnvironmentTask(database(null).db, workers as never, { companyId: "other", leaseId, operation: submit })).rejects.toThrow("lease unavailable");
|
|
expect(workers.call).not.toHaveBeenCalled();
|
|
});
|
|
it("rejects mismatched Runner binding and inactive submission", async () => {
|
|
const workers = worker();
|
|
await expect(executeEnvironmentTask(database().db, workers as never, { companyId: "company", leaseId, operation: { ...submit, runner: { ...submit.runner, runId: "other" } } })).rejects.toThrow("identity mismatch");
|
|
const value = row(); value.lease.status = "released";
|
|
await expect(executeEnvironmentTask(database(value).db, workers as never, { companyId: "company", leaseId, operation: submit })).rejects.toThrow("not active");
|
|
expect(workers.call).not.toHaveBeenCalled();
|
|
});
|
|
it("uses the pinned provider for cleanup after environment edits", async () => {
|
|
const value = row(); value.lease.status = "released"; value.environment!.config.pluginKey = "replacement";
|
|
const workers = worker();
|
|
await executeEnvironmentTask(database(value).db, workers as never, { companyId: "company", leaseId, operation: { kind: "stop" } });
|
|
expect(workers.call).toHaveBeenCalledWith("original", "environmentTask", expect.objectContaining({ config: {}, operation: { kind: "stop" } }), 15_000);
|
|
});
|
|
it("keeps cleanup available after environment, run, and issue deletion", async () => {
|
|
const value = row(); value.environment = null; value.run = null;
|
|
for (const kind of ["stop", "complete", "status"] as const) {
|
|
const workers = worker(kind === "status" ? { kind: "status", taskId: "attempt-1", phase: "cancelled" } : undefined);
|
|
await executeEnvironmentTask(database(value).db, workers as never, { companyId: "company", leaseId, operation: { kind } });
|
|
expect(workers.call).toHaveBeenCalledWith("original", "environmentTask", expect.objectContaining({
|
|
config: {}, environmentId: null, runId: null, agentId: null, projectIds: [],
|
|
}), 15_000);
|
|
}
|
|
await expect(executeEnvironmentTask(database(value).db, worker() as never, { companyId: "company", leaseId, operation: submit })).rejects.toThrow("not active");
|
|
});
|
|
it("preserves opaque provider IDs in requests and receipts", async () => {
|
|
const value = row(); value.lease.providerLeaseId = `provider:attempt.${"x".repeat(120)}`;
|
|
const workers = worker({ kind: "accepted", taskId: value.lease.providerLeaseId });
|
|
await expect(executeEnvironmentTask(database(value).db, workers as never, { companyId: "company", leaseId, operation: { kind: "stop" } })).resolves.toMatchObject({ taskId: value.lease.providerLeaseId });
|
|
});
|
|
it("returns a validated connection only while the lease and run are active", async () => {
|
|
const connection = { kind: "connection", taskId: "attempt-1", endpoint: {
|
|
kind: "authenticated_websocket", websocketUrl: "wss://runner.example.test/connect", generation: "attempt-1",
|
|
secretHeaders: [{ name: "Authorization", value: "Bearer fixture" }],
|
|
} };
|
|
const input = { companyId: "company", leaseId, operation: { kind: "connection" as const } };
|
|
await expect(executeEnvironmentTask(database().db, worker(connection) as never, input)).resolves.toEqual(connection);
|
|
for (const state of ["expired", "finished", "deleted"] as const) {
|
|
const value = row();
|
|
if (state === "expired") value.lease.expiresAt = new Date(0);
|
|
if (state === "finished") value.run!.status = "succeeded";
|
|
if (state === "deleted") value.environment = null;
|
|
const workers = worker(connection);
|
|
await expect(executeEnvironmentTask(database(value).db, workers as never, input)).rejects.toThrow("not active");
|
|
expect(workers.call).not.toHaveBeenCalled();
|
|
}
|
|
for (const endpoint of [
|
|
{ ...connection.endpoint, websocketUrl: "ws://runner.example.test/connect" },
|
|
{ ...connection.endpoint, websocketUrl: "wss://runner.example.test/connect?token=fixture" },
|
|
{ ...connection.endpoint, secretHeaders: [{ name: "Authorization", value: "fixture\r\nInjected: true" }] },
|
|
{ ...connection.endpoint, secretHeaders: [{ name: "Invalid Header", value: "fixture" }] },
|
|
]) {
|
|
await expect(executeEnvironmentTask(database().db, worker({ ...connection, endpoint }) as never, input)).rejects.toThrow("reconcile the same task");
|
|
}
|
|
});
|
|
it.each(["manifest", "worker"])("requires live %s support", async kind => {
|
|
const workers = worker();
|
|
if (kind === "manifest") state.plugin.manifestJson.environmentDrivers[0].supportsTasks = false;
|
|
else workers.getWorker.mockReturnValue({ supportedMethods: [] });
|
|
await expect(executeEnvironmentTask(database().db, workers as never, { companyId: "company", leaseId, operation: submit })).rejects.toThrow("provider unavailable");
|
|
expect(workers.call).not.toHaveBeenCalled();
|
|
});
|
|
it("rejects wrong task receipts and sanitizes worker errors", async () => {
|
|
await expect(executeEnvironmentTask(database().db, worker({ kind: "accepted", taskId: "other" }) as never, { companyId: "company", leaseId, operation: submit })).rejects.toThrow("reconcile the same task");
|
|
const workers = worker(); workers.call.mockRejectedValue(new Error("private-credential"));
|
|
await expect(executeEnvironmentTask(database().db, workers as never, { companyId: "company", leaseId, operation: submit })).rejects.toThrow(/^Plugin-provided Runner execution operation unavailable; reconcile the same task before retrying$/);
|
|
});
|
|
it("accepts PRP identity characters and length limits", () => {
|
|
for (const field of ["runnerId", "leaseId", "runId", "sessionId", "turnId", "itemId"]) {
|
|
for (const value of ["session:part.1", "a".repeat(160)]) {
|
|
expect(environmentTaskOperationSchema.safeParse({ ...submit, runner: { ...submit.runner, [field]: value } }).success).toBe(true);
|
|
}
|
|
for (const value of ["a".repeat(161), "../escape", "a/b", ""]) {
|
|
expect(environmentTaskOperationSchema.safeParse({ ...submit, runner: { ...submit.runner, [field]: value } }).success).toBe(false);
|
|
}
|
|
}
|
|
});
|
|
it("validates the client's inclusive PRP version range", () => {
|
|
for (const range of [{ protocolMin: 1, protocolMax: 1 }, { protocolMin: 1, protocolMax: 2 }, { protocolMin: 3, protocolMax: 5 }]) {
|
|
expect(environmentTaskOperationSchema.safeParse({ ...submit, runner: { ...submit.runner, ...range } }).success).toBe(true);
|
|
}
|
|
for (const range of [
|
|
{ protocolMin: 0, protocolMax: 2 }, { protocolMin: 2, protocolMax: 1 },
|
|
{ protocolMin: 1.5, protocolMax: 2 }, { protocolMin: 1, protocolMax: 2.5 },
|
|
{ protocolMin: 1, protocolMax: Number.MAX_SAFE_INTEGER + 1 },
|
|
{ protocolMin: undefined, protocolMax: 2 }, { protocolMin: 1, protocolMax: undefined },
|
|
]) {
|
|
expect(environmentTaskOperationSchema.safeParse({ ...submit, runner: { ...submit.runner, ...range } }).success).toBe(false);
|
|
}
|
|
});
|
|
it("validates operation and result shape without making acceptance mean readiness", () => {
|
|
expect(environmentTaskOperationSchema.safeParse({ ...submit, surprise: true }).success).toBe(false);
|
|
expect(environmentTaskOperationSchema.safeParse({ ...submit, runner: { ...submit.runner, unknownField: true } }).success).toBe(false);
|
|
expect(() => parseEnvironmentTaskResult({ kind: "status" }, "attempt-1", { kind: "accepted", taskId: "attempt-1" })).toThrow();
|
|
expect(parseEnvironmentTaskResult(submit, "attempt-1", { kind: "accepted", taskId: "attempt-1" }).kind).toBe("accepted");
|
|
});
|
|
});
|