Files
PaperClipAI/tests/runner-e2e/cursor-native-evidence.ts
T
DottaandPaperclip e2b89b6302 Integrate pinned Cursor ACP runtime and explicit public setup
Consolidate Cursor v10 onto current controller behavior, retain admission gates, and prepare native interaction and cleanup qualification. Add the public provisioner and ordinary provider-pack assets.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-03 13:33:15 -05:00

127 lines
12 KiB
TypeScript

import { withoutProvenBootstrapReads, type BootstrapReadProof } from "./native-bootstrap-read-proof.js";
import { createHash } from "node:crypto";
import { isAbsolute, posix } from "node:path";
export interface CursorToolNotice {
runId: string; sessionId: string; turnId: string; toolCallId: string; seq: number;
stage: "tool" | "permission_requested" | "permission_delivered";
status?: string; operation?: string; commandSha256?: string; readTargetSha256?: string; requestId?: string; declineOffered?: boolean; outcome?: string;
}
const rec = (v: unknown): Record<string, any> => v !== null && typeof v === "object" && !Array.isArray(v) ? v as Record<string, any> : {};
const id = (v: unknown): v is string => typeof v === "string" && v.length > 0 && v.length <= 240 && !/[\u0000-\u001f\u007f]/u.test(v) && !v.includes("[REDACTED]");
const enums = { stage: ["tool", "permission_requested", "permission_delivered"], status: ["pending", "in_progress", "completed", "failed"], operation: ["execute", "read"], outcome: ["allow_once", "allow_always", "reject_once", "cancel"] };
const names = new Set(["stage", "toolCallId", "status", "operation", "commandSha256", "readTargetSha256", "requestId", "declineOffered", "outcome"]);
export function readCursorToolEvidence(rows: readonly unknown[], runId: string): CursorToolNotice[] {
const result: CursorToolNotice[] = [];
for (const value of rows) {
const row = rec(value), event = rec(rec(row.payload).prpEvent), p = rec(event.payload);
if (p.category !== "cursor_tool_evidence_v1") continue;
const origin = rec(p.provenance);
if (row.eventType !== "provider.notice.recorded" || row.runId !== runId || row.protocolSchemaVersion !== 1 || event.schemaVersion !== 1 || p.schema !== "paperclip.provider.notice.v1" || p.scope !== "turn" || event.schema !== "paperclip.prp.event.v1" || event.sourceKind !== "runner" || event.eventType !== row.eventType || event.runId !== runId || !id(origin.sessionId) || !id(origin.turnId) || origin.turnId !== event.turnId || !Array.isArray(p.details) || p.details.length > 12 || !Number.isSafeInteger(row.seq) || row.seq < 0 || !Number.isFinite(Date.parse(event.emittedAt))) throw new Error("Invalid Cursor evidence binding");
const fields: Record<string, string> = {};
for (const value of p.details) {
const d = rec(value);
if (d.name === "stage" && d.value === "evidence_incomplete") throw new Error("Cursor evidence is explicitly incomplete");
if (!names.has(d.name) || Object.hasOwn(fields, d.name) || typeof d.value !== "string" || d.value.length > 1024 || d.value.includes("[REDACTED]")) throw new Error("Invalid Cursor evidence detail");
fields[d.name] = d.value;
}
if (!id(fields.toolCallId) || !enums.stage.includes(fields.stage!) || origin.eventType !== fields.stage || origin.method !== (fields.stage === "tool" ? "session/update" : "session/request_permission")) throw new Error("Invalid Cursor evidence origin");
for (const [key, values] of Object.entries(enums)) if (fields[key] !== undefined && !values.includes(fields[key]!)) throw new Error("Invalid Cursor evidence enum");
if (fields.requestId !== undefined && !id(fields.requestId)) throw new Error("Invalid Cursor request identity");
if (fields.readTargetSha256 !== undefined && !/^sha256:[a-f0-9]{64}$/u.test(fields.readTargetSha256)) throw new Error("Invalid Cursor read digest");
if (fields.commandSha256 !== undefined && !/^sha256:[a-f0-9]{64}$/u.test(fields.commandSha256)) throw new Error("Invalid Cursor command digest");
if (fields.declineOffered !== undefined && !["true", "false"].includes(fields.declineOffered)) throw new Error("Invalid Cursor offered choice");
result.push({ ...fields, runId, sessionId: origin.sessionId, turnId: origin.turnId, seq: row.seq, declineOffered: fields.declineOffered === "true" } as CursorToolNotice);
}
if (new Set(result.map(row => row.seq)).size !== result.length) throw new Error("Duplicate Cursor evidence sequence");
return result;
}
/** Exact absolute target removes any dependency on implicit native shell cwd. */
export function cursorDeniedCommand(path: string) {
if (!isAbsolute(path) || /[\u0000-\u001f\u007f]/u.test(path)) throw new Error("Invalid denial target");
const command = `printf 'MUST_NOT_EXIST' > '${path.replaceAll("'", "'\\''")}'`;
return { command, commandSha256: `sha256:${createHash("sha256").update(command).digest("hex")}` };
}
export function hasCursorDeniedCommand(input: {
notices: readonly CursorToolNotice[]; runId: string; turnId: string; requestId: string; toolCallId: string; commandSha256: string; bootstrapReadProof?: BootstrapReadProof;
}): boolean {
const origin = input.notices.find(row => row.stage === "tool" && row.status === "pending" && row.operation === "execute" && row.toolCallId === input.toolCallId && row.commandSha256 === input.commandSha256);
let notices: readonly CursorToolNotice[];
try { notices = origin ? withoutProvenBootstrapReads(input.notices, origin, input.bootstrapReadProof) : input.notices; }
catch { return false; }
const requests = notices.filter(row => row.stage === "permission_requested");
if (requests.length !== 1) return false;
const request = requests[0]!;
const same = (row: CursorToolNotice) => row.runId === input.runId && row.turnId === input.turnId && row.toolCallId === input.toolCallId && row.sessionId === request.sessionId && row.commandSha256 === input.commandSha256 && row.operation === "execute";
if (!same(request) || request.requestId !== input.requestId || !request.declineOffered) return false;
const origins = notices.filter(row => row.stage === "tool" && row.status === "pending");
const delivered = notices.filter(row => row.stage === "permission_delivered");
const failed = notices.filter(row => row.stage === "tool" && row.status === "failed");
return origins.length === 1 && same(origins[0]!) && delivered.length === 1 && same(delivered[0]!)
&& delivered[0]!.requestId === input.requestId && delivered[0]!.outcome === "reject_once"
&& failed.length === 1 && same(failed[0]!) && origins[0]!.seq < request.seq && request.seq < delivered[0]!.seq && delivered[0]!.seq < failed[0]!.seq
&& notices.every(row => same(row) && row.status !== "completed");
}
export function hasCursorCancellation(input: { run: unknown; issue: unknown; events: readonly unknown[]; runId: string; turnId: string; requestedAt: number }): boolean {
const run = rec(input.run), cancellation = rec(rec(run.resultJson).nativeCancellation);
const terminals = input.events.map(rec).map(row => ({ row, event: rec(rec(row.payload).prpEvent) })).filter(({ event }) => ["turn.cancelled", "turn.interrupted"].includes(event.eventType));
return run.id === input.runId && run.status === "cancelled" && rec(input.issue).status === "in_progress"
&& cancellation.dispatchState === "acknowledged" && cancellation.dispatched === true && cancellation.scope === "run"
&& terminals.length === 1 && terminals.every(({ row, event }) => row.runId === input.runId && event.runId === input.runId && event.turnId === input.turnId
&& event.schema === "paperclip.prp.event.v1" && event.schemaVersion === 1 && row.protocolSchemaVersion === 1 && event.sourceKind === "runner"
&& Number.isFinite(input.requestedAt) && Date.parse(event.emittedAt) >= input.requestedAt);
}
/** Controller-observed sandbox receipts; never substitute host copy-back files. */
export interface CursorRemoteBinding {
companyId: string; environmentId: string; runId: string; leaseId: string;
sandboxId: string; remoteCwd: string; image: string;
}
export interface CursorRemoteSnapshot {
binding: CursorRemoteBinding; observedAtMs: number; complete: boolean;
workspace: Record<string, string>;
targets: Record<string, { absent: boolean; sha256: string | null; parent: { dev: string; ino: string }; mutationCount: number; complete: boolean }>;
watcher: { complete: boolean; targetMutationCount: number; workspaceMutationCount: number };
processes: { captured: boolean; root: { pid: number; startTicks: string; bootId: string } | null;
journal: Array<{ pid: number; ppid: number; startTicks: string; bootId: string }>; live: number[] };
}
export function assertCursorRemoteSnapshot(snapshot: CursorRemoteSnapshot, binding: CursorRemoteBinding): void {
if (!snapshot.complete || !snapshot.watcher.complete || (!Number.isSafeInteger(snapshot.observedAtMs) || snapshot.observedAtMs < 0)
|| !Object.keys(binding).every(key => snapshot.binding[key as keyof CursorRemoteBinding] === binding[key as keyof CursorRemoteBinding])
|| !isAbsolute(binding.remoteCwd) || posix.normalize(binding.remoteCwd) !== binding.remoteCwd || !/^.+@sha256:[a-f0-9]{64}$/u.test(binding.image) || ![binding.companyId, binding.environmentId, binding.runId, binding.leaseId, binding.sandboxId].every(id)) throw new Error("Cursor remote evidence lacks exact complete lease authority");
const entries = Object.entries(snapshot.workspace);
if (entries.length > 2048 || entries.some(([name, value]) => !name || name.startsWith("/") || /[\\\u0000-\u001f\u007f]/u.test(name)
|| name.replace(/\/$/u, "").split("/").some(part => !part || part === "." || part === "..")
|| (value !== "directory" && !/^sha256:[a-f0-9]{64}$/u.test(value)))) throw new Error("Cursor remote workspace evidence is malformed or unbounded");
if (![snapshot.watcher.targetMutationCount, snapshot.watcher.workspaceMutationCount].every(value => Number.isSafeInteger(value) && value >= 0)) throw new Error("Cursor remote watcher receipt is malformed");
}
export function hasCursorRemoteRetirement(snapshot: CursorRemoteSnapshot, binding: CursorRemoteBinding): boolean {
try { assertCursorRemoteSnapshot(snapshot, binding); } catch { return false; }
const p = snapshot.processes, root = p.root;
return p.captured && root !== null && Number.isSafeInteger(root.pid) && root.pid > 1 && /^\d+$/u.test(root.startTicks)
&& /^[a-f0-9-]{36}$/iu.test(root.bootId) && p.live.length === 0 && p.journal.length > 0
&& p.journal.some(item => item.pid === root.pid && item.startTicks === root.startTicks && item.bootId === root.bootId)
&& p.journal.every(item => Number.isSafeInteger(item.pid) && item.pid > 1 && /^\d+$/u.test(item.startTicks) && item.bootId === root.bootId);
}
export function cursorRemoteDeniedSample(snapshot: CursorRemoteSnapshot, binding: CursorRemoteBinding, relative: string, phase: string,
expectedParent?: { dev: string; ino: string }) {
assertCursorRemoteSnapshot(snapshot, binding);
if (!/^[a-zA-Z0-9_-]+\.txt$/u.test(relative)) throw new Error("Invalid remote denied target");
const target = snapshot.targets[relative];
if (!target || !target.complete || !/^\d+$/u.test(target.parent.dev) || !/^\d+$/u.test(target.parent.ino)
|| !Number.isSafeInteger(target.mutationCount) || target.mutationCount !== 0
|| (expectedParent && (target.parent.dev !== expectedParent.dev || target.parent.ino !== expectedParent.ino))) throw new Error("Cursor remote denied target observation is incomplete or changed");
return { phase, path: posix.join(binding.remoteCwd, relative), absent: target.absent === true && target.sha256 === null,
observedAt: snapshot.observedAtMs, parent: target.parent };
}
export function hasCursorRemoteWorkspaceUnchanged(snapshot: CursorRemoteSnapshot, baseline: CursorRemoteSnapshot): boolean {
try { assertCursorRemoteSnapshot(snapshot, baseline.binding); assertCursorRemoteSnapshot(baseline, baseline.binding); } catch { return false; }
return snapshot.observedAtMs >= baseline.observedAtMs
&& snapshot.watcher.workspaceMutationCount === baseline.watcher.workspaceMutationCount
&& JSON.stringify(Object.entries(snapshot.workspace).sort()) === JSON.stringify(Object.entries(baseline.workspace).sort());
}