import { parsePaperclipQuestionResponse } from "../../packages/paperclip-runner/src/contracts/question-set.js"; import { normalizeCursorPlanRequest } from "../../packages/paperclip-runner/src/drivers/acpx/cursor-extensions.js"; import { cursorNativePlanResponse, findCursorNativeRequest, hasCursorNativeCardBinding } from "./cursor-native-flow.js"; import { cursorDeniedCommand, type CursorToolNotice } from "./cursor-native-evidence.js"; import { describe, expect, it } from "vitest"; import { cursorNativeCaseDesigns, cursorNativePrompt, hasDeliveredCursorNativeRequest, hasCursorPlanDecision, hasCursorDenialBoundary, CURSOR_DENIAL_SAMPLE_PHASES, hasExactCursorNativeResponse } from "./cursor-native-cases.js"; const proof = (adapter = "acpx-runtime") => { const wrap = (eventType: string, sourceSeq: number, payload: unknown) => ({ runId: "run", protocolSchemaVersion: 1, payload: { prpEvent: { schema: "paperclip.prp.event.v1", schemaVersion: 1, runId: "run", turnId: "turn", eventType, sourceSeq, payload, } } }); return [wrap("runtime_request.created", 1, { request: { requestId: "request", turnId: "turn", type: "input", status: "pending", origin: { adapter, provider: "cursor", method: "cursor/ask_question" } } }), wrap("runtime_request.resolved", 2, { requestId: "request", turnId: "turn", action: "submit" })]; }; const grade = (events: unknown[]) => hasDeliveredCursorNativeRequest({ events, runId: "run", turnId: "turn", requestId: "request", method: "cursor/ask_question", action: "submit" }); describe("prepared Cursor native qualification", () => { it("has four fixed native-only designs and bounded prompts", () => { expect(cursorNativeCaseDesigns).toHaveLength(4); for (const design of cursorNativeCaseDesigns) expect(cursorNativePrompt(design.id, "test-nonce")).toContain("Do not substitute"); expect(() => cursorNativePrompt("native-write-deny-reconnect", "../escape")).toThrow(); }); it("requires correlated native origin and response delivery evidence", () => { expect(grade(proof())).toBe(true); expect(grade([])).toBe(false); }); it("rejects semantic substitutions and permission fallbacks", () => { for (const method of ["request_human_input", "session/request_permission", "cursor/create_plan"]) { const rows = proof(); (rows[0]!.payload.prpEvent.payload as any).request.origin.method = method; expect(grade(rows)).toBe(false); } }); it("rejects missing, duplicated and expired delivery", () => { const rows = proof(); expect(grade(rows.slice(0, 1))).toBe(false); expect(grade([...rows, rows[1]])).toBe(false); rows[1]!.payload.prpEvent.eventType = "runtime_request.expired"; expect(grade(rows)).toBe(false); }); it("rejects mismatched run, turn and event ordering", () => { let rows = proof(); rows[1]!.runId = "foreign"; expect(grade(rows)).toBe(false); rows = proof(); rows[1]!.payload.prpEvent.turnId = "stale"; expect(grade(rows)).toBe(false); rows = proof(); rows[1]!.payload.prpEvent.sourceSeq = 0; expect(grade(rows)).toBe(false); }); }); it("binds plan decisions to the full revision and rejects stale answers", () => { const id = `plan-${"a".repeat(64)}`; const questionSet = { questions: [{ id }] }; const answer = { schema: "paperclip.question_response.v1", answers: { [id]: { selectedOptionIds: ["accept"] } } }; expect(hasCursorPlanDecision(questionSet, answer, "accept")).toBe(true); expect(hasCursorPlanDecision({ questions: [{ id: `plan-${"b".repeat(64)}` }] }, answer, "accept")).toBe(false); expect(hasCursorPlanDecision(questionSet, answer, "reject")).toBe(false); }); it.each(["acpx-runtime", "acpx-runtime-sidecar"])("requires supported denial choices, native IDs and complete no-effect boundaries via %s", adapter => { const input = { request: { requestId: "request", turnId: "turn", type: "permission", status: "pending", details: { toolCallId: "tool" }, origin: { adapter, provider: "cursor", method: "session/request_permission" }, choices: [{ key: "accept" }, { key: "decline" }, { key: "cancel" }] }, expectedRequestId: "request", expectedToolCallId: "tool", path: "/fixture/denied.txt", runId: "run", turnId: "turn", notices: ([{ stage: "tool", status: "pending" }, { stage: "permission_requested", requestId: "request", declineOffered: true }, { stage: "permission_delivered", requestId: "request", outcome: "reject_once" }, { stage: "tool", status: "failed" }].map((row, index) => ({ ...row, seq: index + 1, runId: "run", sessionId: "session", turnId: "turn", toolCallId: "tool", operation: "execute", commandSha256: cursorDeniedCommand("/fixture/denied.txt").commandSha256 })) as CursorToolNotice[]), samples: CURSOR_DENIAL_SAMPLE_PHASES.map((phase, observedAt) => ({ phase, observedAt, absent: true, path: "/fixture/denied.txt" })) }; expect(hasCursorDenialBoundary(input)).toBe(true); for (const origin of [{ ...input.request.origin, adapter: "semantic" }, { ...input.request.origin, provider: "pi" }, { ...input.request.origin, method: "request_human_input" }]) { expect(hasCursorDenialBoundary({ ...input, request: { ...input.request, origin } })).toBe(false); } expect(hasCursorDenialBoundary({ ...input, samples: input.samples.slice(1) })).toBe(false); expect(hasCursorDenialBoundary({ ...input, expectedToolCallId: "foreign" })).toBe(false); input.samples[2]!.absent = false; expect(hasCursorDenialBoundary(input)).toBe(false); input.samples[2]!.absent = true; input.request.choices.push({ key: "accept_for_session" }); expect(hasCursorDenialBoundary(input)).toBe(false); }); it("requires exact delivered answer bytes rather than a saved or different answer", () => { const rows = proof(); const response = { schema: "paperclip.question_response.v1", answers: { q: { selectedOptionIds: ["chosen"] } } }; (rows[1]!.payload.prpEvent.payload as any).response = response; const input = { events: rows, runId: "run", turnId: "turn", requestId: "request", method: "cursor/ask_question" as const, action: "submit" as const, response }; expect(hasExactCursorNativeResponse(input)).toBe(true); expect(hasExactCursorNativeResponse({ ...input, response: { ...response, answers: { q: { selectedOptionIds: ["other"] } } } })).toBe(false); expect(hasExactCursorNativeResponse({ ...input, events: rows.slice(0, 1) })).toBe(false); }); it.each(["accept", "cancel", "reject"] as const)("recognizes canonical delivered native plan %s without an empty optional answer", decision => { const native = normalizeCursorPlanRequest({ toolCallId: "native-plan", plan: "Verify the revised plan.", todos: [] }); const planId = native.questionSet.questions[0]!.id; const response = cursorNativePlanResponse(planId, decision, " Keep exact feedback 漢字\nSecond line "); const delivered = parsePaperclipQuestionResponse(native.questionSet, response); const rows = proof(); (rows[0]!.payload.prpEvent.payload as any).request.origin.method = "cursor/create_plan"; (rows[1]!.payload.prpEvent.payload as any).response = delivered; const input = { events: rows, runId: "run", turnId: "turn", requestId: "request", method: "cursor/create_plan" as const, action: "submit" as const, response }; expect(hasCursorPlanDecision(native.questionSet, response, decision)).toBe(true); expect(hasExactCursorNativeResponse(input)).toBe(true); expect(native.resolve(delivered).outcome.outcome).toBe({ accept: "accepted", cancel: "cancelled", reject: "rejected" }[decision]); if (decision === "reject") expect(delivered.answers.reason?.text).toBe(" Keep exact feedback 漢字\nSecond line "); else { expect(delivered.answers).not.toHaveProperty("reason"); expect(hasExactCursorNativeResponse({ ...input, response: { ...response, answers: { ...response.answers, reason: {} } } })).toBe(false); } }); // Retained paid sidecar plan callback exposed both origin and JSONB key-order drift. it.each(["acpx-runtime", "acpx-runtime-sidecar"])("recognizes a native plan and its durable card via %s", adapter => { const input = normalizeCursorPlanRequest({ toolCallId: "native-plan", name: "Fixture plan", plan: "Verify complete Markdown.", todos: [{ id: "first", content: "Read", status: "pending" }, { id: "second", content: "Check", status: "pending" }] }).questionSet; const rows = proof(adapter); const request = (rows[0]!.payload.prpEvent.payload as any).request; request.origin.method = "cursor/create_plan"; request.input = input; const event = findCursorNativeRequest(rows, "cursor/create_plan", "request")!; expect(event).toBe(rows[0]!.payload.prpEvent); expect(hasDeliveredCursorNativeRequest({ events: rows, runId: "run", turnId: "turn", requestId: "request", method: "cursor/create_plan", action: "submit" })).toBe(true); const reorder = (value: any): any => Array.isArray(value) ? value.map(reorder) : value !== null && typeof value === "object" ? Object.fromEntries(Object.entries(value).reverse().map(([key, nested]) => [key, reorder(nested)])) : value; const card = { sourceRunId: "run", continuationPolicy: "none", payload: { runtimeRequestId: "request", questionSet: reorder(input) } }; expect(JSON.stringify(card.payload.questionSet)).not.toBe(JSON.stringify(input)); expect(hasCursorNativeCardBinding(card, event, "run")).toBe(true); expect(hasCursorNativeCardBinding(card, event, "other-run")).toBe(false); expect(hasCursorNativeCardBinding({ ...card, continuationPolicy: "resume" }, event, "run")).toBe(false); const changed = structuredClone(card); changed.payload.questionSet.questions[0].options[0].label = "Changed choice"; expect(hasCursorNativeCardBinding(changed, event, "run")).toBe(false); const reordered = structuredClone(card); reordered.payload.questionSet.questions[0].options.reverse(); expect(hasCursorNativeCardBinding(reordered, event, "run")).toBe(false); expect(findCursorNativeRequest(rows, "cursor/create_plan", "other-request")).toBeUndefined(); expect(findCursorNativeRequest(rows, "cursor/ask_question", "request")).toBeUndefined(); }); it.each(["acpx-runtime", "acpx-runtime-sidecar"])("rejects foreign native request identity or origin via %s", adapter => { for (const patch of [{ adapter: "unknown" }, { adapter: "acpx-runtime-sidecar-unknown" }, { adapter: "semantic" }, { provider: "pi" }, { method: "request_human_input" }]) { const rows = proof(adapter); Object.assign((rows[0]!.payload.prpEvent.payload as any).request.origin, patch); expect(grade(rows)).toBe(false); expect(findCursorNativeRequest(rows, "cursor/ask_question", "request")).toBeUndefined(); } for (const patch of [{ requestId: "foreign" }, { turnId: "stale" }]) { const rows = proof(adapter); Object.assign((rows[0]!.payload.prpEvent.payload as any).request, patch); expect(grade(rows)).toBe(false); } const rows = proof(adapter); expect(grade([...rows, rows[0]!])).toBe(false); expect(grade([...rows, rows[1]!])).toBe(false); rows[1]!.payload.prpEvent.turnId = "stale"; expect(grade(rows)).toBe(false); });