mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2026-10-13 11:37:13 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6e717817c6 | ||
|
|
3e0fa73771 | ||
|
|
2b00cf6a95 |
No files matched your search
@@ -31,9 +31,19 @@ body:
|
|||||||
value: |
|
value: |
|
||||||
#### 🤖 Use of AI tools
|
#### 🤖 Use of AI tools
|
||||||
|
|
||||||
If this report was generated, in whole or in part, by an AI tool or agent you **must** clearly say so in the description below. Failing to clearly disclose this is a violation of our [Code of Conduct](https://github.com/paperless-ngx/paperless-ngx/blob/main/CODE_OF_CONDUCT.md).
|
If this report was generated, in whole or in part, by an AI tool or agent you **must** say so using the field below. Failing to clearly disclose this is a violation of our [Code of Conduct](https://github.com/paperless-ngx/paperless-ngx/blob/main/CODE_OF_CONDUCT.md).
|
||||||
|
|
||||||
Reports written by an AI agent must describe the _observed behavior only_: what happened, how to reproduce it, logs etc. Do **not** include code analysis, speculation about the root cause, or suggest code fixes.
|
Reports written by an AI agent must describe the _observed behavior only_: what happened, how to reproduce it, logs etc. Do **not** include code analysis, speculation about the root cause, or suggest code fixes.
|
||||||
|
- type: dropdown
|
||||||
|
id: ai-usage
|
||||||
|
attributes:
|
||||||
|
label: AI usage
|
||||||
|
description: Was any part of this report written by an AI tool or agent?
|
||||||
|
options:
|
||||||
|
- No AI tools were used
|
||||||
|
- Yes, AI tools helped write this report
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: description
|
id: description
|
||||||
attributes:
|
attributes:
|
||||||
|
|||||||
@@ -43,6 +43,12 @@ jobs:
|
|||||||
const haystack = `${issue.title}\n${issue.body ?? ''}`;
|
const haystack = `${issue.title}\n${issue.body ?? ''}`;
|
||||||
const honeypot = haystack.toUpperCase().includes('ASLOP-PR-VERIFY');
|
const honeypot = haystack.toUpperCase().includes('ASLOP-PR-VERIFY');
|
||||||
|
|
||||||
|
// The form's required "AI usage" dropdown renders as a `### AI usage` section. An agent
|
||||||
|
// that follows the template trips the honeypot even when it discloses, so only treat
|
||||||
|
// the honeypot as undisclosed AI use when the reporter did not answer "Yes" here.
|
||||||
|
const aiUsage = (issue.body ?? '').match(/^###\s+AI usage\s*\n+\s*(.+?)\s*$/m)?.[1] ?? '';
|
||||||
|
const disclosedAI = aiUsage.toLowerCase().startsWith('yes');
|
||||||
|
|
||||||
// Issues opened through the form always get the template's default labels. GitHub
|
// Issues opened through the form always get the template's default labels. GitHub
|
||||||
// applies them a second or two *after* creation, so re-read them instead of trusting
|
// applies them a second or two *after* creation, so re-read them instead of trusting
|
||||||
// the webhook payload, and retry before concluding that there are none.
|
// the webhook payload, and retry before concluding that there are none.
|
||||||
@@ -55,19 +61,29 @@ jobs:
|
|||||||
if (labels.some((label) => templateLabels.includes(label))) break;
|
if (labels.some((label) => templateLabels.includes(label))) break;
|
||||||
}
|
}
|
||||||
const bypassedTemplate = !labels.some((label) => templateLabels.includes(label));
|
const bypassedTemplate = !labels.some((label) => templateLabels.includes(label));
|
||||||
core.info(`Labels: [${labels.join(', ')}], honeypot: ${honeypot}, bypassed template: ${bypassedTemplate}`);
|
core.info(
|
||||||
|
`Labels: [${labels.join(', ')}], honeypot: ${honeypot}, AI usage: "${aiUsage}", ` +
|
||||||
|
`bypassed template: ${bypassedTemplate}`
|
||||||
|
);
|
||||||
|
|
||||||
if (!honeypot && !bypassedTemplate) {
|
if (!bypassedTemplate && (disclosedAI || !honeypot)) {
|
||||||
core.info('No slop signals found');
|
if (disclosedAI) {
|
||||||
|
core.info('AI use disclosed, labeling only');
|
||||||
|
await github.rest.issues.addLabels({ ...common, labels: ['ai'] });
|
||||||
|
} else {
|
||||||
|
core.info('No slop signals found');
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The honeypot is only ever tripped deliberately, so that message can name the cause.
|
// Reaching here with the honeypot means AI usage was not disclosed in the form, so that
|
||||||
|
// message can name the cause.
|
||||||
// A missing template label only tells us the issue did not come from the form, which
|
// A missing template label only tells us the issue did not come from the form, which
|
||||||
// is reason enough to close it, but not proof of how it was written.
|
// is reason enough to close it, but not proof of how it was written.
|
||||||
const body = honeypot
|
const body = honeypot
|
||||||
? 'This issue was automatically closed because it contains a marker that is only visible to ' +
|
? 'This issue was automatically closed because it contains a marker that is only visible to ' +
|
||||||
'automated tools, which indicates it was generated by an AI agent without being disclosed as such.\n\n' +
|
'automated tools, which indicates it was generated by an AI agent, but the "AI usage" field does not ' +
|
||||||
|
'disclose this.\n\n' +
|
||||||
`Please see our [contributing guidelines](${contributing}) and [Code of Conduct](${codeOfConduct}). ` +
|
`Please see our [contributing guidelines](${contributing}) and [Code of Conduct](${codeOfConduct}). ` +
|
||||||
'You are welcome to open a new issue that describes the problem you observed in your own words.'
|
'You are welcome to open a new issue that describes the problem you observed in your own words.'
|
||||||
: 'This issue was automatically closed because it was not opened using our bug report form. ' +
|
: 'This issue was automatically closed because it was not opened using our bug report form. ' +
|
||||||
|
|||||||
@@ -1,5 +1,54 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## paperless-ngx 3.3.1
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- Fix: use a root document's effective content for the LLM index and pr… [@stumpylog](https://github.com/stumpylog) ([#14423](https://github.com/paperless-ngx/paperless-ngx/pull/14423))
|
||||||
|
- Fix: index document versions as their root document [@stumpylog](https://github.com/stumpylog) ([#14422](https://github.com/paperless-ngx/paperless-ngx/pull/14422))
|
||||||
|
- Fix: authorize document versions by their root in the single-object permission check [@stumpylog](https://github.com/stumpylog) ([#14421](https://github.com/paperless-ngx/paperless-ngx/pull/14421))
|
||||||
|
- Fix: Provide better error messages to users for invalid JSON [@stumpylog](https://github.com/stumpylog) ([#14419](https://github.com/paperless-ngx/paperless-ngx/pull/14419))
|
||||||
|
- Fix: authorize document versions by their root and speed up permission id sets [@stumpylog](https://github.com/stumpylog) ([#14396](https://github.com/paperless-ngx/paperless-ngx/pull/14396))
|
||||||
|
- Chore: update the db options warning [@shamoon](https://github.com/shamoon) ([#14398](https://github.com/paperless-ngx/paperless-ngx/pull/14398))
|
||||||
|
- Fix: bulk reprocess latest version for root documents [@shamoon](https://github.com/shamoon) ([#14385](https://github.com/paperless-ngx/paperless-ngx/pull/14385))
|
||||||
|
- Fix: consistently use root doc for version action permissions [@shamoon](https://github.com/shamoon) ([#14384](https://github.com/paperless-ngx/paperless-ngx/pull/14384))
|
||||||
|
- Fix: Don't attempt to index created dates which are not representable in the FTS index [@stumpylog](https://github.com/stumpylog) ([#14390](https://github.com/paperless-ngx/paperless-ngx/pull/14390))
|
||||||
|
- Fix: retry a search index rebuild that was interrupted [@stumpylog](https://github.com/stumpylog) ([#14379](https://github.com/paperless-ngx/paperless-ngx/pull/14379))
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
- Chore: silence gotenberg startup warning [@slankes](https://github.com/slankes) ([#14405](https://github.com/paperless-ngx/paperless-ngx/pull/14405))
|
||||||
|
- Documentation: remove MARIADB\_HOST from MariaDB compose files [@shamoon](https://github.com/shamoon) ([#14378](https://github.com/paperless-ngx/paperless-ngx/pull/14378))
|
||||||
|
- Documentation: Updates the troubleshooting with 2 common database collation issues [@stumpylog](https://github.com/stumpylog) ([#14370](https://github.com/paperless-ngx/paperless-ngx/pull/14370))
|
||||||
|
|
||||||
|
### Maintenance
|
||||||
|
|
||||||
|
- Chore: Upgrade runners to Ubuntu 26.04 [@stumpylog](https://github.com/stumpylog) ([#14347](https://github.com/paperless-ngx/paperless-ngx/pull/14347))
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
|
||||||
|
- Chore(deps): Bump the npm\_and\_yarn group across 1 directory with 2 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#14417](https://github.com/paperless-ngx/paperless-ngx/pull/14417))
|
||||||
|
|
||||||
|
### All App Changes
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>12 changes</summary>
|
||||||
|
|
||||||
|
- Chore: share the authorizing owner rule between the id sets and the trash filter [@stumpylog](https://github.com/stumpylog) ([#14425](https://github.com/paperless-ngx/paperless-ngx/pull/14425))
|
||||||
|
- Fix: use a root document's effective content for the LLM index and pr… [@stumpylog](https://github.com/stumpylog) ([#14423](https://github.com/paperless-ngx/paperless-ngx/pull/14423))
|
||||||
|
- Fix: index document versions as their root document [@stumpylog](https://github.com/stumpylog) ([#14422](https://github.com/paperless-ngx/paperless-ngx/pull/14422))
|
||||||
|
- Fix: authorize document versions by their root in the single-object permission check [@stumpylog](https://github.com/stumpylog) ([#14421](https://github.com/paperless-ngx/paperless-ngx/pull/14421))
|
||||||
|
- Fix: Provide better error messages to users for invalid JSON [@stumpylog](https://github.com/stumpylog) ([#14419](https://github.com/paperless-ngx/paperless-ngx/pull/14419))
|
||||||
|
- Chore(deps): Bump the npm\_and\_yarn group across 1 directory with 2 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#14417](https://github.com/paperless-ngx/paperless-ngx/pull/14417))
|
||||||
|
- Fix: authorize document versions by their root and speed up permission id sets [@stumpylog](https://github.com/stumpylog) ([#14396](https://github.com/paperless-ngx/paperless-ngx/pull/14396))
|
||||||
|
- Chore: update the db options warning [@shamoon](https://github.com/shamoon) ([#14398](https://github.com/paperless-ngx/paperless-ngx/pull/14398))
|
||||||
|
- Fix: bulk reprocess latest version for root documents [@shamoon](https://github.com/shamoon) ([#14385](https://github.com/paperless-ngx/paperless-ngx/pull/14385))
|
||||||
|
- Fix: consistently use root doc for version action permissions [@shamoon](https://github.com/shamoon) ([#14384](https://github.com/paperless-ngx/paperless-ngx/pull/14384))
|
||||||
|
- Fix: Don't attempt to index created dates which are not representable in the FTS index [@stumpylog](https://github.com/stumpylog) ([#14390](https://github.com/paperless-ngx/paperless-ngx/pull/14390))
|
||||||
|
- Fix: retry a search index rebuild that was interrupted [@stumpylog](https://github.com/stumpylog) ([#14379](https://github.com/paperless-ngx/paperless-ngx/pull/14379))
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
## paperless-ngx 3.3.0
|
## paperless-ngx 3.3.0
|
||||||
|
|
||||||
### Features / Enhancements
|
### Features / Enhancements
|
||||||
|
|||||||
@@ -514,8 +514,11 @@ class Command(CryptMixin, PaperlessCommand):
|
|||||||
else:
|
else:
|
||||||
base_name = document.get_public_filename(counter=filename_counter)
|
base_name = document.get_public_filename(counter=filename_counter)
|
||||||
|
|
||||||
if base_name not in self.exported_files:
|
# The thumbnail and archive names are derived from the stem, so
|
||||||
self.exported_files.add(base_name)
|
# uniqueness must hold without the original's extension
|
||||||
|
unique_key = str(Path(base_name).with_suffix(""))
|
||||||
|
if unique_key not in self.exported_files:
|
||||||
|
self.exported_files.add(unique_key)
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
filename_counter += 1
|
filename_counter += 1
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import hashlib
|
|||||||
import json
|
import json
|
||||||
import shutil
|
import shutil
|
||||||
import tempfile
|
import tempfile
|
||||||
|
from datetime import date
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -44,14 +45,18 @@ from documents.models import Workflow
|
|||||||
from documents.models import WorkflowAction
|
from documents.models import WorkflowAction
|
||||||
from documents.models import WorkflowTrigger
|
from documents.models import WorkflowTrigger
|
||||||
from documents.sanity_checker import check_sanity
|
from documents.sanity_checker import check_sanity
|
||||||
|
from documents.settings import EXPORTER_ARCHIVE_NAME
|
||||||
from documents.settings import EXPORTER_FILE_NAME
|
from documents.settings import EXPORTER_FILE_NAME
|
||||||
from documents.settings import EXPORTER_SHARE_LINK_BUNDLE_NAME
|
from documents.settings import EXPORTER_SHARE_LINK_BUNDLE_NAME
|
||||||
|
from documents.settings import EXPORTER_THUMBNAIL_NAME
|
||||||
from documents.tests.utils import SampleDirMixin
|
from documents.tests.utils import SampleDirMixin
|
||||||
from paperless_mail.models import MailAccount
|
from paperless_mail.models import MailAccount
|
||||||
from paperless_testing.assertions import FileSystemAssertsMixin
|
from paperless_testing.assertions import FileSystemAssertsMixin
|
||||||
from paperless_testing.dirs import DirectoriesMixin
|
from paperless_testing.dirs import DirectoriesMixin
|
||||||
|
from paperless_testing.dirs import PaperlessDirs
|
||||||
from paperless_testing.dirs import paperless_environment
|
from paperless_testing.dirs import paperless_environment
|
||||||
from paperless_testing.factories import DocumentBarcodeFactory
|
from paperless_testing.factories import DocumentBarcodeFactory
|
||||||
|
from paperless_testing.factories import DocumentFactory
|
||||||
from paperless_testing.permissions import grant_object
|
from paperless_testing.permissions import grant_object
|
||||||
|
|
||||||
|
|
||||||
@@ -1462,3 +1467,73 @@ class TestCryptExportImport(
|
|||||||
("No passphrase was given, sensitive fields will be in plaintext"),
|
("No passphrase was given, sensitive fields will be in plaintext"),
|
||||||
stdout.read(),
|
stdout.read(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.management
|
||||||
|
@pytest.mark.django_db
|
||||||
|
class TestExportDerivedFileNameCollisions:
|
||||||
|
def test_same_stem_different_extension_get_distinct_derived_files(
|
||||||
|
self,
|
||||||
|
paperless_dirs: PaperlessDirs,
|
||||||
|
tmp_path: Path,
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- Two documents with the same title and created date
|
||||||
|
- One original is a PDF, the other a PNG, both with archive and thumbnail
|
||||||
|
WHEN:
|
||||||
|
- The exporter runs
|
||||||
|
THEN:
|
||||||
|
- Each document is exported under a distinct original, thumbnail and archive name
|
||||||
|
- Each exported file holds its own document's content
|
||||||
|
"""
|
||||||
|
for pk, (filename, mime_type) in enumerate(
|
||||||
|
[("collide.pdf", "application/pdf"), ("collide.png", "image/png")],
|
||||||
|
start=1,
|
||||||
|
):
|
||||||
|
(paperless_dirs.originals_dir / filename).write_bytes(
|
||||||
|
f"original {pk}".encode(),
|
||||||
|
)
|
||||||
|
(paperless_dirs.archive_dir / f"{pk:07}.pdf").write_bytes(
|
||||||
|
f"archive {pk}".encode(),
|
||||||
|
)
|
||||||
|
(paperless_dirs.thumbnail_dir / f"{pk:07}.webp").write_bytes(
|
||||||
|
f"thumbnail {pk}".encode(),
|
||||||
|
)
|
||||||
|
DocumentFactory(
|
||||||
|
pk=pk,
|
||||||
|
title="collide",
|
||||||
|
created=date(2026, 10, 7),
|
||||||
|
filename=filename,
|
||||||
|
archive_filename=f"{pk:07}.pdf",
|
||||||
|
mime_type=mime_type,
|
||||||
|
)
|
||||||
|
|
||||||
|
target = tmp_path / "export"
|
||||||
|
target.mkdir()
|
||||||
|
call_command(
|
||||||
|
"document_exporter",
|
||||||
|
str(target),
|
||||||
|
"--no-progress-bar",
|
||||||
|
skip_checks=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
manifest = json.loads((target / "manifest.json").read_text())
|
||||||
|
records = {r["pk"]: r for r in manifest if r["model"] == "documents.document"}
|
||||||
|
|
||||||
|
for key in (
|
||||||
|
EXPORTER_FILE_NAME,
|
||||||
|
EXPORTER_THUMBNAIL_NAME,
|
||||||
|
EXPORTER_ARCHIVE_NAME,
|
||||||
|
):
|
||||||
|
assert records[1][key] != records[2][key]
|
||||||
|
|
||||||
|
for pk in (1, 2):
|
||||||
|
record = records[pk]
|
||||||
|
assert (target / record[EXPORTER_FILE_NAME]).read_text() == f"original {pk}"
|
||||||
|
assert (
|
||||||
|
target / record[EXPORTER_THUMBNAIL_NAME]
|
||||||
|
).read_text() == f"thumbnail {pk}"
|
||||||
|
assert (
|
||||||
|
target / record[EXPORTER_ARCHIVE_NAME]
|
||||||
|
).read_text() == f"archive {pk}"
|
||||||
Reference in new issue
Block a user