test(runner): cover document guidance for file-only objectives (#18669)

## Thinking Path

> - Completion guidance can link a saved task document even when the
user requested only a file.
> - The validator loads document publication proof when the objective
requests a document or the completion report cites one.
> - The success tests added in #18661 all requested both a document and
a CSV.
> - Those tests therefore could not detect loss of the cited-document
branch.
> - This test requests only a CSV and resubmits the actual guidance link
with a registered file receipt.

## Linked Issues or Issue Description

Addresses the [coverage warning on
#18661](https://github.com/paperclipai/paperclip/pull/18661#discussion_r4447457009).

**What happened?** The earlier success cases exercised the
document-requested condition. The independently cited-document condition
lacked regression coverage.

**Expected behavior:** A file-only objective can cite a verified
saved-document guidance link alongside the required registered file.

**Steps to reproduce:** Publish a task document, register a CSV, request
only the CSV, obtain the saved-document href from completion guidance,
and submit both pieces of evidence. Removing only the cited-document
lookup must make this case fail.

**Paperclip version or commit:** Current master after #18661.

**Deployment mode:** Local source tests with real PostgreSQL and native
publication receipts; the remote CSV reader is controlled. No deployment
is part of this follow-up.

## What Changed

- Allow the existing registered-file test helper to accept a file-only
objective.
- Add one success case using real document publication, file
registration, and the server-generated guidance href.
- Keep production source and the existing negative cases unchanged.

## Verification

- All 27 native completion feedback tests pass on current production
code.
- The new case fails when only `|| citesTaskDocument` is temporarily
removed from publication lookup. The production file is restored exactly
afterward. This demonstrates a coverage gap; it does not establish a new
production defect.
- Server typecheck and `git diff --check` pass.
- Full repository CI passed, including both required E2E and verify
aggregate gates. Greptile scored the current head 5/5 with no unresolved
findings. The separate optional Paperclip review is queued and has
supplied no finding. The preceding production PR records its separate
local full-suite timeout and isolated passing retry; this follow-up uses
the focused local suite and full CI rather than repeating that long
local run.

## Risks

Low risk: only the existing test file and its fixture helper change. The
new case depends on real document/file publication contracts, including
the CSV receipt requirement.

## Model Used

OpenAI `gpt-6.1-sol` through Codex, with reasoning, code editing, shell
execution, and automated tests. The context-window size is not exposed
in this session.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip authored and GitHub committed 2026-11-02 15:33:46 -06:00
1 parent 45e91f097c
commit 0c48f5e727
1 file changed
+13 -2
@@ -32,8 +32,8 @@ describe("native final-response feedback", () => {
} }) as { document: { id: string; latestRevisionId: string }; documentHref: string };
return { companyId, agentId, issueId, runId, saved };
}
async function publishedFileReport(value: Awaited<ReturnType<typeof fixture>>): Promise<PrpStructuredRunResult> {
await db.update(issues).set({ description: "Save a document on this task. Export the results as a CSV." }).where(eq(issues.id, value.issueId));
async function publishedFileReport(value: Awaited<ReturnType<typeof fixture>>, objective = "Save a document on this task. Export the results as a CSV."): Promise<PrpStructuredRunResult> {
await db.update(issues).set({ description: objective }).where(eq(issues.id, value.issueId));
const bytes = Buffer.from("category,total\nverified,3\n");
const authority = new PaperclipRunnerToolAuthority(db, { companyId: value.companyId, agentId: value.agentId,
issueId: value.issueId, runId: value.runId, workspaceRoot: "/workspace/test", executionTargetKind: "remote", readRemoteTaskFile: async () => bytes });
@@ -234,6 +234,17 @@ describe("native final-response feedback", () => {
await expect(nativeCompletionFeedback(db, value.runId, referenced))
.resolves.toContain("Completion report accepted");
});
it("accepts its generated document guidance link when the objective requests only a CSV", async () => {
const value = await fixture();
await db.update(issues).set({ title: "Export the results as a CSV" }).where(eq(issues.id, value.issueId));
const report = await publishedFileReport(value, "Export the results as a CSV.");
const feedback = await nativeCompletionFeedback(db, value.runId, report);
const href = /\[Saved document\]\(([^)]+)\)/u.exec(feedback)?.[1];
expect(href).toBe(value.saved.documentHref);
await expect(nativeCompletionFeedback(db, value.runId, {
...report, evidence: [...report.evidence, { ref: href! }],
})).resolves.toContain("Completion report accepted");
});
it("retains foreign task, foreign company, bogus link, and local file guards", async () => {
const current = await fixture(), otherTask = await fixture(current.companyId), otherCompany = await fixture();
const report = await publishedFileReport(current);