mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-10 12:37:55 +01:00
fix(runner): send the prepared task scope with retained release and resume (#19896)
This commit is contained in:
1 parent
5020f38f90
commit
91b044858c
2 files changed
+34
-2
No files matched your search
@@ -390,6 +390,34 @@ describe("plugin-provided Runner execution", () => {
|
||||
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.each(["release", "resume"] as const)("sends the prepared project and fresh scope with a retained %s", async kind => {
|
||||
const identity = { runnerInstanceId: "runner", environmentLeaseId: leaseId, runId: "run", normalizedSessionId: "session", turnId: "turn", itemId: "item" };
|
||||
const operation = kind === "release" ? { kind, identity } : { kind, identity, bootstrapTicket: "transient-test-ticket" };
|
||||
const result = kind === "release" ? { kind: "released", taskId: "attempt-1" } : { kind: "resume", taskId: "attempt-1", outcome: "resumed" };
|
||||
const fresh = { attemptId: leaseId, sourceRunId: secondProjectId, previousWorkspaceId: null, recoverySnapshots: [] };
|
||||
const value = row(); value.lease.status = "retained"; value.run!.status = "succeeded";
|
||||
Object.assign(value.lease.metadata, { runnerTaskProjectIds: [projectId, secondProjectId], freshWorkspace: fresh });
|
||||
const { db, query } = database(value, [{ id: projectId }, { id: secondProjectId }]);
|
||||
const workers = worker(result);
|
||||
await expect(executeEnvironmentTask(db, workers as never, { companyId: "company", leaseId, operation })).resolves.toEqual(result);
|
||||
expect(workers.call).toHaveBeenCalledWith("original", "environmentTask", expect.objectContaining({
|
||||
projectIds: [projectId, secondProjectId], fresh, operation }), 15_000);
|
||||
const scope = new PgDialect().sqlToQuery(query.where.mock.calls[1][0]);
|
||||
expect(scope.sql).toContain('"projects"."company_id" =');
|
||||
expect(scope.params).toEqual(["company", projectId, secondProjectId]);
|
||||
|
||||
const unscoped = row(); unscoped.lease.status = "retained";
|
||||
const plain = worker(result);
|
||||
await executeEnvironmentTask(database(unscoped, []).db, plain as never, { companyId: "company", leaseId, operation });
|
||||
const sent = (plain.call.mock.calls[0] as unknown as any[])[2];
|
||||
expect(sent.projectIds).toEqual([]);
|
||||
expect(sent).not.toHaveProperty("fresh");
|
||||
|
||||
const rejected = worker(result);
|
||||
await expect(executeEnvironmentTask(database(value, [{ id: projectId }]).db, rejected as never, { companyId: "company", leaseId, operation }))
|
||||
.rejects.toThrow("project unavailable");
|
||||
expect(rejected.call).not.toHaveBeenCalled();
|
||||
});
|
||||
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");
|
||||
|
||||
@@ -80,6 +80,7 @@ export async function executeEnvironmentTask(db: Db, workers: PluginWorkerManage
|
||||
} : null;
|
||||
const launches = operation.kind === "submit" || operation.kind === "prepare";
|
||||
const scopesProjects = launches || operation.kind === "check_project_auth" || operation.kind === "read_file";
|
||||
const retainedTask = operation.kind === "release" || operation.kind === "resume";
|
||||
const resumeFrom = launches || checkpointIdentity ? (operation as { resumeFrom?: string }).resumeFrom : undefined;
|
||||
const savedIdentity = metadata.runnerTaskIdentity as Record<string, unknown> | undefined;
|
||||
const prior = operation.kind === "prepare" ? operation.priorIdentity : undefined;
|
||||
@@ -192,7 +193,10 @@ export async function executeEnvironmentTask(db: Db, workers: PluginWorkerManage
|
||||
freshWorkspace = await stageProviderFreshWorkspace({ db, companyId: input.companyId,
|
||||
issueId: lease.issueId, ...freshWorkspace, existing: freshWorkspace });
|
||||
}
|
||||
const projectIds = scopesProjects ? operation.projectIds : [];
|
||||
const savedProjectIds = metadata.runnerTaskProjectIds ?? [];
|
||||
if (retainedTask && (!Array.isArray(savedProjectIds) || savedProjectIds.some(id => typeof id !== "string")))
|
||||
throw new Error("Plugin Runner project scope changed");
|
||||
const projectIds: string[] = scopesProjects ? operation.projectIds : retainedTask ? [...savedProjectIds as string[]] : [];
|
||||
if (scopesProjects && Array.isArray(metadata.runnerTaskProjectIds) &&
|
||||
JSON.stringify(projectIds) !== JSON.stringify(metadata.runnerTaskProjectIds)) throw new Error("Plugin Runner project scope changed");
|
||||
if (activeProbe && projectIds.length > 0) throw new Error("Plugin Runner probe cannot use project mounts");
|
||||
@@ -380,7 +384,7 @@ export async function executeEnvironmentTask(db: Db, workers: PluginWorkerManage
|
||||
taskId, runId: run?.id ?? (activeProbe ? String(probe.runId) : null), agentId: run?.agentId ?? null, projectIds,
|
||||
lease: { providerLeaseId: lease.providerLeaseId, metadata: lease.metadata ?? undefined },
|
||||
...(workspaceBinding ? { workspace: workspaceBinding } : {}),
|
||||
...(freshWorkspace && scopesProjects ? { fresh: freshWorkspace } : {}), operation,
|
||||
...(freshWorkspace && (scopesProjects || retainedTask) ? { fresh: freshWorkspace } : {}), operation,
|
||||
executionContextSupported: 1 as const,
|
||||
...(executionContext ? { executionContext } : {}),
|
||||
};
|
||||
|
||||
Reference in new issue
Block a user