fix(runner): reject unpublished workspace file receipts

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Dotta 2026-09-12 22:28:14 -05:00
parent 417a96011a
commit 0be8ba9d97
3 changed files with 11 additions and 6 deletions

View File

@ -1207,7 +1207,9 @@ that the task is complete.
For an explicit file output in the current request, an empty report, a
verification-only reference, or an unregistered URL cannot satisfy delivery.
The report must cite a task-scoped attachment or a registered accessible work
product. Reading or reviewing an existing file for an inline answer does not
product with a published URL. A `workspace_file` locator alone is not delivery
evidence: it neither verifies the file nor preserves its bytes after cleanup.
Reading or reviewing an existing file for an inline answer does not
require uploading that input. Ambiguous prose remains subject to the runner's
completion contract; the server's explicit-output check is deliberately narrow.

View File

@ -84,9 +84,10 @@ export async function validateNativeDeliverableEvidence(
)) : [];
const accessibleProduct = products.some(product => {
if (["failed", "cancelled", "archived"].includes(product.status)) return false;
const resource = product.metadata?.resourceRef as { kind?: unknown; path?: unknown } | undefined;
const accessible = (typeof product.url === "string" && /^https?:\/\//iu.test(product.url)) ||
(resource?.kind === "workspace_file" && typeof resource.path === "string" && resource.path.length > 0);
// A workspace_file resource is only a locator: registration neither checks
// its current bytes nor keeps them alive after workspace cleanup. Requested
// files need a published URL or the verified attachment receipt above.
const accessible = typeof product.url === "string" && /^https?:\/\//iu.test(product.url);
return accessible && [product.url, `work_product:${product.id}`, `work-product:${product.id}`, `artifact:${product.id}`]
.some(ref => typeof ref === "string" && refs.has(ref));
});

View File

@ -226,9 +226,11 @@ describe("native runner file handoff", () => {
await expect(nativeCompletionFeedback(db, runId, doneReport([ref])))
.resolves.toContain("Completion report accepted");
}
await db.update(issueWorkProducts).set({ url: null, metadata: { resourceRef: { kind: "workspace_file", path: "report.pdf" } } }).where(eq(issueWorkProducts.id, product.id));
// Metadata can name a nonexistent or cleaned-up workspace file; it is not a download.
await db.update(issueWorkProducts).set({ url: null, metadata: { resourceRef: { kind: "workspace_file", path: "missing-report.pdf" } } }).where(eq(issueWorkProducts.id, product.id));
await expect(nativeCompletionFeedback(db, runId, doneReport([`work_product:${product.id}`])))
.resolves.toContain("Completion report accepted");
.rejects.toThrow("requested file has no accessible delivery evidence");
await db.update(issueWorkProducts).set({ url: product.url }).where(eq(issueWorkProducts.id, product.id));
await db.update(issueWorkProducts).set({ status: "failed" }).where(eq(issueWorkProducts.id, product.id));
await expect(nativeCompletionFeedback(db, runId, doneReport([`work_product:${product.id}`])))
.rejects.toThrow("requested file has no accessible delivery evidence");