mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-28 12:04:41 +01:00
Match task Runner identities to the PRP contract
Accept PRP stable-ID characters and the 160-character limit for Runner identity fields. Type the worker submission fixture and assert its project list reaches the plugin hook. Verified SDK build, server typecheck, and all 22 focused task and worker tests. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
3 files changed
+23
-4
No files matched your search
@@ -3,6 +3,8 @@ import type { PluginEnvironmentDriverBaseParams, PluginEnvironmentLease } from "
|
||||
|
||||
// Provider IDs are opaque; providers validate their own addressing constraints.
|
||||
const providerId = z.string().min(1);
|
||||
// PRP identity.schema.json stableId.
|
||||
const runnerIdentity = z.string().regex(/^[A-Za-z0-9][A-Za-z0-9._:-]{0,159}$/);
|
||||
const identifier = z.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9_-]{0,95}$/);
|
||||
const secureUrl = z.string().url().refine(value => {
|
||||
const url = new URL(value);
|
||||
@@ -19,8 +21,8 @@ export const environmentTaskOperationSchema = z.discriminatedUnion("kind", [
|
||||
protocolMin: z.number().int().min(1).max(Number.MAX_SAFE_INTEGER),
|
||||
protocolMax: z.number().int().min(1).max(Number.MAX_SAFE_INTEGER),
|
||||
harness: identifier,
|
||||
runnerId: identifier, leaseId: identifier, runId: identifier,
|
||||
sessionId: identifier, turnId: identifier, itemId: identifier,
|
||||
runnerId: runnerIdentity, leaseId: runnerIdentity, runId: runnerIdentity,
|
||||
sessionId: runnerIdentity, turnId: runnerIdentity, itemId: runnerIdentity,
|
||||
}).strict().refine(value => value.protocolMin <= value.protocolMax, "Invalid PRP version range"),
|
||||
bootstrapTicket: z.string().min(1).max(65_536),
|
||||
}).strict(),
|
||||
|
||||
@@ -132,6 +132,16 @@ describe("environment task admission", () => {
|
||||
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(/^Environment task 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);
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
parseMessage,
|
||||
serializeMessage,
|
||||
} from "../../../packages/plugins/sdk/src/protocol.js";
|
||||
import type { PluginEnvironmentTaskParams } from "../../../packages/plugins/sdk/src/environment-tasks.js";
|
||||
import { definePlugin } from "../../../packages/plugins/sdk/src/define-plugin.js";
|
||||
import { startWorkerRpcHost } from "../../../packages/plugins/sdk/src/worker-rpc-host.js";
|
||||
import { pluginManifestV1Schema, type PaperclipPluginManifestV1 } from "@paperclipai/shared";
|
||||
@@ -121,11 +122,17 @@ describe("plugin environment driver seam", () => {
|
||||
expect(isJsonRpcSuccessResponse(initialized)).toBe(true);
|
||||
if (!isJsonRpcSuccessResponse(initialized)) return;
|
||||
expect(initialized.result.supportedMethods).toContain("environmentTask");
|
||||
const params = { driverKey: "fake-plugin", companyId: "company", environmentId: "environment", config: {},
|
||||
taskId: "attempt", runId: "run", agentId: "agent", projectId: null, lease: { providerLeaseId: "attempt" }, operation: { kind: "stop" } };
|
||||
const projectIds = ["10000000-0000-4000-8000-000000000001", "10000000-0000-4000-8000-000000000002"];
|
||||
const params: PluginEnvironmentTaskParams = { driverKey: "fake-plugin", companyId: "company", environmentId: "environment", config: {},
|
||||
taskId: "attempt", runId: "run", agentId: "agent", projectIds, lease: { providerLeaseId: "attempt" },
|
||||
operation: { kind: "submit", projectIds, bootstrapTicket: "transient-test-ticket", runner: {
|
||||
protocolMin: 1, protocolMax: 2, harness: "codex", runnerId: "runner", leaseId: "lease", runId: "run", sessionId: "session:part.1", turnId: "turn", itemId: "item",
|
||||
} } };
|
||||
|
||||
stdin.write(serializeMessage(createRequest("environmentTask", params, 2)));
|
||||
await waitForResponses(responses, 2);
|
||||
expect(responses[1]).toMatchObject({ result: { kind: "accepted", taskId: "attempt" } });
|
||||
expect(calls[0]).toMatchObject({ projectIds, operation: { kind: "submit", projectIds } });
|
||||
stdin.write(serializeMessage(createRequest("environmentTask", { ...params, operation: { kind: "status" } }, 3)));
|
||||
await waitForResponses(responses, 3);
|
||||
expect(isJsonRpcErrorResponse(responses[2])).toBe(true); // Wrong response kind.
|
||||
|
||||
Reference in new issue
Block a user