Read deliverable files before locking mutation rows (#17309)

This commit is contained in:
Nicky Leach authored and GitHub committed 2026-10-23 05:46:38 -07:00
1 parent 412914cfa7
commit 583ec2443a
3 files changed
+71 -34

No files matched your search

@@ -602,6 +602,40 @@ describe("native runner file handoff", () => {
.rejects.toThrow(/size|hash/);
});
it("reads provider files before mutation locks and replays without reading again", async () => {
const body = Buffer.from("Provider callback output\n");
const reader = vi.fn(async () => {
await db.transaction(async tx => {
await tx.execute(sql`set local lock_timeout = '500ms'`);
await tx.select({ id: agents.id }).from(agents).where(eq(agents.id, agentId)).for("share");
});
return body;
});
const remote = authority({ executionTargetKind: "remote", readRemoteTaskFile: reader });
const call = callFor("proof.txt", body, "provider-lock-callback");
const result = await remote.execute(call);
reader.mockRejectedValueOnce(new Error("The original file is no longer available"));
await expect(remote.execute(call)).resolves.toEqual(result);
expect(reader).toHaveBeenCalledTimes(1);
});
it("rejects publication if agent authority changes during a provider read", async () => {
const body = Buffer.from("Revoked output\n");
const before = await db.select({ id: issueAttachments.id }).from(issueAttachments);
const reader = vi.fn(async () => {
await db.update(agents).set({ status: "paused" }).where(eq(agents.id, agentId));
return body;
});
try {
await expect(authority({ executionTargetKind: "remote", readRemoteTaskFile: reader })
.execute(callFor("proof.txt", body, "revoked-during-read")))
.rejects.toThrow("paperclip_runner_tool_binding_not_authorized");
expect(await db.select({ id: issueAttachments.id }).from(issueAttachments)).toEqual(before);
} finally {
await db.update(agents).set({ status: "active" }).where(eq(agents.id, agentId));
}
});
it("registers an absolute file only through a task-location-authorizing reader", async () => {
const body=Buffer.from("Home output\n");const reader=vi.fn(async()=>body);
const call=callFor("/home/agent/proof.txt",body,"home-output");
@@ -298,7 +298,7 @@ function sameFileIdentity(left: Stats, right: Stats): boolean {
);
}
async function readVerifiedWorkspaceFile(
export async function readVerifiedWorkspaceFile(
binding: NativeRunnerFileHandoffBinding,
input: NativeRunnerFileHandoffInput,
): Promise<VerifiedWorkspaceFile> {
@@ -1141,14 +1141,11 @@ async function assertCurrentBinding(
export async function prepareNativeRunnerFileHandoff(input: {
readonly db: Db;
readonly binding: NativeRunnerFileHandoffBinding;
readonly deliverable: NativeRunnerFileHandoffInput;
readonly verifiedFile: VerifiedWorkspaceFile;
readonly storage?: StorageService;
}): Promise<PreparedNativeRunnerFileHandoff> {
const { statusVersion } = await assertCurrentBinding(input.db, input.binding);
const verified = await readVerifiedWorkspaceFile(
input.binding,
input.deliverable,
);
const verified = input.verifiedFile;
const [existing] = await input.db
.select({
@@ -94,7 +94,7 @@ import { getNativeReviewAssignment, readNativeReviewAssignmentContext, type Nati
import { childReviewOutcomes } from "./child-review-outcomes.js";
import { logActivity, persistActivity, publishActivity } from "../activity-log.js";
import { captureRunIdentity } from "../run-identity.js";
import { prepareNativeRunnerFileHandoff, type RemoteWorkspaceFileReader } from "./native-runner-file-handoff.js";
import { prepareNativeRunnerFileHandoff, readVerifiedWorkspaceFile, type RemoteWorkspaceFileReader } from "./native-runner-file-handoff.js";
import { MAX_ATTACHMENT_BYTES } from "../../attachment-types.js";
import {
READ_CURRENT_WAKE_COMMENTS_TOOL_DEFINITION,
@@ -1927,6 +1927,35 @@ export class PaperclipRunnerToolAuthority {
if (!workspaceRoot) {
throw new Error("paperclip_runner_file_handoff_workspace_unavailable");
}
const current = await this.#boundContext();
const authorizeWorkspace = async (actor: typeof agents.$inferSelect) => {
if (this.binding.dotRuntime) {
if (!this.binding.workspaceBridge || actor.adapterConfig?.dotWorkspaceAccess !== true)
throw forbidden("Dot workspace access is required to publish workspace files.");
assertRunnerWorkspacePathAllowed(typeof input.contentRef === "string" ? input.contentRef : "");
await this.binding.assertBridgeAuthority?.();
}
};
await authorizeWorkspace(current.actor);
const fileBinding = {
companyId: this.binding.companyId,
issueId: this.binding.issueId,
runId: this.binding.runId,
agentId: this.binding.agentId,
workspaceRoot,
executionTargetKind: this.binding.executionTargetKind ?? "local",
readRemoteWorkspaceFile: this.binding.readRemoteWorkspaceFile,
readRemoteTaskFile: this.binding.readRemoteTaskFile,
} as const;
const prior = record(record(current.run.resultJson).semanticToolReceipts)[idempotencyKey];
const verifiedFile = prior === undefined ? await readVerifiedWorkspaceFile(fileBinding, {
filename: typeof input.filename === "string" ? input.filename : "",
contentType: typeof input.contentType === "string" ? input.contentType : "",
byteSize: typeof input.byteSize === "number" ? input.byteSize : Number.NaN,
sha256: typeof input.sha256 === "string" ? input.sha256 : "",
contentRef: typeof input.contentRef === "string" ? input.contentRef : "",
title: typeof input.title === "string" ? input.title : "",
}) : null;
let publication:
Awaited<ReturnType<typeof persistActivity>>["publication"] | null = null;
let rollbackDefinitePreCommitFailure: (() => Promise<void>) | null = null;
@@ -1935,35 +1964,12 @@ export class PaperclipRunnerToolAuthority {
idempotencyKey,
input,
async (tx, context) => {
if (this.binding.dotRuntime) {
if (!this.binding.workspaceBridge || context.actor.adapterConfig?.dotWorkspaceAccess !== true)
throw forbidden("Dot workspace access is required to publish workspace files.");
assertRunnerWorkspacePathAllowed(typeof input.contentRef === "string" ? input.contentRef : "");
await this.binding.assertBridgeAuthority?.();
}
await authorizeWorkspace(context.actor);
if (!verifiedFile) throw new Error("paperclip_runner_file_handoff_receipt_changed");
const prepared = await prepareNativeRunnerFileHandoff({
db: tx,
binding: {
companyId: this.binding.companyId,
issueId: this.binding.issueId,
runId: this.binding.runId,
agentId: this.binding.agentId,
workspaceRoot,
executionTargetKind: this.binding.executionTargetKind ?? "local",
readRemoteWorkspaceFile: this.binding.readRemoteWorkspaceFile,
readRemoteTaskFile: this.binding.readRemoteTaskFile,
},
deliverable: {
filename: typeof input.filename === "string" ? input.filename : "",
contentType:
typeof input.contentType === "string" ? input.contentType : "",
byteSize:
typeof input.byteSize === "number" ? input.byteSize : Number.NaN,
sha256: typeof input.sha256 === "string" ? input.sha256 : "",
contentRef:
typeof input.contentRef === "string" ? input.contentRef : "",
title: typeof input.title === "string" ? input.title : "",
},
binding: fileBinding,
verifiedFile,
storage: this.binding.storage,
});
rollbackDefinitePreCommitFailure =