Skip to content

Move the docs stacklet to Paperless-ngx 3.0.4 - #51

Merged
arthware-dev merged 7 commits into
mainfrom
feat/paperless-3
Aug 1, 2026
Merged

Move the docs stacklet to Paperless-ngx 3.0.4#51
arthware-dev merged 7 commits into
mainfrom
feat/paperless-3

Conversation

@arthware-dev

Copy link
Copy Markdown
Contributor

Closes #50.

Moves docs from Paperless-ngx 2.20.15 to 3.0.4. A watchtower pull of :latest once made this jump by accident and broke document filing; this is the deliberate version, with the two things that actually break fixed.

Duplicate detection was broken twice over

3.0 changed duplicate handling in two independent ways, and unmodified we would have hit both.

It stops rejecting duplicates at all. Rejection is now conditional on PAPERLESS_CONSUMER_DELETE_DUPLICATES, which defaults off. With it unset, re-sending the same letter files a second copy and reports success. Now set in the compose.

The rejection no longer carries a message. _DUPLICATE_RE scraped the twin's name out of the failure text. On 3.x there is no text, so wait_task returned None and the archivist reported a generic upload failure instead of "already filed". It now reads result_data.duplicate_of and looks up the title, with the 2.x text path kept for older images.

Worth knowing for reviewers: on a rejected upload 3.x sets related_document_ids to the twin. Nothing but result_data separates that payload from a successful filing.

A second search word narrows again

3.0 also replaced Whoosh with tantivy, whose parser joins bare terms with OR. Since we wildcard every token too, homer car insurance matched 18 of 24 documents in the demo archive: one word was enough to hit. Joining with AND fixes it, measured on the same archive:

user types before after
marge recipe 11 1
lisa report card 6 1

Queries where the caller wrote their own operator or a quoted phrase keep their structure, because injecting AND into either breaks it: "birth certificate" would become "birth AND certificate" and match nothing, and insurance OR recipe would become a parse error.

This trades one failure mode for another. homer car insurance now returns 0, because that document is titled "Auto Insurance Policy 2026" and the word "car" is nowhere in it. Ranking and a confidence cutoff are tracked separately in FAM-18, along with the fact that the German compound rationale for the wildcard is now unverified (the demo set is English only).

On method

The first version of the duplicate parser was written from the upstream source at v3.0.4, and the real server disagreed with it. Source reading said a duplicate task ends success with no related documents; a live 3.0.4 returns failure with the twin's id present. The offline tests passed against the wrong fixture. What is pinned now was captured by uploading the same file twice and dumping /api/tasks/ verbatim, and TestTaskApiShape in the new e2e keeps it honest by handing the real payload to the real parser.

Two of my own e2e assertions counted documents through search, which reads an asynchronously updated index. They passed alone and failed inside the full suite. They now ask the document list instead.

Also here

  • Upgrade note. Paperless migrates its database on first 3.x start and 2.x will not boot afterwards. Anyone already rolled forward by watchtower cannot pin back without a backup taken before that boot. The admin guide now says so, and says what to back up first.
  • stacktests down <stacklet> never worked. exec cannot run a shell function, so an explicit target died with exec: stack_cli: not found and tore nothing down. The no-argument form masked it.
  • AGENTS.md non-negotiable 11. Seed instances through the front door. Loading documents straight into Paperless skips the archivist, so the corpus has no tags, correspondent, type, summary or vault entry, and any measurement taken on it describes a system we do not ship. Learned the hard way in this branch.

Paperless 3.0 stopped rejecting a re-uploaded identical file unless
PAPERLESS_CONSUMER_DELETE_DUPLICATES is set, and when it does reject
one the task now *succeeds* carrying result_data.duplicate_of instead
of failing with a message naming the twin. Left alone, re-sending the
same letter would quietly file a second copy.

Set the flag, and read the twin's id out of the structured result so
the archivist still answers "already filed" and links the original.
The 2.x failure-text path stays for older images.
Paperless migrates its database on the first 3.x start and 2.x will not
boot afterwards, so anyone already rolled to 3.x by Watchtower cannot
pin back without a backup taken before that boot. Say so, and say what
to back up first.
`exec` looks for a program, and stack_cli is a shell function, so
passing an explicit target failed with "exec: stack_cli: not found"
and tore nothing down. The no-argument form worked, which is why it
went unnoticed.
The first pass was written from the upstream source, which suggested a
rejected duplicate ends as a successful task with no related documents.
A real 3.0.4 disagrees: the task is marked failure, and
related_document_ids holds the *twin's* id. Capture the payload from a
running container instead and parse that.

Adds an e2e that drives the bot's own Paperless client against the live
container, so notes, duplicates, and owner scoping are answered by the
server rather than by a fixture we wrote.
Loading documents straight into Paperless skips the archivist, so the
corpus has no tags, correspondent, type, summary, or vault entry, and
anything measured against it describes a system we do not ship. Point
agents at tools/family-docs/ingest.py and say why.
Two e2e checks counted documents via search, which reads an index that
updates asynchronously. Both passed alone and failed inside the full
suite, where indexing sits behind a busier queue, so a lag looked like
a filing bug and a permissions bug. Ask the document list instead: the
question is what the archive holds, not what the index has caught up on.
Paperless 3.0 swapped Whoosh for tantivy, whose parser joins bare terms
with OR. Since we also wildcard every word, "homer car insurance"
matched 18 of 24 documents in the demo archive: any one word was enough.
Join the words with AND so all of them have to appear. The same archive
now answers "marge recipe" with 1 document instead of 11.

Queries where the user wrote their own operator or a quoted phrase keep
their structure, because injecting AND into either breaks it outright.
@arthware-dev
arthware-dev merged commit 64f269c into main Aug 1, 2026
1 check passed
@arthware-dev
arthware-dev deleted the feat/paperless-3 branch August 2, 2026 05:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support Paperless-ngx 3.x

1 participant