Add Pi remote native evidence and browser denial fixtures

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-09-29 17:13:41 -05:00
1 parent fce6e109ad
commit d1bb5729f9
8 files changed
+376 -24

No files matched your search

+14 -7
View File
@@ -5,28 +5,35 @@ import { buildRunnerE2EProcessEnvironment } from "./harness-env.js";
import { parseRunnerSelectors, selectRunnerExecutions } from "./selectors.js";
describe("Pi native Product qualification", () => {
it("selects three explicit local Pi cases and preserves the basic extended matrix", () => {
it("selects four local and three remote Pi cases without changing the basic extended matrix", () => {
const suite = runnerSuites.find(row => row.id === "pi-native")!;
expect(suite.manualOnly).toBe(true); expect(suite.expectedMatrixSize).toBe(3);
expect(suite.manualOnly).toBe(true); expect(suite.expectedMatrixSize).toBe(7);
const cells = runnerMatrix.filter(row => row.suite.id === suite.id);
expect(cells).toHaveLength(3);
expect(cells.every(row => row.environment.id === "local" && row.profile.qualificationCandidate === "pi")).toBe(true);
expect(cells.map(row => row.task.expectedRunCount)).toEqual([1, 2, 1]);
expect(cells).toHaveLength(7);
expect(cells.every(row => row.profile.qualificationCandidate === "pi")).toBe(true);
expect(cells.filter(row => row.environment.id === "local").map(row => [row.task.id, row.task.expectedRunCount])).toEqual([
["native-questions", 1], ["agent-files-fresh-run", 2], ["restrictive-denial", 1], ["human-permission-denial", 1],
]);
expect(cells.filter(row => row.environment.id === "daytona").map(row => [row.task.id, row.task.expectedRunCount])).toEqual([
["native-questions", 1], ["agent-files-fresh-run", 2], ["human-permission-denial", 1],
]);
expect(cells.reduce((sum, row) => sum + row.task.expectedRunCount, 0)).toBe(9);
expect(selectRunnerExecutions(parseRunnerSelectors(["--all"])).some(row => row.suite.id === suite.id)).toBe(false);
expect(runnerMatrix.filter(row => row.suite.id === "extended-harnesses")).toHaveLength(30);
expect(piNativeTasks[0]!.buildPrompt("fixture")).toContain("paperclip_native_question");
expect(piNativeTasks[1]!.buildPrompt("fixture")).toContain("AGENT_HOME");
});
it("admits only the explicit local Pi native candidate and discards ambient admission", () => {
it("admits explicit local and remote Pi native candidates and discards ambient admission", () => {
const cell = runnerMatrix.find(row => row.suite.id === "pi-native")!;
const source = { PAPERCLIP_RUNNER_ACPX_QUALIFICATION: "ambient" };
expect(JSON.parse(buildRunnerE2EProcessEnvironment(source, [cell]).PAPERCLIP_RUNNER_ACPX_QUALIFICATION!)).toEqual([{ agent: "pi", model: cell.profile.model }]);
const remote = runnerMatrix.find(row => row.suite.id === "pi-native" && row.environment.id === "daytona")!;
expect(JSON.parse(buildRunnerE2EProcessEnvironment(source, [remote]).PAPERCLIP_RUNNER_ACPX_QUALIFICATION!)).toEqual([{ agent: "pi", model: remote.profile.model }]);
expect(buildRunnerE2EProcessEnvironment(source, []).PAPERCLIP_RUNNER_ACPX_QUALIFICATION).toBeUndefined();
for (const changed of [
{ ...cell, suite: { ...cell.suite, manualOnly: false } },
{ ...cell, suite: { ...cell.suite, id: "implicit" } },
{ ...cell, environment: { ...cell.environment, id: "daytona" as const } },
{ ...cell, profile: { ...cell.profile, qualificationCandidate: "copilot" as const } },
]) expect(() => buildRunnerE2EProcessEnvironment(source, [changed])).toThrow("explicit provider qualification suite");
});
+6
View File
@@ -5,6 +5,7 @@ export const piNativeTasks: readonly RunnerTaskFixture[] = [
["native-questions", "Four native questions survive browser reconnect", 1],
["agent-files-fresh-run", "Agent files save and survive a fresh task", 2],
["restrictive-denial", "Restrictive native write denial prevents a file effect", 1],
["human-permission-denial", "Browser denial prevents a native write through process retirement", 1],
].map(([id, label, turns]) => ({
id: String(id), label: String(label), groups: [], workMode: "standard", flow: "pi_native",
expectedRunCount: Number(turns), attemptTimeoutMs: { local: 5 * 60_000, daytona: 5 * 60_000 },
@@ -35,6 +36,11 @@ export function piNativePrompt(id: string, nonce: string): string {
"Do not claim that the file is already saved to managed storage; Paperclip collects it after the provider stops.",
piNativeFinish("PI-NATIVE-MEMORY-STAGED"),
].join("\n");
if (id === "human-permission-denial") return [
"Attempt native write exactly once with relative path pi-human-denied.txt and content forbidden. Wait for the actual browser permission decision. Do not retry, use bash or an API, write another path, or work around denial.",
"After the native write returns its denial, finish this negative test. Native denial is the expected result; never claim that the file was written.",
piNativeFinish(`PI-NATIVE-${id}-${nonce}`),
].join("\n");
if (id === "restrictive-denial") return [
"This fixture deliberately denies native writes. Attempt the native write tool exactly once, with relative path pi-denied.txt and content forbidden. Do not use bash, an API, another path, or a retry to work around the denial.",
"After the actual tool failure, report completion of this negative test through the assigned semantic tool. A failed native write is the expected outcome, not a reason to repeat it.",
+33 -7
View File
@@ -3,7 +3,7 @@ import { mkdtemp, mkdir, readFile, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, describe, expect, it, vi } from "vitest";
import { runnerMatrix } from "./catalog.js";
import { piNativeTasks } from "./pi-native-cases.js";
import { packageEvidence } from "./evidence.js";
import { runPiNativeFlow } from "./pi-native-flow.js";
import { sanitizeJson } from "./redaction.js";
@@ -22,16 +22,16 @@ const roots: string[] = [];
afterEach(async () => { await Promise.all(roots.splice(0).map(root => rm(root, { recursive: true, force: true }))); });
type FlowInput = Parameters<typeof runPiNativeFlow>[0];
async function fixture(options: { failComments?: boolean } = {}) {
async function fixture(options: { failComments?: boolean; remote?: boolean; incompleteRemote?: boolean } = {}) {
const root = await mkdtemp(join(tmpdir(), "pi-native-evidence-")); roots.push(root);
const privateDir = join(root, "private"); const uploadDir = join(root, "upload");
const workspacePath = join(root, "workspace"); const snapshots = join(privateDir, "snapshots");
for (const directory of [workspacePath, snapshots, join(privateDir, "html-report"), join(privateDir, "blob-report")]) await mkdir(directory, { recursive: true });
const execution = runnerMatrix.find(row => row.id === "pi-native.runner-acpx-pi.local.native-questions")!;
const execution = { id: "pi-native.runner-acpx-pi.local.native-questions", environment: { id: options.remote ? "daytona" : "local" }, profile: { qualificationCandidate: "pi" }, task: piNativeTasks.find(row => row.id === "native-questions")! } as FlowInput["execution"];
const issue = { id: "issue-fixture", identifier: "PI-1", title: execution.task.buildTitle("fixture"), status: "done" };
const run = { id: "run-fixture", status: "succeeded", runtimeMode: "native", createdAt: "2026-09-29T00:00:00Z" };
const headers = ["Pi native color", "Pi native confirmation", "Pi native name", "Pi native draft"];
let answered = 0; const typed: string[] = [];
let answered = 0; const typed: string[] = []; let answerProof = ""; let remoteFinished = false; const cleanupAssertions: Array<() => Promise<unknown>> = [];
const interactions = () => headers.slice(0, Math.min(4, answered + 1)).map((header, index) => ({
id: `question-${index}`, kind: "ask_user_questions", status: index < answered ? "answered" : "pending",
sourceRunId: run.id, continuationPolicy: "none",
@@ -70,24 +70,38 @@ async function fixture(options: { failComments?: boolean } = {}) {
getByRole: (role: string) => ({ last: () => ({ click: async () => {
if (role !== "button") return;
answered += 1;
if (answered === 4) await writeFile(join(workspacePath, "pi-native-answers.json"), JSON.stringify([
if (answered === 4) { answerProof = JSON.stringify([
{ status: "answered", optionId: "blue" }, { status: "negative_or_cancelled", confirmed: false },
{ status: "answered", value: typed[0] }, { status: "answered", value: typed[1] },
]));
]); await writeFile(join(workspacePath, "pi-native-answers.json"), options.remote ? "WRONG HOST COPYBACK" : answerProof); }
} }) }),
};
const evidence = async (name: string, data: unknown) => { await writeFile(join(snapshots, name), JSON.stringify(sanitizeJson(data, [secret]))); };
const capture = vi.fn(async (_id: string, _label: string, file: string) => { await writeFile(join(privateDir, file), "fixture screenshot boundary"); });
const remoteRoot = { pid: 10, ppid: 1, startTicks: "100", bootId: "boot" };
const remoteSnapshot = { observedAtMs: 2, complete: !options.incompleteRemote, workspace: {}, targets: {}, watcher: { complete: true, targetMutationCount: 0, workspaceMutationCount: 0 }, processes: { captured: true, root: remoteRoot, journal: [remoteRoot], live: [] } };
const remoteFixture = { binding: { runId: run.id }, remoteCwd: "/owned/remote", outsideTarget: null,
snapshot: async () => remoteSnapshot,
finish: async () => { remoteFinished = true; return remoteSnapshot; },
readFile: async (path: string) => { expect(remoteFinished).toBe(true); expect(path).toBe("pi-native-answers.json"); return Buffer.from(answerProof); },
close: vi.fn(async () => {}),
};
const input = {
page: page as unknown as FlowInput["page"], api: api as unknown as FlowInput["api"],
fixtures: { company: { id: "company-fixture", issuePrefix: "PI" }, agent: { name: "Pi fixture" }, environment: { id: "environment-fixture" } } as FlowInput["fixtures"],
execution, nonce: "fixture", workspacePath, deadlineAt: Date.now() + 5000,
restart: async () => { throw new Error("Unexpected restart"); }, observe: () => {}, capture, evidence,
...(options.remote ? { registerCleanupAssertion: (fn: () => Promise<unknown>) => cleanupAssertions.push(fn), remoteBootstrap: {
prompt: () => "Read the bootstrap file only",
bindAndRelease: async (value: { targets: string[]; actionPrompt(fixture: any): Promise<string> | string }) => {
expect(value.targets).toEqual(["pi-native-answers.json"]); const prompt = await value.actionPrompt(remoteFixture); expect(prompt).toContain("paperclip_native_question"); return remoteFixture;
},
} } : {}),
};
for (const [file, content] of [["result.json", "{}"], ["snapshots/fixtures.json", "{}"], ["junit.xml", "<testsuites/>"], ["html-report/index.html", "fixture report"]]) await writeFile(join(privateDir, file!), content!);
const blob = join(privateDir, "blob-report"); await writeFile(join(blob, "fixture.txt"), "unit fixture");
execFileSync("zip", ["-q", "report.zip", "fixture.txt"], { cwd: blob });
return { input, privateDir, uploadDir, snapshots, paths, capture, issue, run, secret };
return { input: input as FlowInput, privateDir, uploadDir, snapshots, paths, capture, issue, run, secret, cleanupAssertions };
}
describe("Pi native terminal evidence", () => {
@@ -127,3 +141,15 @@ describe("Pi native terminal evidence", () => {
expect(packaged.missing).toEqual(["final-state.png", "snapshots/api-state.json"]);
});
});
it("uses sealed remote answer bytes rather than host copyback and requires independent retirement", async () => {
const f = await fixture({ remote: true }); const result = await runPiNativeFlow(f.input);
expect(result.checks.every(check => check.passed)).toBe(true); expect(f.cleanupAssertions).toHaveLength(1); await f.cleanupAssertions[0]!();
const retained = JSON.parse(await readFile(join(f.snapshots, "pi-remote-1-questions-final.json"), "utf8")); expect(retained.snapshot.processes.live).toEqual([]);
});
it("cannot replace missing remote retirement proof with a succeeded Product run", async () => {
const f = await fixture({ remote: true, incompleteRemote: true });
await expect(runPiNativeFlow(f.input)).rejects.toThrow("retirement is unproven");
await expect(f.cleanupAssertions[0]!()).rejects.toThrow("retirement proof incomplete");
});
+56
View File
@@ -0,0 +1,56 @@
/** Public durable permission/tool evidence. Independent filesystem and process
* proofs remain required; assistant text can never satisfy this oracle. */
type Row = Record<string, any>;
const record = (value: unknown): Row => value !== null && typeof value === "object" && !Array.isArray(value) ? value as Row : {};
export interface PiDeniedPermission { runId: string; turnId: string; requestId: string; toolCallId: string; target: string }
export function piPermissionRequests(rows: readonly unknown[], runId: string): Array<{ row: Row; event: Row; request: Row }> {
return rows.map(record).flatMap(row => {
const event = record(record(row.payload).prpEvent), request = record(record(event.payload).request), origin = record(request.origin);
if (row.eventType !== "runtime_request.created" || event.eventType !== row.eventType || row.runId !== runId || event.runId !== runId
|| row.protocolSchemaVersion !== 1 || event.schemaVersion !== 1 || event.schema !== "paperclip.prp.event.v1" || event.sourceKind !== "runner"
|| (!Number.isSafeInteger(row.seq) || row.seq < 1) || typeof event.turnId !== "string" || !event.turnId || request.turnId !== event.turnId
|| request.type !== "permission" || request.status !== "pending" || origin.adapter !== "acpx-runtime" || origin.provider !== "pi" || origin.method !== "session/request_permission"
|| typeof request.requestId !== "string" || !request.requestId || typeof request.details?.toolCallId !== "string" || !request.details.toolCallId
|| !Array.isArray(request.choices) || !request.choices.some((choice: unknown) => record(choice).key === "decline")) return [];
return [{ row, event, request }];
});
}
export function hasDeliveredPiDenial(rows: readonly unknown[], expected: PiDeniedPermission): boolean {
const requests = piPermissionRequests(rows, expected.runId).filter(value => value.request.requestId === expected.requestId);
if (requests.length !== 1) return false;
const created = requests[0]!;
if (created.event.turnId !== expected.turnId || created.request.details.toolCallId !== expected.toolCallId) return false;
const events = rows.map(record).map(row => ({ row, event: record(record(row.payload).prpEvent) }))
.filter(({ row, event }) => row.runId === expected.runId && event.runId === expected.runId && event.turnId === expected.turnId
&& row.eventType === event.eventType && row.protocolSchemaVersion === 1 && event.schemaVersion === 1
&& event.schema === "paperclip.prp.event.v1" && event.sourceKind === "runner" && Number.isSafeInteger(row.seq) && row.seq >= 1);
const resolutions = events.filter(({ event }) => ["runtime_request.resolved", "runtime_request.expired", "runtime_request.cancelled"].includes(event.eventType) && event.payload?.requestId === expected.requestId);
if (resolutions.length !== 1 || resolutions[0]!.event.eventType !== "runtime_request.resolved" || resolutions[0]!.event.payload.action !== "decline"
|| resolutions[0]!.event.payload.turnId !== expected.turnId || resolutions[0]!.row.seq <= created.row.seq) return false;
const completed = events.filter(({ event }) => event.eventType === "tool.execution.completed" && event.payload?.transport === "builtin" && event.payload?.operation === "edit");
return completed.length === 1 && completed.every(({ row, event }) => row.seq > created.row.seq && event.payload.schema === "paperclip.tool.execution.v1"
&& event.payload.executionId === expected.toolCallId && event.payload.name === "write" && event.payload.status === "failed"
&& event.payload.target === expected.target && typeof event.payload.output === "string" && event.payload.output.includes("Pi operation was denied or cancelled"));
}
/** The shared remote observer supplies these facts directly from the bound
* sandbox. Missing watcher/process coverage is never treated as an absent file. */
export function hasPiRemoteRetirement(value: unknown): boolean {
const snapshot = record(value), processes = record(snapshot.processes), root = record(processes.root);
return snapshot.complete === true && record(snapshot.watcher).complete === true && Number.isFinite(snapshot.observedAtMs)
&& processes.captured === true && Number.isSafeInteger(root.pid) && root.pid > 1
&& typeof root.startTicks === "string" && root.startTicks.length > 0 && typeof root.bootId === "string" && root.bootId.length > 0
&& Array.isArray(processes.journal) && processes.journal.length > 0
&& processes.journal.some((entry: Row) => entry.pid === root.pid && entry.startTicks === root.startTicks && entry.bootId === root.bootId)
&& Array.isArray(processes.live) && processes.live.length === 0;
}
export function hasUnchangedPiRemoteTarget(beforeValue: unknown, afterValue: unknown, target: string): boolean {
const before = record(beforeValue), after = record(afterValue), first = record(record(before.targets)[target]), last = record(record(after.targets)[target]);
return before.complete === true && hasPiRemoteRetirement(after) && before.observedAtMs <= after.observedAtMs
&& first.complete === true && last.complete === true && record(after.watcher).complete === true
&& first.mutationCount === 0 && last.mutationCount === 0
&& typeof first.absent === "boolean" && first.absent === last.absent && first.sha256 === last.sha256
&& (first.absent ? first.sha256 === null : typeof first.sha256 === "string" && /^sha256:[a-f0-9]{64}$/.test(first.sha256))
&& record(first.parent).dev !== undefined && record(first.parent).ino !== undefined
&& first.parent.dev === record(last.parent).dev && first.parent.ino === record(last.parent).ino;
}
+101 -10
View File
@@ -1,3 +1,5 @@
import { observeRunProcesses, watchDeniedTarget } from "./copilot-local-fixtures.js";
import { hasDeliveredPiDenial, piPermissionRequests, hasPiRemoteRetirement, hasUnchangedPiRemoteTarget } from "./pi-native-evidence.js";
import { randomBytes } from "node:crypto";
import { lstat, readFile } from "node:fs/promises";
import { join, resolve } from "node:path";
@@ -9,6 +11,13 @@ import { gradePiNativeAnswers, hasFailedPiWrite, hasPiCrossRootDenial, PI_NATIVE
import type { LiveFixtureValues } from "./live-fixtures.js";
import type { MatrixExecution } from "./types.js";
import type { RemoteNativeFixture, RemoteNativeSnapshot } from "./remote-native-fixtures.js";
export interface PiRemoteBootstrap {
prompt(nonce: string): string;
bindAndRelease(input: { issueId: string; runId: string; targets: string[]; crossRoot?: { initialText: string }; actionPrompt(fixture: RemoteNativeFixture): string | Promise<string> }): Promise<RemoteNativeFixture>;
}
type Row = Record<string, any>;
type Check = { id: string; passed: boolean; detail: string };
@@ -18,9 +27,26 @@ export async function runPiNativeFlow(input: {
observe(issue: Row, runs: Row[]): void;
capture(id: string, label: string, file: string): Promise<void>;
evidence(name: string, data: unknown): Promise<void>;
remoteBootstrap?: PiRemoteBootstrap;
registerCleanupAssertion?(assertion: () => Promise<Check[]>): void;
}) {
const { page, api, fixtures, execution, nonce } = input;
if (execution.environment.id !== "local" || execution.profile.qualificationCandidate !== "pi") throw new Error("Pi native fixtures require the isolated local Pi candidate");
const remote = execution.environment.id === "daytona";
if (!["local", "daytona"].includes(execution.environment.id) || execution.profile.qualificationCandidate !== "pi") throw new Error("Pi native fixtures require an isolated Pi candidate");
if (remote && (!input.remoteBootstrap || !input.registerCleanupAssertion)) throw new Error("Pi Daytona requires owned remote bootstrap and pre-delete retirement proof");
if (remote && execution.task.id === "restrictive-denial") throw new Error("Pi deny-all cannot read the native action-file bootstrap; remote auto-denial remains unsupported");
let currentRemote: RemoteNativeFixture | undefined, currentBaseline: RemoteNativeSnapshot | undefined;
let remoteSequence = 0;
async function finishRemote(label: string) {
if (!currentRemote) throw new Error("Missing exact owned remote fixture");
const snapshot = await currentRemote.finish();
await input.evidence(`pi-remote-${remoteSequence}-${label}.json`, { binding: currentRemote.binding, baseline: currentBaseline, snapshot });
if (!hasPiRemoteRetirement(snapshot)) throw new Error("Pi remote provider retirement is unproven; sandbox deletion is not proof");
return snapshot;
}
async function readWorkspace(path: string) {
return remote ? (await currentRemote!.readFile(path)).toString("utf8") : await readFile(join(input.workspacePath, path), "utf8");
}
const checks: Check[] = []; let issue: Row = {}; let runs: Row[] = [];
const project = await api.post<Row>(`/api/companies/${fixtures.company.id}/projects`, {
name: `Pi native workspace ${nonce}`, executionWorkspacePolicy: { enabled: true, defaultMode: "shared_workspace", sharedWorkspaceConcurrency: "serialize", allowIssueOverride: false, environmentId: fixtures.environment.id, workspaceStrategy: { type: "project_primary" } },
@@ -38,11 +64,24 @@ export async function runPiNativeFlow(input: {
return { issue, runs, interactions };
};
const rejectFailure = (state: Awaited<ReturnType<typeof load>>) => state.runs.some(run => ["failed", "cancelled", "timed_out"].includes(run.status)) ? "Pi provider run failed" : undefined;
async function create(title: string, prompt: string) {
await createTaskThroughUi({ page, issuePrefix: fixtures.company.issuePrefix!, agentName: fixtures.agent.name, title, prompt, workMode: "standard", projectName: project.name });
async function create(title: string, prompt: string | ((fixture: RemoteNativeFixture) => string), options: { targets?: string[]; crossRoot?: { initialText: string } } = {}) {
const previous = new Set(runs.map(run => run.id));
const actualPrompt = remote ? input.remoteBootstrap!.prompt(`${nonce}-${++remoteSequence}`) : typeof prompt === "string" ? prompt : (() => { throw new Error("Local prompt cannot depend on remote fixture"); })();
await createTaskThroughUi({ page, issuePrefix: fixtures.company.issuePrefix!, agentName: fixtures.agent.name, title, prompt: actualPrompt, workMode: "standard", projectName: project.name });
const found = await pollUntil({ label: title, deadlineAt: input.deadlineAt, load: async () => (await api.get<Row[]>(`/api/companies/${fixtures.company.id}/issues?limit=100`)).find(row => row.title === title), accept: Boolean });
if (!found) throw new Error("Browser-created Pi task is absent"); issue = found; input.observe(issue, runs);
await page.goto(`/${fixtures.company.issuePrefix}/issues/${issue.identifier ?? issue.id}`);
if (remote) {
const state = await pollUntil({ label: "Pi remote bootstrap native run", deadlineAt: input.deadlineAt, load, reject: rejectFailure, accept: value => value.runs.filter(run => !previous.has(run.id)).length === 1 });
const run = state.runs.find(value => !previous.has(value.id))!;
currentRemote = await input.remoteBootstrap!.bindAndRelease({ issueId: issue.id, runId: run.id, targets: options.targets ?? [], crossRoot: options.crossRoot,
actionPrompt: async fixture => { currentBaseline = await fixture.snapshot("before-action"); await input.evidence(`pi-remote-${remoteSequence}-armed.json`, { binding: fixture.binding, baseline: currentBaseline }); return typeof prompt === "string" ? prompt : prompt(fixture); } });
const fixture = currentRemote, ordinal = remoteSequence;
input.registerCleanupAssertion!(async () => {
try { const snapshot = await fixture.finish(); const passed = hasPiRemoteRetirement(snapshot); await input.evidence(`pi-remote-${ordinal}-retirement.json`, { binding: fixture.binding, snapshot, passed }); if (!passed) throw new Error("Pi remote retirement proof incomplete"); return [{ id: `remote-retirement-${ordinal}`, passed, detail: "Independent remote observer sealed exact run retirement before public lease deletion" }]; }
finally { await fixture.close(); }
});
}
}
async function settle(count: number) {
const result = await pollUntil({ label: "Pi native task completion", deadlineAt: input.deadlineAt, load,
@@ -55,7 +94,7 @@ export async function runPiNativeFlow(input: {
try {
if (execution.task.id === "native-questions") {
const name = `name-${randomBytes(8).toString("hex")}`; const draft = `draft-${randomBytes(8).toString("hex")}\nSecond line`;
await create(execution.task.buildTitle(nonce), execution.task.buildPrompt(nonce));
await create(execution.task.buildTitle(nonce), execution.task.buildPrompt(nonce), { targets: ["pi-native-answers.json"] });
const seen = new Set<string>(); let runId: string | undefined;
const forms = [{ title: "Pi native color", label: "Blue" }, { title: "Pi native confirmation", label: "No" }, { title: "Pi native name", value: name }, { title: "Pi native draft", value: draft }];
for (const [index, form] of forms.entries()) {
@@ -77,32 +116,84 @@ export async function runPiNativeFlow(input: {
await input.evidence(`pi-native-question-${index}-answered.json`, resolved.interactions.find(row => row.id === card.id));
}
const final = await settle(1);
const proof = JSON.parse(await readFile(join(input.workspacePath, "pi-native-answers.json"), "utf8"));
if (remote) await finishRemote("questions-final");
const proof = JSON.parse(await readWorkspace("pi-native-answers.json"));
check("native-typed-delivery", gradePiNativeAnswers(proof, name, draft), "Independent workspace JSON contains the exact four typed native tool results, including undisclosed browser text");
check("four-durable-answers", final.interactions.length === 4 && final.interactions.every(row => row.status === "answered" && row.sourceRunId === runId), "All four saved questions were answered in the original provider run");
await input.evidence("pi-native-typed-proof.json", { proof, interactions: final.interactions, events: await events(runs[0]!.id) });
} else if (execution.task.id === "agent-files-fresh-run") {
const retained = randomBytes(16).toString("hex"); const outside = resolve(input.workspacePath, "..", `pi-unassigned-${nonce}.txt`);
check("unassigned-initially-absent", await absent(outside), "The isolated cross-root marker did not already exist");
await create(execution.task.buildTitle(nonce), `${execution.task.buildPrompt(retained)}\nBefore finishing, attempt native write exactly once to ${JSON.stringify(outside)} with content forbidden. This intentionally unassigned root must be denied. Never use bash or another operation to work around its failure.`);
if (!remote) check("unassigned-initially-absent", await absent(outside), "The isolated cross-root marker did not already exist");
const prompt = (outsidePath: string) => `${execution.task.buildPrompt(retained)}\nBefore finishing, attempt native write exactly once to ${JSON.stringify(outsidePath)} with content forbidden. This intentionally unassigned root must be denied. Never use bash or another operation to work around its failure.`;
await create(execution.task.buildTitle(nonce), remote ? fixture => { if (!fixture.outsideTarget) throw new Error("Remote cross-root target is absent"); return prompt(fixture.outsideTarget); } : prompt(outside), { crossRoot: { initialText: `unchanged-${randomBytes(16).toString("hex")}` } });
await settle(1);
const personal = await api.get<Row>(`/api/agents/${fixtures.agent.id}/instructions-bundle/file?path=${encodeURIComponent(PI_NATIVE_MEMORY_PATH)}`);
const firstEvents = await events(runs[0]!.id);
check("registered-file-saved", personal.content === `${retained}\n`, "Public managed-file API contains the exact native-write bytes");
check("stopped-save-receipt", firstEvents.some(row => row.eventType === "instruction_save" && row.payload?.state === "saved"), "Provider stop produced a durable file-save receipt");
check("cross-root-denied", await absent(outside) && hasPiCrossRootDenial(firstEvents), "A single native write recorded the exact cross-root denial reason and the isolated target remains absent");
const crossRootIntact = remote ? hasUnchangedPiRemoteTarget(currentBaseline, await finishRemote("cross-root-final"), "@cross-root") : await absent(outside);
check("cross-root-denied", crossRootIntact && hasPiCrossRootDenial(firstEvents), "A single native write recorded the exact cross-root denial reason and the isolated target remains unchanged");
await input.evidence("pi-agent-files-first-save.json", { personal, run: runs[0], events: firstEvents });
await input.restart();
await create(`Pi read persisted memory ${nonce}`, [
`Use native read to read ${PI_NATIVE_MEMORY_PATH} under the fresh registered AGENT_HOME. Read its exact current bytes; do not infer them from another task, conversation, or history.`,
"Use native write to copy those exact bytes into pi-agent-memory-proof.txt in the task workspace. This is an internal assertion file, not a deliverable. Do not change personal memory.",
piNativeFinish(execution.task.buildVisibleMarker(nonce)),
].join("\n"));
].join("\n"), { targets: ["pi-agent-memory-proof.txt"] });
await settle(2);
check("fresh-run-readback", await readFile(join(input.workspacePath, "pi-agent-memory-proof.txt"), "utf8") === `${retained}\n`, "A new issue after server restart copied the undisclosed saved agent-file bytes");
if (remote) await finishRemote("memory-readback-final");
check("fresh-run-readback", await readWorkspace("pi-agent-memory-proof.txt") === `${retained}\n`, "A new issue after server restart copied the undisclosed saved agent-file bytes");
const current = await api.get<Row>(`/api/agents/${fixtures.agent.id}/instructions-bundle/file?path=${encodeURIComponent(PI_NATIVE_MEMORY_PATH)}`);
check("persistent-bytes-unchanged", current.content === personal.content, "Fresh-run readback preserved the saved managed bytes");
await input.evidence("pi-agent-files-fresh-read.json", { current, runs, events: await events(runs[1]!.id) });
} else if (execution.task.id === "human-permission-denial") {
if (!input.registerCleanupAssertion) throw new Error("Pi human denial requires post-retirement cleanup assertions");
const agent = await api.get<Row>(`/api/agents/${fixtures.agent.id}`);
const configured = await api.patch<Row>(`/api/agents/${fixtures.agent.id}`, { adapterConfig: { ...agent.adapterConfig, acpxPermissionMode: "approve-reads", lifecycleMode: "per_turn", timeoutSec: 120 } });
check("human-denial-policy", configured.adapterConfig.acpxPermissionMode === "approve-reads" && configured.adapterConfig.lifecycleMode === "per_turn", "Native write requires a browser permission decision in an owned per-turn process");
const target = "pi-human-denied.txt", path = join(input.workspacePath, target);
const watcher = remote ? null : watchDeniedTarget(input.workspacePath, target), observer = remote ? null : observeRunProcesses();
let processError = false, processAuthority: string | undefined;
const observe = () => {
const run = runs[0]; const authority = run?.processPid ? { pid: run.processPid, groupId: run.processGroupId, startedAt: run.processStartedAt, runId: run.id } : undefined;
if (authority) { const key = JSON.stringify(authority); if (processAuthority && processAuthority !== key) processError = true; processAuthority ??= key; }
return observer ? observer.sample(authority) : { captured: false, live: [] as number[], journal: [] };
};
let processes = observe();
const timer = remote ? undefined : setInterval(() => { try { processes = observe(); } catch { processError = true; } }, 250);
if (!remote) input.registerCleanupAssertion(async () => {
try {
await load(); processes = observe();
if (processes.captured && processes.live.length) processes = await pollUntil({ label: "Pi denied-write provider retirement", deadlineAt: Date.now() + 5000, load: async () => observe(), accept: value => value.live.length === 0 });
const fileAbsent = await absent(path), journal = watcher!.finish();
const passed = runs.length === 1 && runs[0]!.status === "succeeded" && processes.captured && processes.live.length === 0 && !processError && fileAbsent && journal.complete && journal.targetMutationCount === 0;
await input.evidence("pi-human-denial-retirement.json", { processes, processError, fileAbsent, journal, passed });
if (!passed) throw new Error("Pi human denial lacks independent no-effect and provider-retirement proof");
return [{ id: "human-denial-through-retirement", passed, detail: "Browser-denied target stayed absent through exact owned provider-process retirement" }];
} finally { clearInterval(timer); watcher!.finish(); }
});
if (!remote) check("human-target-initially-absent", await absent(path), "Independent target is absent before provider work");
await create(execution.task.buildTitle(nonce), execution.task.buildPrompt(nonce), { targets: [target] });
if (remote) check("human-target-initially-absent", currentBaseline?.targets[target]?.absent === true && currentBaseline.targets[target]!.complete, "Remote watcher was armed before action publication with an absent target");
const pending = await pollUntil({ label: "Pi native browser permission", deadlineAt: input.deadlineAt, load: async () => { const state = await load(); processes = observe(); return { ...state, events: state.runs.length === 1 ? await events(state.runs[0]!.id) : [] }; }, reject: rejectFailure,
accept: state => state.runs.length === 1 && piPermissionRequests(state.events, state.runs[0]!.id).length === 1 });
const native = piPermissionRequests(pending.events, pending.runs[0]!.id)[0]!;
const identity = { runId: pending.runs[0]!.id, turnId: native.event.turnId, requestId: native.request.requestId, toolCallId: native.request.details.toolCallId, target };
check("human-target-pending-absent", remote ? (await currentRemote!.snapshot("permission-pending")).targets[target]?.absent === true : await absent(path), "Pending native write has no file effect");
await page.reload();
const before = await events(identity.runId);
check("human-permission-reconnect", piPermissionRequests(before, identity.runId).some(value => value.request.requestId === identity.requestId) && !before.some(row => row.payload?.prpEvent?.payload?.requestId === identity.requestId && ["runtime_request.resolved", "runtime_request.expired", "runtime_request.cancelled"].includes(row.eventType)), "Reload retained the exact unanswered native permission");
const card = page.getByTestId("task-chat-runtime-request").filter({ visible: true }); await expect(card).toHaveCount(1);
await input.capture("pi-human-denial", "Pi native permission awaiting browser denial", "pi-human-denial.png");
const route = `/api/heartbeat-runs/${identity.runId}/runtime-requests/${encodeURIComponent(identity.requestId)}/resolve`;
const posted = page.waitForRequest(request => new URL(request.url()).pathname === route && request.method() === "POST");
await card.getByRole("button", { name: native.request.choices.find((choice: Row) => choice.key === "decline").label, exact: true }).click();
const response = (await posted).postDataJSON();
check("human-exact-browser-decline", response.turnId === identity.turnId && response.requestKind === "permission_approval" && response.resolution?.action === "decline", "Actual browser POST declines this run, turn and request");
const final = await settle(1), runEvents = await events(identity.runId);
check("human-native-denial-delivered", hasDeliveredPiDenial(runEvents, identity), "Public durable delivery and same native write failure prove actual rejection");
check("human-target-terminal-absent", remote ? hasUnchangedPiRemoteTarget(currentBaseline, await finishRemote("human-denial-final"), target) : await absent(path), "The target remains absent after negative-test completion");
await input.evidence("pi-human-denial.json", { identity, final, events: runEvents });
} else if (execution.task.id === "restrictive-denial") {
const agent = await api.get<Row>(`/api/agents/${fixtures.agent.id}`);
await api.patch(`/api/agents/${fixtures.agent.id}`, { adapterConfig: { ...agent.adapterConfig, acpxPermissionMode: "deny-all" } });
@@ -0,0 +1,60 @@
import { mkdtemp, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { expect, it, vi } from "vitest";
import { piNativeTasks } from "./pi-native-cases.js";
import { runPiNativeFlow } from "./pi-native-flow.js";
const proof = vi.hoisted(() => ({ mutation: false, live: false }));
vi.mock("./user-actions.js", () => ({ createTaskThroughUi: vi.fn() }));
vi.mock("./copilot-local-fixtures.js", () => ({
watchDeniedTarget: () => ({ finish: () => ({ complete: true, targetMutationCount: proof.mutation ? 1 : 0 }) }),
observeRunProcesses: () => ({ sample: () => ({ captured: true, live: proof.live ? [123] : [], journal: [] }) }),
}));
vi.mock("@playwright/test", () => ({ expect: (actual: any, message?: string) => ({
toBe: (value: unknown) => expect(actual, message).toBe(value), toBeVisible: async () => {}, toHaveCount: async (value: number) => expect(actual.count).toBe(value),
}) }));
const wrap = (eventType: string, seq: number, payload: unknown) => ({ runId: "run", protocolSchemaVersion: 1, eventType, seq, payload: { prpEvent: { schema: "paperclip.prp.event.v1", schemaVersion: 1, sourceKind: "runner", runId: "run", turnId: "turn", eventType, payload } } });
async function exercise(mutation: boolean, remote = false) {
proof.mutation = mutation; proof.live = false;
const workspacePath = await mkdtemp(join(tmpdir(), "pi-human-fixture-"));
let declined = false, browserPosts = 0; const saved = new Map<string, any>(); const cleanup: Array<() => Promise<any>> = [];
const task = piNativeTasks.find(row => row.id === "human-permission-denial")!;
const issue = () => ({ id: "issue", identifier: "PI-1", title: task.buildTitle("fixture"), status: declined ? "done" : "in_progress" });
const run = () => ({ id: "run", status: declined ? "succeeded" : "running", runtimeMode: "native", processPid: 123, processGroupId: 123, processStartedAt: "2026-09-29T00:00:00Z" });
const request = { requestId: "request", turnId: "turn", type: "permission", status: "pending", details: { toolCallId: "pi-tool-1" }, origin: { adapter: "acpx-runtime", provider: "pi", method: "session/request_permission" }, choices: [{ key: "decline", label: "Decline" }] };
const events = () => [wrap("runtime_request.created", 1, { request }), ...(declined ? [wrap("runtime_request.resolved", 2, { requestId: "request", turnId: "turn", action: "decline" }), wrap("tool.execution.completed", 3, { schema: "paperclip.tool.execution.v1", transport: "builtin", operation: "edit", executionId: "pi-tool-1", name: "write", target: "pi-human-denied.txt", status: "failed", output: "Pi operation was denied or cancelled" })] : [])];
const api = {
post: async () => ({ name: "Pi fixture project" }), patch: async (_path: string, value: any) => value,
get: async (path: string) => {
if (path === "/api/agents/agent") return { adapterConfig: {} };
if (path.endsWith("/issues?limit=100")) return [issue()];
if (path === "/api/issues/issue") return issue();
if (path.endsWith("/heartbeat-runs?limit=100")) return [run()];
if (path === "/api/heartbeat-runs/run") return run();
if (path.endsWith("/interactions") || path.endsWith("/comments")) return [];
if (path.includes("/events?")) return events();
throw new Error(`Unexpected fixture API ${path}`);
},
};
const browserRequest = { url: () => "http://fixture/api/heartbeat-runs/run/runtime-requests/request/resolve", method: () => "POST", postDataJSON: () => ({ turnId: "turn", requestKind: "permission_approval", resolution: { action: "decline" } }) };
let resolvePost: ((value: typeof browserRequest) => void) | undefined;
const page = { goto: async () => {}, reload: async () => {}, waitForRequest: (predicate: (v: typeof browserRequest) => boolean) => new Promise(resolve => { expect(predicate(browserRequest)).toBe(true); resolvePost = resolve; }),
getByTestId: (id: string) => id === "issue-detail-header" ? { getByRole: () => ({}) } : { filter: () => ({ count: 1, getByRole: (_role: string, options: { name: string }) => ({ click: async () => { expect(options.name).toBe("Decline"); browserPosts++; declined = true; resolvePost!(browserRequest); } }) }) } };
const nativeRoot = { pid: 456, ppid: 1, startTicks: "123", bootId: "remote-boot" };
const snapshot = () => ({ observedAtMs: declined ? 2 : 1, complete: true, workspace: {}, targets: { "pi-human-denied.txt": { absent: true, sha256: null, parent: { dev: "1", ino: "2" }, mutationCount: mutation ? 1 : 0, complete: true } }, watcher: { complete: true, targetMutationCount: mutation ? 1 : 0, workspaceMutationCount: 0 }, processes: { captured: true, root: nativeRoot, journal: [nativeRoot], live: declined ? [] : [456] } });
const fixture = { binding: { runId: "run" }, remoteCwd: "/remote", outsideTarget: null, snapshot: async () => snapshot(), finish: async () => snapshot(), close: async () => {} };
const remoteBootstrap = remote ? { prompt: () => "Read bootstrap only", bindAndRelease: async (input: any) => { expect(input.targets).toEqual(["pi-human-denied.txt"]); expect(await input.actionPrompt(fixture)).toContain("native write exactly once"); return fixture; } } : undefined;
try {
if (remote) await writeFile(join(workspacePath, "pi-human-denied.txt"), "POISON HOST COPYBACK");
const result = await runPiNativeFlow({ page, api, fixtures: { company: { id: "company", issuePrefix: "PI" }, agent: { id: "agent", name: "Pi" }, environment: { id: "environment" } }, execution: { task, environment: { id: remote ? "daytona" : "local" }, profile: { qualificationCandidate: "pi" } }, workspacePath, nonce: "fixture", deadlineAt: Date.now() + 2000, restart: async () => {}, observe: () => {}, capture: async () => {}, evidence: async (name: string, value: unknown) => { saved.set(name, value); }, remoteBootstrap, registerCleanupAssertion: (fn: () => Promise<any>) => cleanup.push(fn) } as any);
expect(browserPosts).toBe(1); expect(result.checks.every(check => check.passed)).toBe(true); expect(saved.has("api-state.json")).toBe(true);
expect(cleanup).toHaveLength(1);
if (mutation) await expect(cleanup[0]!()).rejects.toThrow("no-effect");
else expect((await cleanup[0]!())[0].passed).toBe(true);
expect(saved.get(remote ? "pi-remote-1-retirement.json" : "pi-human-denial-retirement.json").passed).toBe(!mutation);
} finally { await rm(workspacePath, { recursive: true, force: true }); }
}
it("drives actual browser-denial flow and independent retirement proof", async () => exercise(false));
it("rejects an observed create/delete even when final target is absent", async () => exercise(true));
it("proves browser denial against remote watcher and retirement without trusting a host file", async () => exercise(false, true));
@@ -0,0 +1,53 @@
import { expect, it } from "vitest";
import { hasDeliveredPiDenial, piPermissionRequests } from "./pi-native-evidence.js";
const identity = { runId: "run", turnId: "turn", requestId: "permission", toolCallId: "pi-tool-1", target: "pi-human-denied.txt" };
function evidence() {
const wrap = (eventType: string, seq: number, payload: unknown) => ({ runId: "run", protocolSchemaVersion: 1, eventType, seq, payload: { prpEvent: {
schema: "paperclip.prp.event.v1", schemaVersion: 1, sourceKind: "runner", runId: "run", turnId: "turn", eventType, payload,
} } });
return [wrap("runtime_request.created", 1, { request: { requestId: "permission", turnId: "turn", type: "permission", status: "pending", details: { toolCallId: "pi-tool-1" },
origin: { adapter: "acpx-runtime", provider: "pi", method: "session/request_permission" }, choices: [{ key: "decline" }] } }),
wrap("runtime_request.resolved", 2, { requestId: "permission", turnId: "turn", action: "decline" }),
wrap("tool.execution.completed", 3, { schema: "paperclip.tool.execution.v1", executionId: "pi-tool-1", transport: "builtin", operation: "edit", name: "write", status: "failed", target: identity.target, output: "Pi operation was denied or cancelled" })];
}
it("requires the actual correlated durable browser denial and native tool failure", () => {
expect(piPermissionRequests(evidence(), "run")).toHaveLength(1);
expect(hasDeliveredPiDenial(evidence(), identity)).toBe(true);
for (const rows of [[], evidence().slice(1), evidence().slice(0, 2), [...evidence(), evidence()[2]!]]) expect(hasDeliveredPiDenial(rows, identity)).toBe(false);
});
it("rejects foreign identities, approvals, expiration, write success and policy-only failure", () => {
for (const key of ["runId", "turnId", "requestId", "toolCallId", "target"]) expect(hasDeliveredPiDenial(evidence(), { ...identity, [key]: "other" })).toBe(false);
for (const [index, patch] of [[1, { action: "accept" }], [2, { status: "completed" }], [2, { output: "outside its assigned workspace" }], [2, { executionId: "other" }], [2, { target: "other.txt" }]] as const) {
const rows = evidence(); Object.assign(rows[index]!.payload.prpEvent.payload as object, patch); expect(hasDeliveredPiDenial(rows, identity)).toBe(false);
}
for (const eventType of ["runtime_request.expired", "runtime_request.cancelled"]) {
const rows = evidence(); rows[1]!.eventType = eventType; rows[1]!.payload.prpEvent.eventType = eventType;
expect(hasDeliveredPiDenial(rows, identity)).toBe(false);
}
const malformed = evidence(); (malformed[0]!.payload.prpEvent.payload as any).request.choices = [null, {}];
expect(piPermissionRequests(malformed, "run")).toEqual([]);
const stale = evidence(); stale[1]!.seq = 0; expect(hasDeliveredPiDenial(stale, identity)).toBe(false);
const wrong = evidence(); wrong[2]!.payload.prpEvent.sourceKind = "agent"; expect(hasDeliveredPiDenial(wrong, identity)).toBe(false);
});
it("does not misorder native notifications that arrive before the public delivery receipt is persisted", () => {
const rows = evidence(); rows[1]!.seq = 3; rows[2]!.seq = 2;
// Both native failed invocation and post-write delivery receipt are required;
// asynchronous persistence order is not treated as native execution order.
expect(hasDeliveredPiDenial(rows, identity)).toBe(true);
});
import { hasPiRemoteRetirement, hasUnchangedPiRemoteTarget } from "./pi-native-evidence.js";
it("remote denial requires complete watching, stable parent and real process retirement before lease deletion", () => {
const root = { pid: 12, startTicks: "100", bootId: "boot" };
const before = { observedAtMs: 1, complete: true, targets: { denied: { absent: true, sha256: null, parent: { dev: "1", ino: "2" }, mutationCount: 0, complete: true } } };
const after = { ...structuredClone(before), observedAtMs: 2, watcher: { complete: true }, processes: { captured: true, root, journal: [root], live: [] as number[] } };
expect(hasUnchangedPiRemoteTarget(before, after, "denied")).toBe(true);
for (const broken of [{ ...after, complete: false }, { ...after, processes: { ...after.processes, live: [12] } }, { ...after, processes: { ...after.processes, captured: false } }, { ...after, processes: { ...after.processes, journal: [] } }, { ...after, watcher: { complete: false } }]) expect(hasUnchangedPiRemoteTarget(before, broken, "denied")).toBe(false);
for (const change of [{ mutationCount: 1 }, { absent: false }, { parent: { dev: "1", ino: "3" } }, { complete: false }]) {
expect(hasUnchangedPiRemoteTarget(before, { ...after, targets: { denied: { ...after.targets.denied, ...change } } }, "denied")).toBe(false);
}
expect(hasPiRemoteRetirement({ sandboxDeleted: true })).toBe(false);
expect(hasPiRemoteRetirement({ ...after, watcher: { complete: false } })).toBe(false);
const existing = structuredClone(before); existing.targets.denied.absent = false; (existing.targets.denied as any).sha256 = `sha256:${"a".repeat(64)}`;
expect(hasUnchangedPiRemoteTarget(existing, { ...after, targets: existing.targets }, "denied")).toBe(true);
});
@@ -0,0 +1,53 @@
import { createHash } from "node:crypto";
import { mkdtemp, rm, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { expect, it, vi } from "vitest";
import { piNativeTasks } from "./pi-native-cases.js";
import { runPiNativeFlow } from "./pi-native-flow.js";
const browser = vi.hoisted(() => ({ create: (_value: any) => {} }));
vi.mock("./user-actions.js", () => ({ createTaskThroughUi: async (value: unknown) => browser.create(value) }));
vi.mock("@playwright/test", () => ({ expect: (value: unknown, message?: string) => ({ toBe: (expected: unknown) => expect(value, message).toBe(expected), toBeVisible: async () => {} }) }));
it("rebinds both remote runs, saves managed bytes, and reads sealed sandbox bytes after restart", async () => {
const root = await mkdtemp(join(tmpdir(), "pi-remote-memory-"));
const task = piNativeTasks.find(row => row.id === "agent-files-fresh-run")!;
const issues: any[] = [], runs: any[] = [], cleanup: Array<() => Promise<any>> = []; const captures: any[] = [], evidence = new Map<string, any>();
let personal = "", restarted = false, readAfterFinish = false;
browser.create = value => {
expect(value.prompt).toMatch(/^Read only bootstrap-/); expect(value.prompt).not.toContain("forbidden");
const n = issues.length + 1; issues.push({ id: `issue-${n}`, title: value.title, status: "in_progress" }); runs.push({ id: `run-${n}`, status: "running", runtimeMode: "native", createdAt: `2026-09-29T00:00:0${n}Z` });
};
const api = { post: async () => ({ name: "Pi remote project" }), get: async (path: string) => {
if (path.endsWith("/issues?limit=100")) return issues;
if (path.endsWith("/heartbeat-runs?limit=100")) return runs;
if (/\/api\/issues\/issue-[12]$/.test(path)) return issues.find(row => path.endsWith(row.id));
if (/\/api\/heartbeat-runs\/run-[12]$/.test(path)) return runs.find(row => path.endsWith(row.id));
if (path.endsWith("/interactions") || path.endsWith("/comments")) return [];
if (path.includes("instructions-bundle/file?")) return { content: personal };
if (path.includes("/events?")) return path.includes("run-1/") ? [{ eventType: "instruction_save", seq: 1, payload: { state: "saved" } }, { eventType: "tool.execution.completed", seq: 2, payload: { prpEvent: { payload: { schema: "paperclip.tool.execution.v1", transport: "builtin", operation: "edit", name: "write", status: "failed", target: null, executionId: "tool-1", output: "Pi tool path is outside its assigned workspace and agent files" } } } }] : [];
throw new Error(`Unexpected fixture path ${path}`);
} };
const remoteBootstrap = { prompt: (nonce: string) => `Read only bootstrap-${nonce}`, bindAndRelease: async (input: any) => {
const ordinal = runs.length; expect(input.runId).toBe(`run-${ordinal}`);
const identity = { pid: 100 + ordinal, ppid: 1, startTicks: String(ordinal), bootId: `boot-${ordinal}` };
const targets: any = ordinal === 1 ? { "@cross-root": { absent: false, sha256: `sha256:${createHash("sha256").update(input.crossRoot.initialText).digest("hex")}`, parent: { dev: "1", ino: "2" }, mutationCount: 0, complete: true } } : {};
const snapshot = { observedAtMs: ordinal, complete: true, targets, workspace: {}, watcher: { complete: true, targetMutationCount: 0, workspaceMutationCount: 0 }, processes: { captured: true, root: identity, journal: [identity], live: [] } };
let armed = false, finished = false;
const fixture = { binding: { runId: input.runId }, remoteCwd: `/remote/run-${ordinal}`, outsideTarget: ordinal === 1 ? `/tmp/owned-${ordinal}/cross-root-target` : null,
snapshot: async () => { armed = true; return snapshot; }, finish: async () => { finished = true; return snapshot; }, close: async () => {},
readFile: async (path: string) => { expect(finished).toBe(true); expect(restarted).toBe(true); expect(path).toBe("pi-agent-memory-proof.txt"); readAfterFinish = true; return Buffer.from(personal); },
};
const action = await input.actionPrompt(fixture); expect(armed).toBe(true);
if (ordinal === 1) {
expect(action).toContain(fixture.outsideTarget); const literal = /Write exactly (".*?") to memory\/pi-native.txt/.exec(action)?.[1]; expect(literal).toBeDefined(); personal = JSON.parse(literal!); expect(personal).toMatch(/^[a-f0-9]{32}\n$/);
} else { expect(action).not.toContain(personal.trim()); expect(input.targets).toEqual(["pi-agent-memory-proof.txt"]); }
issues.at(-1).status = "done"; runs.at(-1).status = "succeeded"; captures.push(fixture.binding); return fixture;
} };
try {
await writeFile(join(root, "pi-agent-memory-proof.txt"), "WRONG HOST COPYBACK");
const result = await runPiNativeFlow({ page: { goto: async () => {}, reload: async () => {}, getByTestId: () => ({ getByRole: () => ({}) }) }, api, fixtures: { company: { id: "company", issuePrefix: "PI" }, agent: { id: "agent", name: "Pi" }, environment: { id: "daytona-env" } }, execution: { task, environment: { id: "daytona" }, profile: { qualificationCandidate: "pi" } }, nonce: "fixture", workspacePath: root, deadlineAt: Date.now() + 2000,
restart: async () => { expect(evidence.has("pi-remote-1-cross-root-final.json")).toBe(true); restarted = true; }, observe: () => {}, capture: async () => {}, evidence: async (name: string, value: unknown) => { evidence.set(name, value); }, remoteBootstrap, registerCleanupAssertion: (fn: () => Promise<any>) => cleanup.push(fn) } as any);
expect(result.checks.every(check => check.passed)).toBe(true); expect(captures).toEqual([{ runId: "run-1" }, { runId: "run-2" }]); expect(readAfterFinish).toBe(true); expect(cleanup).toHaveLength(2); for (const fn of cleanup) await fn();
} finally { await rm(root, { recursive: true, force: true }); }
});