fix(docsync): stop a push from stealing another file's pairing

Papra answers a push of bytes it already holds with the document that has
them. Two trip files with the same content therefore pushed to one document,
and the unique index on (link_id, remote_id) aborted that run and every run
after it with a constraint error. The pairing now stays with the file that
owns it and the second file is recorded as a conflict.

Also in this pass:
- Paperless no longer adopts a same-bytes document that lacks the scope's tag
  when a consume outlives the poll budget; that document belongs to someone
  else's filing and delete-through would have trashed it.
- The pull path writes the file row, the retirement of the copy it replaces
  and the pairing in one transaction, and announces them only once committed.
- PATCH links stops accepting remoteRootId/remoteRootPath, which it read and
  silently dropped.
- The MCP sync tool clears shelved items before running, as the REST route
  does.
This commit is contained in:
Maurice
2026-09-19 00:44:43 +02:00
parent 7237a1881d
commit e6a5ca1521
11 changed files with 267 additions and 39 deletions
+10 -1
View File
@@ -149,7 +149,16 @@ export const docsyncLinkInputSchema = z.object({
});
export type DocsyncLinkInput = z.infer<typeof docsyncLinkInputSchema>;
export const docsyncLinkUpdateSchema = docsyncLinkInputSchema.omit({ connectionId: true, scopeKey: true }).partial();
/**
* Which folder or tag a binding points at is fixed once it exists: every
* pairing in `document_sync_items` was made against that container, and moving
* it would leave them all pointing somewhere else. The root fields are left out
* rather than ignored — they were accepted and silently dropped, so a client
* sending one was told the move had happened.
*/
export const docsyncLinkUpdateSchema = docsyncLinkInputSchema
.omit({ connectionId: true, scopeKey: true, remoteRootId: true, remoteRootPath: true })
.partial();
// ── Sync state ───────────────────────────────────────────────────────────────