Commit Graph
624 Commits
Author SHA1 Message Date
05eb74022a chore(ci): migrate Python tooling to uv and standardize workflow execution (#7386)
# Description of Changes

This PR modernizes the project's Python tooling across GitHub Actions by
migrating CI workflows from pip-based dependency management to `uv` and
aligning Python execution with the engine project's managed environment.

### What was changed

- Replaced `actions/setup-python` and ad-hoc `pip install` steps with
`astral-sh/setup-uv` across CI workflows.
- Configured shared `uv` dependency caching using
`engine/pyproject.toml` and `engine/uv.lock`.
- Updated Python script execution to use `uv run --project engine
--locked` for a consistent runtime environment.
- Replaced package installation steps with `uv sync` for the required
dependency groups (e.g. `tools` and `cucumber`).
- Added Docker image build validation for both production and
development AI engine images.
- Updated workflow cache configuration and Docker build context where
required.
- Removed obsolete Python requirements files that are no longer needed
after the migration.
- Applied minor Python code modernizations, including import cleanup,
modern built-in generic type annotations (`list[...]`, `tuple[...]`,
`float | None`), and small style improvements.
- Removed unnecessary Python formatter/linter extensions from the
development container configuration.

### Why the change was made

- Standardize Python dependency management across the repository.
- Reduce duplicated dependency installation logic in CI.
- Improve workflow performance through shared dependency caching.
- Ensure all Python utilities execute against the same locked dependency
set managed by the engine project.
- Simplify long-term maintenance by eliminating legacy requirements
files and pip-specific workflow steps.


---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.

---------

Signed-off-by: Carsten Drewes <c.drewes@stud.uni-hannover.de>
Co-authored-by: albanobattistella <34811668+albanobattistella@users.noreply.github.com>
Co-authored-by: kastenherri <116314318+kastenherri@users.noreply.github.com>
Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: James Brunton <jbrunton96@gmail.com>
2026-08-11 08:09:21 +00:00
EthanHealy01 c929386442 Record policy-run failures as durable, actionable events (Review Flow PR 1) (#7269)
# Description of Changes

PR 1 of the failure-notification work: a durable, team-scoped record of
**why a policy run failed**, surfaced in the portal with the triage
actions each failure allows.

Today a failed policy run is not quite invisible, but it is unusable:
the ledger marks the file `ERROR`, and the audit aspect keeps the
exception message and status code. Nothing classifies either one,
nothing surfaces them, and neither offers a next step. If the file came
from a folder, bucket or webhook there is also no user watching, so
nobody learns it never made it through. This adds the record and the
read surface; the remediation that acts on documents comes later (see
below).

## What this does

**A failure kind registry as data.** `FailureKind` describes what can go
wrong: a stable wire id, i18n keys, an English fallback, and four facets
the review surface needs (`Stage`, `Severity`, `Remedy`, `Scope`). It is
shaped like the existing `ExceptionUtils.ErrorCode` and *links* to that
vocabulary rather than replacing it.

**Classification off structured codes, not message matching.** Policy
steps dispatch over loopback HTTP, so a tool's 4xx arrives as a
`RestClientResponseException` whose body is the Problem Details document
carrying `errorCode`. `FailureClassifier` reads that. Anything
unrecognised becomes `UNKNOWN`, which is the point: every failed run
gets an addressable record from day one, and which kinds to promote next
is answered by production frequency rather than guesswork.

**Actions declared by a kind, implemented as beans.** A kind lists the
`FailureActionId`s it offers; behaviour lives in `FailureAction` beans
resolved by id — the idiom this codebase already uses for `InputSource`,
`PolicyOutputSink` and `PolicyTrigger`. A kind cannot be sent an action
it never declared (400), so an incoherent pairing is unreachable rather
than merely unrendered. A new kind ships as a registry entry plus copy:
no new endpoint, no UI change.

**Repeat folding.** Recording folds a genuine repeat into the existing
incident instead of inserting again, keyed on `(team_id, dedup_key)`.
That matters for a snapshot-mode source that re-lists every file on each
sweep: the same broken file is one incident, not one per sweep. Distinct
files keep distinct rows. The unique constraint is enforced by the
database, and a writer that loses the insert race folds into the
winner's row.

One granularity caveat worth naming: nothing populates `file_id` in this
PR, so every row has it NULL. A FILE-scoped kind therefore dedups on
`policy + run` rather than `policy + file`. That still yields one row
per document for the sources shipped here, because the folder, S3 and
webhook sources each start one run per file; it stops holding as soon as
a single run carries several documents, which is why editor-origin
reporting (item 3 below) populates `file_id`.

**No document identity is stored.** No file name, no content. `fileId`
is an opaque reference only the owner's own client can resolve locally.
`detail` keeps the raw message (the only diagnostic an `UNKNOWN` failure
has) with anything path- or filename-shaped stripped on the way in,
capped at 2,000 characters. `PolicyExecutor`'s type-mismatch message now
reports the *extension* rather than the filename, since that message
becomes the stored `detail`.

**Access.** Reads and triage are leader-only, gated exactly the way
`PolicyController` gates policy editing, with the single-user carve-out
when login is disabled. Every read and write is scoped to the caller's
own team from the authenticated principal — there is no team parameter
on the API.

Self-hosted needs no migration: the table is created from the entity by
`ddl-auto=update`, as with every other table.

## What this does not do yet

- **Actions are incident dispositions, not document dispositions.**
Acknowledge and Dismiss change how a failure is displayed and touch
nothing else — not the document, not the processed-file ledger, not the
run, not any output destination. That is what makes them safe to offer
against `UNKNOWN`, and why there is no Approve/Release yet.
- **Two kinds only.** `INPUT_PASSWORD_PROTECTED` and `UNKNOWN`.
Everything else classifies as `UNKNOWN` and shows its raw message.
- **Editor-origin failures are not reported.** Every row is `PROCESSOR`.
`FailureOrigin.EDITOR` and `API` exist in the enum but nothing writes
them.
- **The list is dev-only for now.** The section renders behind
`import.meta.env.DEV`, so it ships in no production bundle. The
endpoints are live and gated.
- **No retention or per-team cap** on `file_run_events`. Tracked
separately.
- **No suspend-and-prompt.** `PolicyInputRequiredException` and the
engine's `suspend()` exist but nothing throws it, so a run cannot pause
to ask for a password today.
- **SaaS needs a migration** in `Stirling-PDF-SaaS` (`CREATE TABLE IF
NOT EXISTS stirling_pdf.file_run_events`), per the convention documented
at `app/saas/src/main/resources/application-saas.properties:21`.

## What follows in later PRs

1. **Map the remaining error codes to specific kinds** — corrupted file,
OCR unavailable, output destination unreachable, entitlement refusals,
and so on — each with its own copy and its own action set, replacing
today's `UNKNOWN` catch-all with a named notification in the review UI.
2. **Real remediation actions** attached to those kinds: fix (supply a
password and resume), skip (drop this file, continue the batch), and
decline (reject an incoming file outright), acting on the held document
rather than only on the incident row. This is where the
suspend-and-prompt path gets wired.
3. **Editor-origin reporting**, so a failure a user hits in the editor
lands in the same queue as one from a bucket.
4. **The user-facing review surface**: notifications with a sticky
review section, per-file badges, and an export gate, with the dev-only
list here replaced by the real thing.

## How to test

Needs a SaaS or proprietary build with login enabled, and an account
that leads a team.

1. Create a policy in the Processor with any step (Auto-redact is fine)
and a source you can drop files into.
2. Upload two files that will fail it: **a password-protected PDF**, and
**a corrupted PDF** (truncate a valid one, or rename a `.csv` to
`.pdf`).
3. Let the policy run and fail on both.
4. Go to the portal's **Documents** view and scroll to **Failures** (dev
builds only).

Expect two rows:

- **Password-protected document** — classified from `E004`, with the
kind's own labels **"I'll unlock this"** and **"Skip this file"** rather
than generic wording.
- **Unrecognised failure** — the corrupted file, classified `UNKNOWN`
(`E001` is not claimed by a kind yet), showing its raw message with
generic **Acknowledge** / **Dismiss**.

Neither row contains a file name anywhere, including in the raw message.
Press **Show raw JSON** to read exactly what the server returned. Acting
on a row transitions it and comes back with both buttons disabled and a
reason.

Re-running the same batch increments the occurrence count on the
existing rows rather than adding new ones; two *different*
password-protected files produce two separate rows.

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
2026-08-10 16:45:13 +00:00
Anthony Stirling 59ed4f5fd1 Fix automate unrunnable tools (#7311)
# Description of Changes

Fix automate unrunnable tools


## Problem

- Remove Image failed in Automate with `Tool operation not supported:
removeImage`
- Its registry entry had `operationConfig: undefined` even though the
config existed and was already tested
- The Automate picker only filtered on `supportsAutomate`, never on
`operationConfig` — so broken tools were selectable and failed only at
run time

## Fixes

- Wire up `removeImage` and `pageLayout` operation configs (both already
existed, just never registered)
- Exclude `validateSignature` (report tool, not on the operationConfig
seam) and `scannerEffect` (no frontend implementation) via
`supportsAutomate: false`
- Picker now also filters on `operationConfig`, so this class of bug
can't reach users again
- `overlay-pdfs` returns 400 instead of 500 when overlay files or mode
are missing
- Fix `new URL().pathname` Windows path bug that stopped 2 test suites
from loading

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-10 11:14:56 +00:00
James Brunton af5f54274d Add defaults to calculations for ToolIO (#7289)
# Description of Changes
Currently when calculating the output file type for some tools, the
system will get it wrong because it doesn't know about what the default
parameters in tools are, so if it doesn't have a value for some key,
it'll just bail out and say "it might not be compatible". This PR adds
logic to `ToolIO` to read the default values set for the parameters if
the tool has `ToolIOCase`s and takes them into account when figuring out
the output type. I've built it with horrible Java reflection magic to
avoid having to specify the default for params twice, which will make it
impossible for the defaults to disagree with each other. This just runs
once at startup so there's negligible performance impact.

The change is easily tested with Change Parameters, which is just
`add-password` behind the scenes but with the password params omitted
(so Change Password is always PDF->PDF, never encrypted like Add
Password).

Also (somewhat hackily) fixes a bug I noticed where saving a Change
Permissions step then leaving and returning to the pipeline will cause
the step to be reloaded as Add Password. I've added a system to
disambiguate tools which share the same endpoint (which is only these
two currently).

## Currently

<img width="455" height="135" alt="image"
src="https://github.com/user-attachments/assets/c8867d6a-599b-4f21-a2db-4a1b6ac22d73"
/>

## Now

<img width="415" height="122" alt="image"
src="https://github.com/user-attachments/assets/bd8d1bab-f00a-421b-8c91-5af0d2ad5335"
/>
2026-08-10 11:00:16 +00:00
brios 4e901f7524 refactor(package): rename example classes and update package structure (#7400)
# Description of Changes

Refactors a small set of previously vendored/derived PDFBox "example"
classes into Stirling’s own package namespaces.



<!--
Please provide a summary of the changes, including:

- What was changed
- Why the change was made
- Any challenges encountered

Closes #(issue_number)
-->

---

## Checklist

### General

- [X] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [X] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [X] I have performed a self-review of my own code
- [X] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [X] I have run `task check` to verify linters, typechecks, and tests
pass
- [X] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-09 20:41:57 +01:00
Ludy 0c0a96aa83 build(deps): bump org.simplejavamail from 8.12.0 to 9.2.0 (#7398)
Bump org.simplejavamail:simple-java-mail and outlook-module from 8.12.6
to 9.2.0 in app/common/build.gradle. Add a Dependabot group for the
org.simplejavamail artifacts in .github/dependabot.yml so updates for
those packages are grouped into a single PR.


---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-09 20:30:33 +01:00
dependabot[bot] 6d08cb2f52 build(deps): bump com.drewnoakes:metadata-extractor from 2.20.0 to 2.21.0 (#7338)
Bumps
[com.drewnoakes:metadata-extractor](https://github.com/drewnoakes/metadata-extractor)
from 2.20.0 to 2.21.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/drewnoakes/metadata-extractor/releases">com.drewnoakes:metadata-extractor's
releases</a>.</em></p>
<blockquote>
<h2>2.21.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Replace custom number formatting in IccDescriptor with String.format
by <a href="https://github.com/drewnoakes"><code>@​drewnoakes</code></a>
with <a href="https://github.com/Copilot"><code>@​Copilot</code></a> in
<a
href="https://redirect.github.com/drewnoakes/metadata-extractor/pull/721">drewnoakes/metadata-extractor#721</a></li>
<li>Fix OOM DoS vector in BmpHeaderDescriptor.formatHex via unbounded
digits parameter by <a
href="https://github.com/drewnoakes"><code>@​drewnoakes</code></a> with
<a href="https://github.com/Copilot"><code>@​Copilot</code></a> in <a
href="https://redirect.github.com/drewnoakes/metadata-extractor/pull/722">drewnoakes/metadata-extractor#722</a></li>
<li>Normalize sign to numerator in Rational.toString() by <a
href="https://github.com/drewnoakes"><code>@​drewnoakes</code></a> with
<a href="https://github.com/Copilot"><code>@​Copilot</code></a> in <a
href="https://redirect.github.com/drewnoakes/metadata-extractor/pull/724">drewnoakes/metadata-extractor#724</a></li>
<li>Extract IPTC metadata from PNG text chunks by <a
href="https://github.com/drewnoakes"><code>@​drewnoakes</code></a> with
<a href="https://github.com/Copilot"><code>@​Copilot</code></a> in <a
href="https://redirect.github.com/drewnoakes/metadata-extractor/pull/723">drewnoakes/metadata-extractor#723</a></li>
<li>Tolerate duplicate PNG chunks that are not allowed to appear
multiple times by <a
href="https://github.com/drewnoakes"><code>@​drewnoakes</code></a> with
<a href="https://github.com/Copilot"><code>@​Copilot</code></a> in <a
href="https://redirect.github.com/drewnoakes/metadata-extractor/pull/725">drewnoakes/metadata-extractor#725</a></li>
<li>Null-check and length-guard getPowerUpTimeDescription by <a
href="https://github.com/drewnoakes"><code>@​drewnoakes</code></a> with
<a href="https://github.com/Copilot"><code>@​Copilot</code></a> in <a
href="https://redirect.github.com/drewnoakes/metadata-extractor/pull/727">drewnoakes/metadata-extractor#727</a></li>
<li>Move DisposalMethod description logic to GifControlDescriptor by <a
href="https://github.com/drewnoakes"><code>@​drewnoakes</code></a> with
<a href="https://github.com/Copilot"><code>@​Copilot</code></a> in <a
href="https://redirect.github.com/drewnoakes/metadata-extractor/pull/729">drewnoakes/metadata-extractor#729</a></li>
<li>Port .NET RiffReader.processChunks error checking to Java by <a
href="https://github.com/drewnoakes"><code>@​drewnoakes</code></a> with
<a href="https://github.com/Copilot"><code>@​Copilot</code></a> in <a
href="https://redirect.github.com/drewnoakes/metadata-extractor/pull/728">drewnoakes/metadata-extractor#728</a></li>
<li>Fix NPE in ItemLocationBox when offset_size/length_size is 0 (valid
AVIF) by <a href="https://github.com/hanskr"><code>@​hanskr</code></a>
in <a
href="https://redirect.github.com/drewnoakes/metadata-extractor/pull/732">drewnoakes/metadata-extractor#732</a></li>
<li>Harden ICO parsing against oversized image counts by <a
href="https://github.com/drewnoakes"><code>@​drewnoakes</code></a> with
<a href="https://github.com/Copilot"><code>@​Copilot</code></a> in <a
href="https://redirect.github.com/drewnoakes/metadata-extractor/pull/734">drewnoakes/metadata-extractor#734</a></li>
<li>Support LONG8 and SLONG8 TIFF data formats by <a
href="https://github.com/dschmidt"><code>@​dschmidt</code></a> in <a
href="https://redirect.github.com/drewnoakes/metadata-extractor/pull/739">drewnoakes/metadata-extractor#739</a></li>
<li>Detect QuickTime files that lack a leading ftyp box by <a
href="https://github.com/dschmidt"><code>@​dschmidt</code></a> in <a
href="https://redirect.github.com/drewnoakes/metadata-extractor/pull/736">drewnoakes/metadata-extractor#736</a></li>
<li>Add basic MP4 udta metadata (Title, Comment, Subtitle, Rating,
Category, Mood) by <a
href="https://github.com/drewnoakes"><code>@​drewnoakes</code></a> in <a
href="https://redirect.github.com/drewnoakes/metadata-extractor/pull/740">drewnoakes/metadata-extractor#740</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a
href="https://github.com/drewnoakes"><code>@​drewnoakes</code></a> with
<a href="https://github.com/Copilot"><code>@​Copilot</code></a> made
their first contribution in <a
href="https://redirect.github.com/drewnoakes/metadata-extractor/pull/721">drewnoakes/metadata-extractor#721</a></li>
<li><a href="https://github.com/hanskr"><code>@​hanskr</code></a> made
their first contribution in <a
href="https://redirect.github.com/drewnoakes/metadata-extractor/pull/732">drewnoakes/metadata-extractor#732</a></li>
<li><a href="https://github.com/dschmidt"><code>@​dschmidt</code></a>
made their first contribution in <a
href="https://redirect.github.com/drewnoakes/metadata-extractor/pull/739">drewnoakes/metadata-extractor#739</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/drewnoakes/metadata-extractor/compare/2.20.0...2.20.1">https://github.com/drewnoakes/metadata-extractor/compare/2.20.0...2.20.1</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/drewnoakes/metadata-extractor/commit/6f037943c0811799a59b0ea19896cb2b07da17be"><code>6f03794</code></a>
Merge pull request <a
href="https://redirect.github.com/drewnoakes/metadata-extractor/issues/740">#740</a>
from drewnoakes/dev/drnoakes/mp4-metadata-pr-review</li>
<li><a
href="https://github.com/drewnoakes/metadata-extractor/commit/d01cc419eea457b8d482f864d1f1bbacee834828"><code>d01cc41</code></a>
Detect QuickTime files that lack a leading ftyp box (<a
href="https://redirect.github.com/drewnoakes/metadata-extractor/issues/736">#736</a>)</li>
<li><a
href="https://github.com/drewnoakes/metadata-extractor/commit/1b45d70723192a02bde7626d6e011081d52c3592"><code>1b45d70</code></a>
Support LONG8 and SLONG8 TIFF data formats</li>
<li><a
href="https://github.com/drewnoakes/metadata-extractor/commit/353e27c37c414702e92304a4067de96bb383def1"><code>353e27c</code></a>
Address Copilot review: align entry parsing and fix tag name casing</li>
<li><a
href="https://github.com/drewnoakes/metadata-extractor/commit/31632d2d70497333e5aa71401323f17e0f6d515a"><code>31632d2</code></a>
Clean up salvaged MP4 udta metadata code</li>
<li><a
href="https://github.com/drewnoakes/metadata-extractor/commit/845cdbab9a8f1348c52b1b4e10a6f774929ba303"><code>845cdba</code></a>
Add more UDTA metadata to MP4 parser (Title, Comment, Subtitle, Rating,
Categ...</li>
<li><a
href="https://github.com/drewnoakes/metadata-extractor/commit/5647ffd0541279bc488f6387ea91aa3c785de36b"><code>5647ffd</code></a>
Harden ICO parsing against oversized image counts (<a
href="https://redirect.github.com/drewnoakes/metadata-extractor/issues/734">#734</a>)</li>
<li><a
href="https://github.com/drewnoakes/metadata-extractor/commit/99383f6d345dfa9a72e0780c37876a8ebd31ce31"><code>99383f6</code></a>
Merge pull request <a
href="https://redirect.github.com/drewnoakes/metadata-extractor/issues/732">#732</a>
from hanskr/fix-iloc-zero-offset-size-npe</li>
<li><a
href="https://github.com/drewnoakes/metadata-extractor/commit/35af34ae39dec240e1e0a1bded0161f6f86df38e"><code>35af34a</code></a>
Fix NPE in ItemLocationBox when offset_size/length_size is 0</li>
<li><a
href="https://github.com/drewnoakes/metadata-extractor/commit/520e07fed8167863e2245f67d2622f9bb473d017"><code>520e07f</code></a>
Merge pull request <a
href="https://redirect.github.com/drewnoakes/metadata-extractor/issues/728">#728</a>
from drewnoakes/copilot/enhance-error-checking-riffre...</li>
<li>Additional commits viewable in <a
href="https://github.com/drewnoakes/metadata-extractor/compare/2.20.0...2.21.0">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=com.drewnoakes:metadata-extractor&package-manager=gradle&previous-version=2.20.0&new-version=2.21.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-08 21:43:28 +01:00
Anthony Stirling 2cf6db99ce Fix timing-fragile Valkey rate-limit boundary test (#7302)
# Description of Changes

Fix timing-fragile Valkey rate-limit boundary test

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-07 08:47:09 +00:00
dependabot[bot] ad8830b645 build(deps): bump com.sun.xml.bind:jaxb-core from 4.0.7 to 4.0.9 (#7270)
Bumps com.sun.xml.bind:jaxb-core from 4.0.7 to 4.0.9.


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=com.sun.xml.bind:jaxb-core&package-manager=gradle&previous-version=4.0.7&new-version=4.0.9)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-06 12:20:38 +00:00
Ludy 8094765bab build(licenses): Module-specific license. Add dependency overrides. (#7049)
# Description of Changes

This change adds a version-scoped override mechanism for dependencies
whose published metadata does not expose a detectable license.

- Added `app/license-overrides.json` with verified Apache License 2.0
metadata for:
  - `com.hubspot.immutables:immutables-exceptions:1.9`
  - `com.hubspot:algebra:1.5`
- Added `ModuleLicenseOverrideFilter` as custom `buildSrc` logic for the
Gradle dependency license report plugin.
- Applied overrides only when the exact `group:artifact:version` matches
and no usable license metadata was detected.
- Added automatic maintenance of the override file:
  - Removes overrides when the dependency is no longer resolved.
- Removes overrides when the dependency starts publishing valid license
metadata.
- Migrates stale overrides to newer unresolved versions and clears their
metadata for re-verification.
- Adds null-valued placeholders for newly detected dependencies without
license metadata.
- Preserves populated overrides for newer versions when already present.
- Added Gradle version-aware dependency ordering for override migration.
- Registered `app/license-overrides.json` as an input for license-report
and license-check preparation tasks.
- Centralized the dependency license report plugin version in
`buildSrc`.
- Added unit tests covering override application, cleanup, migration,
exact-version matching, concurrent versions, placeholder generation, and
numeric version ordering.
- Added documentation describing the override lifecycle, verification
requirements, maintenance workflow, and validation commands.
- Replaced broad null-license allowances for the two HubSpot modules
with explicit Apache License 2.0 metadata.
- Added accepted GNU Lesser General Public License name variants
encountered in dependency metadata.

The change was made because some dependencies have known upstream
licenses but do not publish license metadata in a form detected by the
Gradle license report plugin. Previously, these dependencies were
permitted through module-specific null-license exceptions, leaving
incomplete information in the generated report. The new mechanism
supplies verified metadata without overriding valid metadata published
by dependencies.

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-06 11:44:12 +00:00
Ludy b10fc1b2de fix(java): prevent executor, task, regex, and stream resource leaks (#7284)
# Description of Changes

- Added graceful shutdown handling for service-owned executors in
`JobExecutorService`, `PolicyEngine`, and `AsyncConfig`.
- Added expiration and cleanup for abandoned pending jobs in
`TaskManager`.
- Replaced the unbounded regex pattern cache with a bounded cache
limited to 512 entries.
- Ensured `Files.walk()` is closed correctly in `MobileScannerService`.
- These changes prevent unbounded heap growth, lingering virtual-thread
executors, and file-descriptor leaks.
- Added configurable pending-job expiration through
`stirling.job.pendingExpiryMinutes`, defaulting to 24 hours.

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-06 11:06:28 +00:00
Ludy 866e56728d fix(storage): delete share access records before expired share links (#7161)
# Description of Changes

- Updated expired share-link cleanup to delete related `FileShareAccess`
records before deleting their parent `FileShare` records.
- Wrapped the cleanup operation in a transaction to ensure the deletion
order is enforced atomically.
- Prevents foreign-key constraint violations and scheduled-task failures
during cleanup.
- The full backend check was limited by a Gradle distribution
download/network error.

```cmd
[backend:dev:proprietary] 16:25:43.362 [scheduled-vt-2] WARN  org.hibernate.orm.jdbc.error - HHH000247: ErrorCode: 23503, SQLState: 23503
[backend:dev:proprietary] 16:25:43.362 [scheduled-vt-2] WARN  org.hibernate.orm.jdbc.error - Referentielle Integrität verletzt: "FKQ6V4QH5LFCAWII0ABRVSJO5SG: PUBLIC.FILE_SHARE_ACCESSES FOREIGN KEY(FILE_SHARE_ID) REFERENCES PUBLIC.FILE_SHARES(FILE_SHARE_ID) (CAST(97 AS BIGINT))"
[backend:dev:proprietary] Referential integrity constraint violation: "FKQ6V4QH5LFCAWII0ABRVSJO5SG: PUBLIC.FILE_SHARE_ACCESSES FOREIGN KEY(FILE_SHARE_ID) REFERENCES PUBLIC.FILE_SHARES(FILE_SHARE_ID) (CAST(97 AS BIGINT))"; SQL statement:
[backend:dev:proprietary] delete from file_shares where file_share_id=? [23503-240]
[backend:dev:proprietary] 16:25:43.380 [scheduled-vt-2] ERROR o.s.s.s.TaskUtils$LoggingErrorHandler - Unexpected error occurred in scheduled task
[backend:dev:proprietary] org.springframework.dao.DataIntegrityViolationException: could not execute statement [Referentielle Integrität verletzt: "FKQ6V4QH5LFCAWII0ABRVSJO5SG: PUBLIC.FILE_SHARE_ACCESSES FOREIGN KEY(FILE_SHARE_ID) REFERENCES PUBLIC.FILE_SHARES(FILE_SHARE_ID) (CAST(97 AS BIGINT))"
[backend:dev:proprietary] Referential integrity constraint violation: "FKQ6V4QH5LFCAWII0ABRVSJO5SG: PUBLIC.FILE_SHARE_ACCESSES FOREIGN KEY(FILE_SHARE_ID) REFERENCES PUBLIC.FILE_SHARES(FILE_SHARE_ID) (CAST(97 AS BIGINT))"; SQL statement:
[backend:dev:proprietary] delete from file_shares where file_share_id=? [23503-240]] [delete from file_shares where file_share_id=?]; SQL [delete from file_shares where file_share_id=?]; constraint [FKQ6V4QH5LFCAWII0ABRVSJO5SG]
[backend:dev:proprietary]       at org.springframework.orm.jpa.hibernate.HibernateExceptionTranslator.convertHibernateAccessException(HibernateExceptionTranslator.java:169)
[backend:dev:proprietary]       at org.springframework.orm.jpa.hibernate.HibernateExceptionTranslator.convertHibernateAccessException(HibernateExceptionTranslator.java:131)
[backend:dev:proprietary]       at org.springframework.orm.jpa.hibernate.HibernateExceptionTranslator.translateExceptionIfPossible(HibernateExceptionTranslator.java:105)
[backend:dev:proprietary]       at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:223)
[backend:dev:proprietary]       at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:557)
[backend:dev:proprietary]       at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:794)
[backend:dev:proprietary]       at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:757)
[backend:dev:proprietary]       at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:687)
[backend:dev:proprietary]       at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:408)
[backend:dev:proprietary]       at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:130)
[backend:dev:proprietary]       at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
[backend:dev:proprietary]       at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:135)
[backend:dev:proprietary]       at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
[backend:dev:proprietary]       at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:166)
[backend:dev:proprietary]       at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
[backend:dev:proprietary]       at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:222)
[backend:dev:proprietary]       at jdk.proxy4/jdk.proxy4.$Proxy246.deleteAll(Unknown Source)
[backend:dev:proprietary]       at stirling.software.proprietary.storage.service.StorageCleanupService.cleanupExpiredShareLinks(StorageCleanupService.java:71)
[backend:dev:proprietary]       at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
[backend:dev:proprietary]       at java.base/java.lang.reflect.Method.invoke(Method.java:565)
[backend:dev:proprietary]       at org.springframework.scheduling.support.ScheduledMethodRunnable.runInternal(ScheduledMethodRunnable.java:128)
[backend:dev:proprietary]       at org.springframework.scheduling.support.ScheduledMethodRunnable.lambda$run$1(ScheduledMethodRunnable.java:122)
[backend:dev:proprietary]       at io.micrometer.observation.Observation.observe(Observation.java:569)
[backend:dev:proprietary]       at org.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:122)
[backend:dev:proprietary]       at org.springframework.scheduling.config.Task$OutcomeTrackingRunnable.run(Task.java:88)
[backend:dev:proprietary]       at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54)
[backend:dev:proprietary]       at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:545)
[backend:dev:proprietary]       at java.base/java.util.concurrent.FutureTask.runAndReset(FutureTask.java:369)
[backend:dev:proprietary]       at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:310)
[backend:dev:proprietary]       at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1090)
[backend:dev:proprietary]       at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:614)
[backend:dev:proprietary]       at java.base/java.lang.VirtualThread.run(VirtualThread.java:460)
[backend:dev:proprietary] Caused by: org.hibernate.exception.ConstraintViolationException: could not execute statement [Referentielle Integrität verletzt: "FKQ6V4QH5LFCAWII0ABRVSJO5SG: PUBLIC.FILE_SHARE_ACCESSES FOREIGN KEY(FILE_SHARE_ID) REFERENCES PUBLIC.FILE_SHARES(FILE_SHARE_ID) (CAST(97 AS BIGINT))"
[backend:dev:proprietary] Referential integrity constraint violation: "FKQ6V4QH5LFCAWII0ABRVSJO5SG: PUBLIC.FILE_SHARE_ACCESSES FOREIGN KEY(FILE_SHARE_ID) REFERENCES PUBLIC.FILE_SHARES(FILE_SHARE_ID) (CAST(97 AS BIGINT))"; SQL statement:
[backend:dev:proprietary] delete from file_shares where file_share_id=? [23503-240]] [delete from file_shares where file_share_id=?]
[backend:dev:proprietary]       at org.hibernate.dialect.H2Dialect.lambda$buildSQLExceptionConversionDelegate$0(H2Dialect.java:840)
[backend:dev:proprietary]       at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:34)
[backend:dev:proprietary]       at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:115)
[backend:dev:proprietary]       at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:184)
[backend:dev:proprietary]       at org.hibernate.engine.jdbc.mutation.internal.AbstractMutationExecutor.performNonBatchedMutation(AbstractMutationExecutor.java:145)
[backend:dev:proprietary]       at org.hibernate.engine.jdbc.mutation.internal.MutationExecutorSingleNonBatched.performNonBatchedOperations(MutationExecutorSingleNonBatched.java:53)
[backend:dev:proprietary]       at org.hibernate.engine.jdbc.mutation.internal.AbstractMutationExecutor.execute(AbstractMutationExecutor.java:66)
[backend:dev:proprietary]       at org.hibernate.persister.entity.mutation.AbstractDeleteCoordinator.doStaticDelete(AbstractDeleteCoordinator.java:268)
[backend:dev:proprietary]       at org.hibernate.persister.entity.mutation.AbstractDeleteCoordinator.delete(AbstractDeleteCoordinator.java:79)
[backend:dev:proprietary]       at org.hibernate.action.internal.EntityDeleteAction.execute(EntityDeleteAction.java:119)
[backend:dev:proprietary]       at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:634)
[backend:dev:proprietary]       at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:505)
[backend:dev:proprietary]       at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:381)
[backend:dev:proprietary]       at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:40)
[backend:dev:proprietary]       at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:138)
[backend:dev:proprietary]       at org.hibernate.internal.SessionImpl.fireFlush(SessionImpl.java:1484)
[backend:dev:proprietary]       at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:481)
[backend:dev:proprietary]       at org.hibernate.internal.SessionImpl.flushBeforeTransactionCompletion(SessionImpl.java:2111)
[backend:dev:proprietary]       at org.hibernate.internal.SessionImpl.beforeTransactionCompletion(SessionImpl.java:2033)
[backend:dev:proprietary]       at org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.beforeTransactionCompletion(JdbcCoordinatorImpl.java:410)
[backend:dev:proprietary]       at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.beforeCompletionCallback(JdbcResourceLocalTransactionCoordinatorImpl.java:166)
[backend:dev:proprietary]       at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commitNoRollbackOnly(JdbcResourceLocalTransactionCoordinatorImpl.java:248)
[backend:dev:proprietary]       at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:242)
[backend:dev:proprietary]       at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:89)
[backend:dev:proprietary]       at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:553)
[backend:dev:proprietary]       ... 27 common frames omitted
[backend:dev:proprietary] Caused by: org.h2.jdbc.JdbcSQLIntegrityConstraintViolationException: Referentielle Integrität verletzt: "FKQ6V4QH5LFCAWII0ABRVSJO5SG: PUBLIC.FILE_SHARE_ACCESSES FOREIGN KEY(FILE_SHARE_ID) REFERENCES PUBLIC.FILE_SHARES(FILE_SHARE_ID) (CAST(97 AS BIGINT))"
[backend:dev:proprietary] Referential integrity constraint violation: "FKQ6V4QH5LFCAWII0ABRVSJO5SG: PUBLIC.FILE_SHARE_ACCESSES FOREIGN KEY(FILE_SHARE_ID) REFERENCES PUBLIC.FILE_SHARES(FILE_SHARE_ID) (CAST(97 AS BIGINT))"; SQL statement:
[backend:dev:proprietary] delete from file_shares where file_share_id=? [23503-240]
[backend:dev:proprietary]       at org.h2.message.DbException.getJdbcSQLException(DbException.java:520)
[backend:dev:proprietary]       at org.h2.message.DbException.getJdbcSQLException(DbException.java:489)
[backend:dev:proprietary]       at org.h2.message.DbException.get(DbException.java:223)
[backend:dev:proprietary]       at org.h2.message.DbException.get(DbException.java:199)
[backend:dev:proprietary]       at org.h2.constraint.ConstraintReferential.checkRow(ConstraintReferential.java:363)
[backend:dev:proprietary]       at org.h2.constraint.ConstraintReferential.checkRowRefTable(ConstraintReferential.java:380)
[backend:dev:proprietary]       at org.h2.constraint.ConstraintReferential.checkRow(ConstraintReferential.java:254)
[backend:dev:proprietary]       at org.h2.table.Table.fireConstraints(Table.java:1208)
[backend:dev:proprietary]       at org.h2.table.Table.fireAfterRow(Table.java:1226)
[backend:dev:proprietary]       at org.h2.command.dml.Delete.update(Delete.java:81)
[backend:dev:proprietary]       at org.h2.command.dml.DataChangeStatement.update(DataChangeStatement.java:77)
[backend:dev:proprietary]       at org.h2.command.CommandContainer.update(CommandContainer.java:139)
[backend:dev:proprietary]       at org.h2.command.Command.executeUpdate(Command.java:306)
[backend:dev:proprietary]       at org.h2.command.Command.executeUpdate(Command.java:250)
[backend:dev:proprietary]       at org.h2.jdbc.JdbcPreparedStatement.executeUpdateInternal(JdbcPreparedStatement.java:213)
[backend:dev:proprietary]       at org.h2.jdbc.JdbcPreparedStatement.executeUpdate(JdbcPreparedStatement.java:172)
[backend:dev:proprietary]       at com.zaxxer.hikari.pool.ProxyPreparedStatement.executeUpdate(ProxyPreparedStatement.java:61)
[backend:dev:proprietary]       at com.zaxxer.hikari.pool.HikariProxyPreparedStatement.executeUpdate(HikariProxyPreparedStatement.java)
[backend:dev:proprietary]       at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:181)
[backend:dev:proprietary]       ... 48 common frames omitted
```

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-06 11:06:16 +00:00
brios e560ee4cc4 chore(deps): update junrar dependency to version 8.0.0 (#7210)
# Description of Changes

Since this was a major version update i tested manually, afterwards
figured i'll submit as PR.

This version of junrar adds long-awaited (by me) RAR 5 support to the
library. RAR 5 is newest version of the RAR file format and was not
available in previous Junrar version, but is somewhat common for CBR
files to be RAR 5.


For junrar release notes see:
https://github.com/junrar/junrar/releases/tag/v8.0.0

Changes:
- Bumped junrar dep to version 8.0.0


<!--
Please provide a summary of the changes, including:

- What was changed
- Why the change was made
- Any challenges encountered

Closes #(issue_number)
-->

---

## Checklist

### General

- [X] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [X] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [X] I have performed a self-review of my own code
- [X] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [X] I have run `task check` to verify linters, typechecks, and tests
pass
- [X] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-06 10:25:22 +00:00
brios 7cccee4c34 refactor(get-info): remove redundant PDF validation logic (#7213)
# Description of Changes

Could not get past validation, since very few endpoint have such
validation, i think redundant.

Changes:
* Removed the `validatePdfFile` method, which previously checked for
file presence, size limits, and content type, from `GetInfoOnPDF.java`.
* Deleted the invocation of `validatePdfFile` and its associated error
handling from the `getPdfInfo` method, so uploaded files are no longer
validated at this layer.

<!--
Please provide a summary of the changes, including:

- What was changed
- Why the change was made
- Any challenges encountered

Closes #(issue_number)
-->

---

## Checklist

### General

- [X] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [X] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [X] I have performed a self-review of my own code
- [X] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [X] I have run `task check` to verify linters, typechecks, and tests
pass
- [X] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-06 10:17:27 +00:00
brios a2dd0298dc refactor(api): replace length checks with isEmpty (#7214)
# Description of Changes

Stylistic problem reported by static analyzer. 


Changes:
* Replaced `sb.length() > 0` and `sb.length() == 0` with `!sb.isEmpty()`
and `sb.isEmpty()` for `StringBuilder`, `String`, and collections
throughout the codebase, improving readability and aligning with modern
Java best practices.


<!--
Please provide a summary of the changes, including:

- What was changed
- Why the change was made
- Any challenges encountered

Closes #(issue_number)
-->

---

## Checklist

### General

- [x] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [x] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [x] I have performed a self-review of my own code
- [x] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [x] I have run `task check` to verify linters, typechecks, and tests
pass
- [x] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-06 10:00:04 +00:00
ConnorYoh d0d197f09f Procurement: draft Enterprise Agreement + signature, legal pages & consent, quote/agreement split (#7021)
Consolidates the enterprise procurement and legal work into one PR off
`main`. Supersedes #7020 (closed; every commit from it is contained
here). Sits on top of PAYG prepaid bundles (#7032) and the `--color-*` →
`--c-*` portal token rename.

## Why

Enterprise procurement was a mock. The stage screens read from a fake
state machine, the "agreement" was prose hardcoded in a component, and
nothing a buyer did was recorded anywhere. To actually sell to an
enterprise we need three things it didn't have: a real document they can
read and sign, a record that proves they signed that exact version, and
a licence that flips when they pay.

## What

**The agreement is a real versioned document**

- Registry at `resources/legal/manifest.json` +
`legal/<id>/<version>/*.md`. Publishing a new version is a markdown file
and a manifest bump, no code change. `@`-prefixed parts are generated
sections.
- `AgreementAssembler` builds MSA (Part A) + generated Order Form (Part
B) + DPA (Part C) as one document. Only the Order Form varies per deal.
- `AgreementPdfRenderer` goes through our own pipeline (commonmark →
`FileToPdf`/WeasyPrint), so we dogfood it.
- Immutable signature record pinning document id and version, a SHA-256
of the exact rendered markdown, the variable snapshot, typed signatory
details, timestamp and IP.

**Legal document pages and consent logging**

- `GET /api/v1/legal/{docId}` serves any registry document; a viewer
modal renders it with a draft badge. The SLA exhibit is viewable for the
first time.
- `legal_consent` + `POST /api/v1/legal/consent`. EULA clickwrap is
recorded once: at trial start, or at the quote step only if there was no
trial.

**Quote and Agreement are separate steps**

The quote step is a plain itemised review (figures, renewal, PO) with
download and "Accept quote". Accepting advances to the agreement and
does not charge Stripe. Signing the agreement is still the commitment
point.

**One quote number**

We no longer mint our own reference. The Stripe quote number is the
identifier everywhere, so the UI and the memo can't disagree.
`quote_number` is nullable until Stripe assigns it at finalisation
(`20260808000000`).

**Payment takes the deal live**

`invoice.paid` on the stripe-webhook moves the deal to live and the UI
reflects it. Nothing watched for payment before, so a paid customer sat
in "payment" forever. Needs `invoice.paid` enabled on the webhook
endpoint in the Stripe dashboard.

**Security**

Any signup could self-issue a $0 enterprise licence, from three things
compounding: leader-on-signup, no entitlement gate, and no ACV floor.
So: `startTrial` now has a stage guard (it was replacing committed
licences), the offline `.lic` is gated on entitlement, the ACV floor is
enforced before the quote persists, and the air-gap check reads the
quote's deployment rather than the deal's. Invitee emails are redacted
in logs. Dev and Storybook were hitting real Stripe; both now route
through `resolveDemoResponse`.

**Removed the dead procurement island**

The original stage-by-stage page survived the rebuild with no route and
no consumer, so it was invisible to review but still cost a reader's
time. 16 unreferenced files, 182 lines of superseded API, 53 orphaned
en-US keys, and `Procurement.css` from 1665 to 968 lines. Nothing
deleted had a live consumer.

## Screenshots

Home, deal underway (hero card footer):

<!-- home-in-procurement.png -->

Quote builder, step 1:

<!-- quote-builder.png -->

Agreement, ready to sign:

<!-- agreement-signing.png -->

Payment and live:

<!-- stage-payment.png / stage-live.png -->

## How to test

**Storybook** covers every state without a backend:

```bash
cd frontend && npm run storybook
```

Then `Portal/Procurement/*`:

| Story | What to look at |
| --- | --- |
| `DealStatusHero` — Trial / Quote / Agreement / Payment / Live | One
hero per stage: progress band, one-line status, stage CTA |
| `QuoteBuilder` — Default | 4 steps. Users + volume drive the price;
Governance and PDF size are multipliers; step 4 is the itemised review |
| `ProcurementAgreement` — Default / Signing | Header actions,
always-visible scrollbar on the paper, one-line signature row |
| `ProcurementStages` — Payment / Live / License | "View & pay invoice"
opens Stripe directly; licence key and `.lic` download |
| `Views/Home` — Subscribed In Procurement | The hero in real page
context |

Note: `ProcurementAgreement` renders "Could not load the agreement" in
Storybook because it fetches the document from the backend. The chrome
is accurate, the paper body needs the app.

**Full flow** needs SaaS running and a linked team:

1. Home → **Explore enterprise** → trial setup (deployment + seats).
EULA is recorded here.
2. **Build your quote** → 4 steps → Generate. Buyer details are required
first.
3. Review the itemised quote → **Accept quote**. Confirm Stripe was
*not* charged.
4. Agreement → tick, fill signatory, **Sign agreement**. Check
`procurement_signature` for the version and content hash.
5. **View & pay invoice** → pay in Stripe test mode → deal should move
to live on the `invoice.paid` webhook.

Worth reviewing specifically: the licence cannot be issued without
entitlement (step 3 before payment), and `startTrial` on an
already-committed deal is rejected rather than overwriting.

## Verification

- `:saas compileJava` + `spotlessJavaCheck`
- `task frontend:check:all` green end to end: 9 typecheck variants,
eslint at zero warnings, `theme-lint`, `lint:css`, prettier, build,
**1656 tests across 188 files**
- 7 deno tests on the `invoice.paid` handler, covering all four shapes
Stripe uses for the subscription reference

## Open, not addressed here

- **The commercial model contradicts itself in three places.** The Order
Form says annual-in-advance, the MSA §2.3/§3.2 implies otherwise, the
quote engine computes `tcv = annualNet × termYears` flat, and Stripe
only invoices one year. Needs a decision before this is customer-facing.
- The 25 MB data-processing increments vs the ×1.4/×2.4 size multiplier,
deferred pending Matt.
- All legal text is **draft**. It renders with a draft badge and is not
presented as executed; counsel's read is still a publish gate.
- `{{subprocessor_url}}` / `{{eula_url}}` awaiting marketing's final
links.
- `frontend-a11y` is red on pre-existing portal contrast debt, deferred
by decision.

## Schema notes

Two migrations land on the SaaS side (`v3`), both applied by that repo's
PR CI:

- `20260808000000` drops the NOT NULL on
`procurement_quote.quote_number`, which is required rather than cosmetic
— the number now comes from Stripe at finalisation, so a draft holds
NULL, and `ddl-auto` cannot drop an existing NOT NULL itself.
- `20260809000000` adds `procurement_deal.last_paid_invoice_id`,
nullable.

Nothing here needs a migration in this repo: Flyway is not on the
classpath, so the Java side only ever adds via `ddl-auto`, and Postgres
migrations run ahead of the app deploy.
2026-08-05 15:23:50 +00:00
Anthony StirlingandReece Browne d7c130fca9 Serve SPA shell for deep frontend routes (#7145)
# Description of Changes

stops the /new urls crashing page on f5 


---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.

---------

Co-authored-by: Reece Browne <74901996+reecebrowne@users.noreply.github.com>
2026-08-05 11:03:12 +00:00
Anthony Stirling 921bdac4b7 Desktop installer fixes (#7174)
# Description of Changes

* Adds a `windows-11-arm` CI/release leg (NSIS, Microsoft JDK 25,
updater keys); JPDFium natives deliberately excluded
(`jpdfiumPlatforms=none`) until published, so don't ship ARM64
installers to users yet
* Defaults `WEBKIT_DISABLE_DMABUF_RENDERER=1` on Linux (crash switching
tools on NVIDIA)
* Strips the bundled libwayland from AppImages (blank window on Fedora
Wayland)
* Blocks off-app webview navigation + window drop guard + close failsafe
(drag-drop bricks the app)
* 120s startup grace before the backend is declared unhealthy, restart
success only announced after a real health check ("Backend stopped
unexpectedly" spam and likely the OAuth port churn)
* Verified: green `windows-arm64` build (234 MB NSIS artifact) and green
Linux run with libwayland confirmed stripped
* JPDFium fixes for multi threading issues
---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-04 14:40:41 +01:00
ConnorYoh 50bc4a7866 fix(storage): don't query the encryption key registry when storage is off (unblocks backend:dev:saas) (#7265)
# Description of Changes

Fixes a startup failure introduced by #7155 and reported against `task
backend:dev:saas`.

**What goes wrong**

`StorageProviderConfig.storageEncryptionState(...)` is created on every
startup, in every profile. When `storage.encryption.enabled` is false —
the default, and what SaaS ships — the `||` short-circuit evaluates
`fileEncryptionKeyRepository.count()`, a live query against
`file_encryption_keys`:

```java
if (writeEnabled || fileEncryptionKeyRepository.count() > 0) {   // <- always runs when the flag is off
```

That table only exists if `ddl-auto=update` managed to create it. When
it cannot — permissions on a shared Supabase branch DB, concurrent DDL
from several developers, schema ordering — **ddl-auto logs and
continues**, so the situation used to be a warning nobody noticed. Now
it is a query that throws during bean creation and takes the whole
context down.

Two things make this sting in SaaS specifically: `storage.enabled` is
false there, so before this feature nothing ever touched the table; and
`hibernate.default_schema=stirling_pdf` means the table has to exist in
a schema the app may not be able to create in.

There is a second exposure on the request path:
`suppressDirectDownloads()` also counts (60s cached), so even a
surviving boot could 500 on downloads.

**Fix**

- The boot probe runs only when `storage.enabled` is true, so a
deployment that does not use storage never touches the table.
- Registry reads are wrapped. The boot probe degrades to "no keys"
rather than propagating; `suppressDirectDownloads()` **fails safe by
suppressing** rather than issuing a presigned URL it cannot vouch for.
Losing the direct-download fast path is recoverable; serving ciphertext
is not.

**Safety is unchanged, and that is the important part.** The decorator
is still installed unconditionally, so any blob carrying the `SPDFEAR1`
magic is still decrypted via lazy materialisation or fails loudly — the
eager probe only ever bought *earlier* master-key verification. A node
that can actually serve stored files has `storage.enabled` on by
definition, which is exactly the node the drifted-node protection is
for; that test now configures it that way, and a new test pins that the
decorator remains installed even with storage off.

**Tests** — storage-disabled never calls `count()`; an unreadable
registry still boots *and* still suppresses direct downloads; the
decorator stays installed with storage off; storage-enabled still
probes. Full proprietary suite green apart from the pre-existing
Windows-symlink `FolderIdentitiesTest` failure, which is environmental
and unrelated.

**Note on scope:** deliberately minimal so it can land quickly. The
Aikido `findAll()` code-quality finding lives in #7173 only
(`rotateMasterKey` does not exist on main), so it is fixed there rather
than here. #7173 will be rebased once this merges.

---

## Checklist

### General

- [x] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [x] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [x] I have performed a self-review of my own code
- [x] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [x] I have run `task check` to verify linters, typechecks, and tests
pass
- [x] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-03 16:41:56 +00:00
Anthony Stirling 21dff695fe Add SFTP, FTP and SMB network sources to the processor (#7153)
# Description of Changes

Add SFTP, FTP and SMB network sources to the processor plus UI change to
enable it

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-03 14:58:10 +00:00
James BruntonandAnthony Stirling cd199c8659 Define tool inputs & outputs in a structured way (#7204)
# Description of Changes
Change tool APIs to use structured definitions for input/output/type
info because we need that info to be able to validate whether policies
can actually successfully work based on whether one tool accepts the
output of another. There were various bugs in the previous string
definitions because of either misspellings or just incorrect
definitions, so I've gone through and fixed all that I can find.

<img width="729" height="271" alt="image"
src="https://github.com/user-attachments/assets/08357e96-6fbb-4b9c-ba4d-8995420c7b86"
/>

<img width="749" height="264" alt="image"
src="https://github.com/user-attachments/assets/76f46284-1866-4b64-b1ed-2480e01866e9"
/>

<img width="402" height="636" alt="image"
src="https://github.com/user-attachments/assets/8f7a36ca-2845-4f14-a2df-ec9c772e66f6"
/>

<img width="393" height="317" alt="image"
src="https://github.com/user-attachments/assets/46d8b891-9820-4ce3-8109-a8b782277037"
/>

---------

Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
2026-08-03 14:13:43 +00:00
dependabot[bot] 88cdfb3a43 build(deps): bump org.postgresql:postgresql from 42.7.11 to 42.7.13 (#7243)
Bumps [org.postgresql:postgresql](https://github.com/pgjdbc/pgjdbc) from
42.7.11 to 42.7.13.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/pgjdbc/pgjdbc/releases">org.postgresql:postgresql's
releases</a>.</em></p>
<blockquote>
<h2>v42.7.13</h2>
<h2>Changes</h2>
<ul>
<li>docs: add 42.7.13 release changelog <a
href="https://github.com/davecramer"><code>@​davecramer</code></a> (<a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4270">#4270</a>)</li>
<li>Adjust EditorConfig für Makefile <a
href="https://github.com/BaumiCoder"><code>@​BaumiCoder</code></a> (<a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4279">#4279</a>)</li>
<li>fix(scram): fail closed on channel-binding downgrade (no scram bump)
<a href="https://github.com/vlsi"><code>@​vlsi</code></a> (<a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4272">#4272</a>)</li>
<li>Bump pgjdbc version from 42.7.12 to 42.7.13 <a
href="https://github.com/davecramer"><code>@​davecramer</code></a> (<a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4269">#4269</a>)</li>
<li>chore: remove test-anorm-sbt module and its disabled CI wiring <a
href="https://github.com/vlsi"><code>@​vlsi</code></a> (<a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4261">#4261</a>)</li>
<li>refactor(test-gss): convert to Java/JUnit 5 submodule of the main
build <a href="https://github.com/vlsi"><code>@​vlsi</code></a> (<a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4166">#4166</a>)</li>
<li>ci: derive PG test versions from a Renovate-managed maxPgVersion <a
href="https://github.com/vlsi"><code>@​vlsi</code></a> (<a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4218">#4218</a>)</li>
<li>feat(insert): cap reWriteBatchedInserts by the protocol limit, not
128 <a href="https://github.com/vlsi"><code>@​vlsi</code></a> (<a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4207">#4207</a>)</li>
<li>refactor(metadata): derive getPrimaryKeys from pg_constraint.conkey
<a href="https://github.com/vlsi"><code>@​vlsi</code></a> (<a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4202">#4202</a>)</li>
<li>fix(protocol): defer flushes until response processing <a
href="https://github.com/vlsi"><code>@​vlsi</code></a> (<a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4196">#4196</a>)</li>
<li>fix(build): resolve the Temurin 8 test toolchain by vendor <a
href="https://github.com/vlsi"><code>@​vlsi</code></a> (<a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4257">#4257</a>)</li>
<li>build: include multi-release source sets in the JaCoCo coverage
report <a href="https://github.com/vlsi"><code>@​vlsi</code></a> (<a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4256">#4256</a>)</li>
<li>fix(ci): read java_vendor before overwriting java_distribution <a
href="https://github.com/vlsi"><code>@​vlsi</code></a> (<a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4255">#4255</a>)</li>
<li>ci: generate the whole matrix in one batch, coverage job included <a
href="https://github.com/vlsi"><code>@​vlsi</code></a> (<a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4253">#4253</a>)</li>
<li>ci: pass CODECOV_TOKEN so protected-branch coverage uploads succeed
<a href="https://github.com/vlsi"><code>@​vlsi</code></a> (<a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4254">#4254</a>)</li>
<li>ci: collect coverage on one pinned job <a
href="https://github.com/vlsi"><code>@​vlsi</code></a> (<a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4245">#4245</a>)</li>
<li>ci: apply -DqueryTimeout from the matrix query_timeout axis <a
href="https://github.com/vlsi"><code>@​vlsi</code></a> (<a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4246">#4246</a>)</li>
<li>ci: make Codecov project and patch statuses informational <a
href="https://github.com/vlsi"><code>@​vlsi</code></a> (<a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4244">#4244</a>)</li>
<li>fix(build): restore JaCoCo XML report so Codecov receives coverage
<a href="https://github.com/vlsi"><code>@​vlsi</code></a> (<a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4240">#4240</a>)</li>
<li>test(replication): shrink big-transaction inserts to avoid CI
timeouts <a href="https://github.com/vlsi"><code>@​vlsi</code></a> (<a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4243">#4243</a>)</li>
<li>update maintainers <a
href="https://github.com/davecramer"><code>@​davecramer</code></a> (<a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4222">#4222</a>)</li>
<li>test: add hermetic test for localSocketAddress <a
href="https://github.com/vlsi"><code>@​vlsi</code></a> (<a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4224">#4224</a>)</li>
<li>docs(translation): clean up leftover German header in ja.po <a
href="https://github.com/vlsi"><code>@​vlsi</code></a> (<a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4206">#4206</a>)</li>
<li>Update ja.po <a
href="https://github.com/davecramer"><code>@​davecramer</code></a> (<a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/2004">#2004</a>)</li>
<li>test: add PostgreSQL 18 to the CI test matrix <a
href="https://github.com/vlsi"><code>@​vlsi</code></a> (<a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4198">#4198</a>)</li>
<li>test: silence expected SSPI warning stack trace in
SSPIClientWaffleTest <a
href="https://github.com/vlsi"><code>@​vlsi</code></a> (<a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4197">#4197</a>)</li>
<li>fix(ssl): build PKIX trust anchors without a KeyStore so FIPS-mode
JVMs can load sslrootcert <a
href="https://github.com/vlsi"><code>@​vlsi</code></a> (<a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4193">#4193</a>)</li>
<li>test: fix flaky sentLocationEqualToLastReceiveLSN replication test
<a href="https://github.com/vlsi"><code>@​vlsi</code></a> (<a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4175">#4175</a>)</li>
<li>build: promote MethodCanBeStatic to error level <a
href="https://github.com/vlsi"><code>@​vlsi</code></a> (<a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4172">#4172</a>)</li>
<li>Fix PGInterval.setSeconds to reject out of range and NaN values <a
href="https://github.com/sehrope"><code>@​sehrope</code></a> (<a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4194">#4194</a>)</li>
<li>Replace connectThreadFactory with connectExecutor <a
href="https://github.com/sehrope"><code>@​sehrope</code></a> (<a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4165">#4165</a>)</li>
<li>Fix deleting temp file when spooling large stream to disk in
StreamWrapper <a
href="https://github.com/sehrope"><code>@​sehrope</code></a> (<a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4190">#4190</a>)</li>
<li>chore: Add top level /scratch to gitignore <a
href="https://github.com/sehrope"><code>@​sehrope</code></a> (<a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4164">#4164</a>)</li>
<li>refactor: favour composition over inheritance for Driver.ConnectTask
<a href="https://github.com/vlsi"><code>@​vlsi</code></a> (<a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4160">#4160</a>)</li>
<li>Fix NumberParser.getFastLong(...) handling of overlong values <a
href="https://github.com/sehrope"><code>@​sehrope</code></a> (<a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4163">#4163</a>)</li>
<li>build: produce a multi-release jar from reduced-pom.xml on Java 11+
<a href="https://github.com/vlsi"><code>@​vlsi</code></a> (<a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4157">#4157</a>)</li>
<li>Add connectThreadFactory and refactor Driver to use FutureTask for
loginTimeout connection attempts <a
href="https://github.com/sehrope"><code>@​sehrope</code></a> (<a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4120">#4120</a>)</li>
<li>test: verify custom properties reach socket factory <a
href="https://github.com/vlsi"><code>@​vlsi</code></a> (<a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4125">#4125</a>)</li>
<li>test: fix LazyCleanerTest timeouts for the lingering Java 8 cleanup
thread <a href="https://github.com/vlsi"><code>@​vlsi</code></a> (<a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4122">#4122</a>)</li>
<li>test: stabilise StatementTest.fastCloses on Windows <a
href="https://github.com/vlsi"><code>@​vlsi</code></a> (<a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4121">#4121</a>)</li>
<li>fix: append default non-proxy hosts when socksNonProxyHosts is set
<a href="https://github.com/davecramer"><code>@​davecramer</code></a>
(<a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4045">#4045</a>)</li>
<li>test: budget terminating Sync in BatchDeadlockTest small-RETURNING
branch <a href="https://github.com/vlsi"><code>@​vlsi</code></a> (<a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4116">#4116</a>)</li>
<li>test: make message assertions locale-independent <a
href="https://github.com/vlsi"><code>@​vlsi</code></a> (<a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4113">#4113</a>)</li>
<li>build: drop xgettext default keywords; regenerate translations <a
href="https://github.com/vlsi"><code>@​vlsi</code></a> (<a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4100">#4100</a>)</li>
<li>ci: opt-in scheduled workflows via ENABLE_SCHEDULED_JOBS repo
variable <a href="https://github.com/vlsi"><code>@​vlsi</code></a> (<a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4085">#4085</a>)</li>
<li>Avoid direct java.lang.management dependency in maxResultBuffer
parser <a
href="https://github.com/mblakley-casana"><code>@​mblakley-casana</code></a>
(<a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4069">#4069</a>)</li>
<li>fix: restore pre-describe for generated-key batches <a
href="https://github.com/bilalshehata"><code>@​bilalshehata</code></a>
(<a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4014">#4014</a>)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/pgjdbc/pgjdbc/blob/master/CHANGELOG.md">org.postgresql:postgresql's
changelog</a>.</em></p>
<blockquote>
<h2>[42.7.13] (2026-07-06)</h2>
<h3>Added</h3>
<ul>
<li>feat: invalidate the prepared-statement cache when the server
reports a <code>search_path</code> change via GUC_REPORT (PostgreSQL
18+), so cached plans are no longer used against the wrong schema [PR <a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4259">#4259</a>](<a
href="https://redirect.github.com/pgjdbc/pgjdbc/pull/4259">pgjdbc/pgjdbc#4259</a>)</li>
<li>feat: <code>reWriteBatchedInserts</code> now merges up to 32768 rows
into one multi-values <code>INSERT</code> (bounded by the 65535
bind-parameter limit on the extended protocol) instead of capping at
128, which speeds up batches of few-column rows. The new
<code>reWriteBatchedInsertsSize</code> connection property lowers that
cap when set; the default of <code>0</code> uses that maximum. [PR <a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4207">#4207</a>](<a
href="https://redirect.github.com/pgjdbc/pgjdbc/pull/4207">pgjdbc/pgjdbc#4207</a>)</li>
<li>feat: invalidate the prepared-statement cache after
CREATE/DROP/ALTER so callers no longer trip on &quot;cached plan must
not change result type&quot; without opting into
<code>autosave=ALWAYS</code>. Controlled by the new
<code>flushCacheOnDdl</code> connection property (default
<code>true</code>); set to <code>false</code> for the prior behaviour.
[PR <a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4067">#4067</a>](<a
href="https://redirect.github.com/pgjdbc/pgjdbc/pull/4067">pgjdbc/pgjdbc#4067</a>)</li>
<li>feat: add <code>connectExecutor</code> connection property to
customize the <code>Executor</code> used to run the worker task that
performs the connection attempt when <code>loginTimeout</code> is in
effect. The value is the fully qualified name of a class implementing
<code>java.util.concurrent.Executor</code>. With a null value, the
default, the driver retains the prior behavior of running the connection
attempt on a daemon thread named <code>&quot;PostgreSQL JDBC driver
connection thread&quot;</code>. The executor must run the task on a
thread other than the caller's. Running the attempt on a named thread
lets applications that monitor driver-created threads identify it. [PR
<a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4165">#4165</a>](<a
href="https://redirect.github.com/pgjdbc/pgjdbc/pull/4165">pgjdbc/pgjdbc#4165</a>)</li>
<li>feat: add <code>classLoaderStrategy</code> connection property to
control which classloaders the driver searches when loading a class
named by a connection property, for example <code>socketFactory</code>.
The default <code>driver-first</code> now falls back to the thread
context classloader when the driver's classloader cannot resolve the
class, which fixes class loading in non-flat class paths such as Quarkus
and OSGi. Set <code>driver</code> to keep the previous
driver-classloader-only behaviour, or <code>context-first</code> to
prefer the thread context classloader [Issue <a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/2112">#2112</a>](<a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/2112">pgjdbc/pgjdbc#2112</a>)
[PR <a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4167">#4167</a>](<a
href="https://redirect.github.com/pgjdbc/pgjdbc/pull/4167">pgjdbc/pgjdbc#4167</a>)</li>
<li>feat: add OID constants for geometric arrays, <code>RECORD</code>,
and <code>refcursor</code> [PR <a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4220">#4220</a>](<a
href="https://redirect.github.com/pgjdbc/pgjdbc/pull/4220">pgjdbc/pgjdbc#4220</a>)</li>
<li>feat: <code>LargeObject</code> <code>BlobInputStream</code> now
skips by seeking instead of reading, and the driver exposes the server
version so it can select the 64-bit large-object API where available [PR
<a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4204">#4204</a>](<a
href="https://redirect.github.com/pgjdbc/pgjdbc/pull/4204">pgjdbc/pgjdbc#4204</a>)</li>
</ul>
<h3>Changed</h3>
<ul>
<li>refactor: the worker that runs the connection attempt under
<code>loginTimeout</code> is now a <code>FutureTask</code>
(<code>ConnectTask</code>) instead of the hand-rolled
<code>ConnectThread</code>. When the caller hits the timeout, the task
is now cancelled with <code>cancel(true)</code>, which interrupts the
worker thread rather than letting it run to completion. This makes the
connection attempt interruptible, so <code>loginTimeout</code> can stop
a slow connection attempt instead of leaking a thread. As before, a
connection that the worker still manages to establish after the caller
gives up is closed by the worker so that it does not leak. There are no
public API changes and this should only lead to faster background
resource cleanup for connections that time out. [PR <a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4120">#4120</a>](<a
href="https://redirect.github.com/pgjdbc/pgjdbc/pull/4120">pgjdbc/pgjdbc#4120</a>)</li>
<li>chore: <code>PGXAConnection.ConnectionHandler</code> now rejects
<code>setAutoCommit(false)</code> and <code>setSavepoint(...)</code>
during an active XA branch, in addition to the long-rejected
<code>setAutoCommit(true)</code> / <code>commit()</code> /
<code>rollback()</code>. The <code>setSavepoint</code> rejection was
already meant to be in place but the guard misspelled the method name as
<code>setSavePoint</code>, so savepoints silently went through. Both
changes bring the proxy in line with JTA 1.2 §3.4. [PR <a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4114">#4114</a>](<a
href="https://redirect.github.com/pgjdbc/pgjdbc/pull/4114">pgjdbc/pgjdbc#4114</a>)</li>
<li>chore: <code>commitPrepared</code> /
<code>rollback</code>-of-prepared now return <code>XAER_RMFAIL</code>
instead of <code>XAER_RMERR</code> when the underlying connection is
left in a non-idle <code>TransactionState</code>. Transaction managers
(Geronimo, Narayana, Atomikos) treat <code>XAER_RMFAIL</code> as
retryable on a fresh <code>XAResource</code>; the prepared transaction
is no longer abandoned. [PR <a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4114">#4114</a>](<a
href="https://redirect.github.com/pgjdbc/pgjdbc/pull/4114">pgjdbc/pgjdbc#4114</a>)</li>
<li>refactor: derive <code>getPrimaryKeys</code> from
<code>pg_constraint.conkey</code> [PR <a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4202">#4202</a>](<a
href="https://redirect.github.com/pgjdbc/pgjdbc/pull/4202">pgjdbc/pgjdbc#4202</a>)</li>
</ul>
<h3>Fixed</h3>
<ul>
<li>fix: the published GitHub release now ships the released
<code>postgresql-&lt;version&gt;.jar</code> and its detached PGP
signature, taken from the same signed build that is uploaded to Maven
Central, instead of a leftover SNAPSHOT jar [Issue <a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/3812">#3812</a>](<a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/3812">pgjdbc/pgjdbc#3812</a>)
[PR <a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/3814">#3814</a>](<a
href="https://redirect.github.com/pgjdbc/pgjdbc/pull/3814">pgjdbc/pgjdbc#3814</a>)</li>
<li>fix: simplify the <code>Statement#cancel</code> state machine by
dropping the redundant <code>CANCELLED</code> state.
<code>killTimerTask</code> now waits for the state to return to
<code>IDLE</code> directly, which removes a spin-forever case when more
than one thread observes the cancel completing [PR <a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/1827">#1827</a>](<a
href="https://redirect.github.com/pgjdbc/pgjdbc/pull/1827">pgjdbc/pgjdbc#1827</a>).</li>
<li>perf: defer simple-query flushes until the driver reads the
response, allowing <code>BEGIN</code> and the following query to share a
network flush [Issue <a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/3894">#3894</a>](<a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/3894">pgjdbc/pgjdbc#3894</a>)
[PR <a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4196">#4196</a>](<a
href="https://redirect.github.com/pgjdbc/pgjdbc/pull/4196">pgjdbc/pgjdbc#4196</a>)</li>
<li>fix: <code>reWriteBatchedInserts</code> no longer throws
<code>IllegalArgumentException</code> when batching a parameterless
<code>INSERT</code> (for example <code>INSERT INTO t VALUES (1,
2)</code>) of 256 rows or more [PR <a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4207">#4207</a>](<a
href="https://redirect.github.com/pgjdbc/pgjdbc/pull/4207">pgjdbc/pgjdbc#4207</a>)</li>
<li>fix: a comment before <code>CALL</code> in a
<code>CallableStatement</code> no longer hides the native call, so OUT
parameter registration works for <code>/* comment */ call proc(?,
?)</code> and similar. <code>Parser.modifyJdbcCall</code> now skips
leading whitespace and SQL comments (both <code>--</code> and <code>/*
*/</code>) before the call, tolerates a trailing comment after a <code>{
... }</code> escape, and no longer adds a spurious comma when moving an
OUT parameter into a call whose arguments are only a comment [Issue <a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/2538">#2538</a>](<a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/2538">pgjdbc/pgjdbc#2538</a>)
[PR <a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4209">#4209</a>](<a
href="https://redirect.github.com/pgjdbc/pgjdbc/pull/4209">pgjdbc/pgjdbc#4209</a>)</li>
<li>fix: <code>PreparedStatement.toString()</code> no longer throws for
a <code>bytea</code> value supplied as text via <code>PGobject</code>.
Hex-format values (<code>\x...</code>) are validated and rendered as a
<code>bytea</code> literal, and escape-format values are quoted and cast
like any other literal [Issue <a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/3757">#3757</a>](<a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/3757">pgjdbc/pgjdbc#3757</a>)
[PR <a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4201">#4201</a>](<a
href="https://redirect.github.com/pgjdbc/pgjdbc/pull/4201">pgjdbc/pgjdbc#4201</a>)</li>
<li>fix: the driver no longer nulls the <code>contextClassLoader</code>
of shared <code>ForkJoinPool.commonPool()</code> worker threads, which
previously left unrelated tasks on those threads running with a
<code>null</code> classloader [Issue <a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4155">#4155</a>](<a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4155">pgjdbc/pgjdbc#4155</a>)
[PR <a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4156">#4156</a>](<a
href="https://redirect.github.com/pgjdbc/pgjdbc/pull/4156">pgjdbc/pgjdbc#4156</a>)</li>
<li>fix: <code>PgResultSet#getCharacterStream</code> wraps
<code>String</code> in a <code>StringReader</code> [PR <a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4063">#4063</a>](<a
href="https://redirect.github.com/pgjdbc/pgjdbc/pull/4063">pgjdbc/pgjdbc#4063</a>)</li>
<li>fix: <code>PGXAConnection</code> no longer saves and restores the
underlying connection's JDBC <code>autoCommit</code> flag. All
XA-protocol SQL (<code>BEGIN</code>, <code>PREPARE TRANSACTION</code>,
<code>COMMIT</code>, <code>ROLLBACK</code>, <code>COMMIT
PREPARED</code>, <code>ROLLBACK PREPARED</code>, the
<code>recover()</code> SELECT) is sent through
<code>QUERY_SUPPRESS_BEGIN</code>, so the caller's
<code>autoCommit</code> value is invariant across every
<code>XAResource</code> call. Fixes the &quot;2nd phase commit must be
issued using an idle connection&quot; failure during recovery on managed
datasources that pool connections with <code>autoCommit=false</code>
(TomEE, WildFly, WebSphere Liberty) [PR <a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4114">#4114</a>](<a
href="https://redirect.github.com/pgjdbc/pgjdbc/pull/4114">pgjdbc/pgjdbc#4114</a>)</li>
<li>fix: <code>PGXAConnection.prepare()</code> now mutates XA state only
after <code>PREPARE TRANSACTION</code> succeeds. A failed
<code>PREPARE</code> previously left the driver thinking the branch was
already prepared, so the follow-up <code>rollback(xid)</code> tried
<code>ROLLBACK PREPARED</code> against a non-existent gid and returned
<code>XAER_RMERR</code>. Transaction managers (Narayana) escalated this
to <code>HeuristicMixedException</code>. With the fix,
<code>rollback(xid)</code> takes the active-branch path and issues a
plain <code>ROLLBACK</code>, which the server accepts cleanly. Fixes
[Issue <a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/3153">#3153</a>](<a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/3153">pgjdbc/pgjdbc#3153</a>),
[Issue <a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/3123">#3123</a>](<a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/3123">pgjdbc/pgjdbc#3123</a>).
[PR <a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4114">#4114</a>](<a
href="https://redirect.github.com/pgjdbc/pgjdbc/pull/4114">pgjdbc/pgjdbc#4114</a>)</li>
<li>fix: an updatable result set over an unqualified table name is now
classified using only the table visible through
<code>search_path</code>. When two schemas held a table with the same
name and the same primary or unique index name but a different set of
key columns, the driver took the union of both schemas' columns, so the
result set could be wrongly rejected as not updatable [PR <a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4214">#4214</a>](<a
href="https://redirect.github.com/pgjdbc/pgjdbc/pull/4214">pgjdbc/pgjdbc#4214</a>).
Supersedes [PR <a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/3400">#3400</a>](<a
href="https://redirect.github.com/pgjdbc/pgjdbc/pull/3400">pgjdbc/pgjdbc#3400</a>).</li>
<li>fix: <code>LargeObject.close()</code> now flushes a buffered output
stream before marking the object closed, so closing a large object
without an explicit <code>flush()</code> no longer drops buffered
writes. The flush runs while the object is still open (it calls back
into <code>LargeObject.write()</code>), and <code>lo_close</code> always
runs afterward; a failure from <code>lo_close</code> no longer masks an
earlier flush error, and the transaction is not committed when the flush
failed [Issue <a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4247">#4247</a>](<a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4247">pgjdbc/pgjdbc#4247</a>)
[PR <a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4248">#4248</a>](<a
href="https://redirect.github.com/pgjdbc/pgjdbc/pull/4248">pgjdbc/pgjdbc#4248</a>).</li>
<li>fix: reject empty <code>timestamp</code>, <code>timestamptz</code>,
and <code>date</code> text with a clear <code>SQLException</code>
(SQLState <code>22007</code>) instead of an
<code>ArrayIndexOutOfBoundsException</code> [PR <a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4278">#4278</a>](<a
href="https://redirect.github.com/pgjdbc/pgjdbc/pull/4278">pgjdbc/pgjdbc#4278</a>)</li>
<li>fix: return null <code>CHAR_OCTET_LENGTH</code> for non-character
columns [PR <a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4231">#4231</a>](<a
href="https://redirect.github.com/pgjdbc/pgjdbc/pull/4231">pgjdbc/pgjdbc#4231</a>)</li>
<li>fix: honor scale in <code>ResultSet.getBigDecimal(int, int)</code>
[PR <a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4211">#4211</a>](<a
href="https://redirect.github.com/pgjdbc/pgjdbc/pull/4211">pgjdbc/pgjdbc#4211</a>)</li>
<li>fix: support <code>java.time</code> values in an updatable
<code>ResultSet</code> <code>updateRow()</code> /
<code>insertRow()</code> [PR <a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/3848">#3848</a>](<a
href="https://redirect.github.com/pgjdbc/pgjdbc/pull/3848">pgjdbc/pgjdbc#3848</a>)</li>
<li>fix: improve batching when the <code>RETURNING</code> clause
contains <code>varchar</code> or <code>numeric</code> types [PR <a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4014">#4014</a>](<a
href="https://redirect.github.com/pgjdbc/pgjdbc/pull/4014">pgjdbc/pgjdbc#4014</a>)</li>
<li>fix: correct <code>estimatedReceiveBufferBytes</code> accounting
after a forced <code>Sync</code> [PR <a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4014">#4014</a>](<a
href="https://redirect.github.com/pgjdbc/pgjdbc/pull/4014">pgjdbc/pgjdbc#4014</a>)</li>
<li>fix: avoid creating a transient <code>ResultSet</code> for
describe-statement purposes, and restore the pre-describe path for
generated-key batches [PR <a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4014">#4014</a>](<a
href="https://redirect.github.com/pgjdbc/pgjdbc/pull/4014">pgjdbc/pgjdbc#4014</a>)</li>
<li>fix: add an explicit failure message when a multi-statement command
executes in a batch [PR <a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4014">#4014</a>](<a
href="https://redirect.github.com/pgjdbc/pgjdbc/pull/4014">pgjdbc/pgjdbc#4014</a>)</li>
<li>fix: detect <code>search_path</code> changes case-insensitively [PR
<a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4216">#4216</a>](<a
href="https://redirect.github.com/pgjdbc/pgjdbc/pull/4216">pgjdbc/pgjdbc#4216</a>)</li>
<li>fix: auto-detect the SSL key format instead of relying on the
<code>.key</code> extension [PR <a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/3946">#3946</a>](<a
href="https://redirect.github.com/pgjdbc/pgjdbc/pull/3946">pgjdbc/pgjdbc#3946</a>)</li>
<li>fix: build PKIX trust anchors without a <code>KeyStore</code> so
FIPS JVMs work [PR <a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4193">#4193</a>](<a
href="https://redirect.github.com/pgjdbc/pgjdbc/pull/4193">pgjdbc/pgjdbc#4193</a>)</li>
<li>fix: use <code>gssResponseTimeout</code> rather than
<code>sslResponseTimeout</code> for GSS connections [PR <a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4076">#4076</a>](<a
href="https://redirect.github.com/pgjdbc/pgjdbc/pull/4076">pgjdbc/pgjdbc#4076</a>)</li>
<li>fix: skip the autosave savepoint for <code>SET LOCAL</code> /
<code>SET SESSION TRANSACTION</code> [PR <a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4203">#4203</a>](<a
href="https://redirect.github.com/pgjdbc/pgjdbc/pull/4203">pgjdbc/pgjdbc#4203</a>)</li>
<li>fix: do not throw <code>AssertionError</code> from
<code>BatchResultHandler</code> on a closed connection [PR <a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4187">#4187</a>](<a
href="https://redirect.github.com/pgjdbc/pgjdbc/pull/4187">pgjdbc/pgjdbc#4187</a>)</li>
<li>fix: reject <code>SQL_TSI_FRAC_SECOND</code> with an explicit,
explained error [PR <a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4229">#4229</a>](<a
href="https://redirect.github.com/pgjdbc/pgjdbc/pull/4229">pgjdbc/pgjdbc#4229</a>)</li>
<li>fix: reject a null URL in <code>Driver.acceptsURL</code> with a
clear <code>NullPointerException</code> [PR <a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4205">#4205</a>](<a
href="https://redirect.github.com/pgjdbc/pgjdbc/pull/4205">pgjdbc/pgjdbc#4205</a>)</li>
<li>fix: reject overlong inputs in <code>NumberParser.getFastLong</code>
instead of silently wrapping [PR <a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4163">#4163</a>](<a
href="https://redirect.github.com/pgjdbc/pgjdbc/pull/4163">pgjdbc/pgjdbc#4163</a>)</li>
<li>fix: reject out-of-range and NaN values in
<code>PGInterval.setSeconds</code> [PR <a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4194">#4194</a>](<a
href="https://redirect.github.com/pgjdbc/pgjdbc/pull/4194">pgjdbc/pgjdbc#4194</a>)</li>
<li>fix: close the socket when <code>PgConnection</code> setup fails
after connect [PR <a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4161">#4161</a>](<a
href="https://redirect.github.com/pgjdbc/pgjdbc/pull/4161">pgjdbc/pgjdbc#4161</a>)</li>
<li>fix: keep the <code>LazyCleanerImpl</code> cleanup task alive across
a transient empty queue [PR <a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4038">#4038</a>](<a
href="https://redirect.github.com/pgjdbc/pgjdbc/pull/4038">pgjdbc/pgjdbc#4038</a>)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/pgjdbc/pgjdbc/commit/3297557c6a8059d0d6e3522c79f0bd9a6f82ee07"><code>3297557</code></a>
docs: add 42.7.13 release changelog (<a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4270">#4270</a>)</li>
<li><a
href="https://github.com/pgjdbc/pgjdbc/commit/d93d370984fbbccd099fc6466e4075ba46d8ec59"><code>d93d370</code></a>
style: apply Autostyle to docs/ and .github/</li>
<li><a
href="https://github.com/pgjdbc/pgjdbc/commit/2e05ff9e3ea9e8249f25dcb7017984285f1f76b1"><code>2e05ff9</code></a>
build: check docs/ and .github/ formatting with Autostyle</li>
<li><a
href="https://github.com/pgjdbc/pgjdbc/commit/b4a6087d2f07b578923a5272fa0155602ade9d40"><code>b4a6087</code></a>
Adjust EditorConfig für Makefiles</li>
<li><a
href="https://github.com/pgjdbc/pgjdbc/commit/725cebbb4e13be777483bd916b19dfcd428b5f26"><code>725cebb</code></a>
fix(jdbc): reject empty timestamp/timestamptz text with a clear
error</li>
<li><a
href="https://github.com/pgjdbc/pgjdbc/commit/23a1b0dac5a8fc633cc163e883eb21f0219ea521"><code>23a1b0d</code></a>
fix(scram): fail closed on channel-binding downgrade (no scram
bump)</li>
<li><a
href="https://github.com/pgjdbc/pgjdbc/commit/0b4077a529b2448cc55a6ca87b2be8667243c9ab"><code>0b4077a</code></a>
Bump pgjdbc version from 42.7.12 to 42.7.13 (<a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4269">#4269</a>)</li>
<li><a
href="https://github.com/pgjdbc/pgjdbc/commit/394800a38aebf54f9f293f6198e1fc5c8b19f10f"><code>394800a</code></a>
fix: flush LargeObject output stream before marking closed (<a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4248">#4248</a>)</li>
<li><a
href="https://github.com/pgjdbc/pgjdbc/commit/83780f130e0c83a77bb67350603f3cca8d4b9bb2"><code>83780f1</code></a>
Maintain consistency with the use of the word maintainer vs comitter (<a
href="https://redirect.github.com/pgjdbc/pgjdbc/issues/4234">#4234</a>)</li>
<li><a
href="https://github.com/pgjdbc/pgjdbc/commit/d42cad5cd12a13197e68aa53f09a7721336dce7a"><code>d42cad5</code></a>
fix(jdbc): classify updatable result set by search_path visibility</li>
<li>Additional commits viewable in <a
href="https://github.com/pgjdbc/pgjdbc/compare/REL42.7.11...REL42.7.13">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=org.postgresql:postgresql&package-manager=gradle&previous-version=42.7.11&new-version=42.7.13)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-03 12:25:01 +00:00
dependabot[bot] 98bdacc706 build(deps): bump org.apache.pdfbox:jbig2-imageio from 3.0.4 to 3.0.5 in /app/core (#7253)
Bumps org.apache.pdfbox:jbig2-imageio from 3.0.4 to 3.0.5.


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=org.apache.pdfbox:jbig2-imageio&package-manager=gradle&previous-version=3.0.4&new-version=3.0.5)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-03 09:40:57 +00:00
dependabot[bot]andAnthony Stirling e4c9b0410c build(deps): bump org.verapdf:validation-model from 1.28.2 to 1.30.2 in /app/core (#6836)
Bumps
[org.verapdf:validation-model](https://github.com/veraPDF/veraPDF-validation)
from 1.28.2 to 1.30.2.
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/veraPDF/veraPDF-validation/commit/94caa46c1a594512247fbd46c808edae39469542"><code>94caa46</code></a>
Improve security of the DocumentBuilder</li>
<li><a
href="https://github.com/veraPDF/veraPDF-validation/commit/ae243fc06775ef79495accd2165e5262e31dcf67"><code>ae243fc</code></a>
Fix typo</li>
<li><a
href="https://github.com/veraPDF/veraPDF-validation/commit/f89130de3b97f19ef4030e5fc84345c4463cd867"><code>f89130d</code></a>
Fix getAlt in GFPDAnnot</li>
<li><a
href="https://github.com/veraPDF/veraPDF-validation/commit/11ad0c577853a658d11370277196c12e34b5fd81"><code>11ad0c5</code></a>
Proposed SECURITY.md file for veraPDF projects</li>
<li><a
href="https://github.com/veraPDF/veraPDF-validation/commit/59f9cd4c97799dd87d738ce847c066530bcf2ed1"><code>59f9cd4</code></a>
Use non static isCircularMappingExist</li>
<li><a
href="https://github.com/veraPDF/veraPDF-validation/commit/7494455aa44fd322fa7bd900456a3cb8f0602f18"><code>7494455</code></a>
REL - v1.30</li>
<li><a
href="https://github.com/veraPDF/veraPDF-validation/commit/ecd191d3d796dfdfa13970a114920530627a25bb"><code>ecd191d</code></a>
Fix glyph name detection for symbolic TrueType font</li>
<li><a
href="https://github.com/veraPDF/veraPDF-validation/commit/32e21ce19a478a4e8e47c4a090674cbb2cb58026"><code>32e21ce</code></a>
Update fixRevProperty method (<a
href="https://redirect.github.com/veraPDF/veraPDF-validation/issues/726">#726</a>)</li>
<li><a
href="https://github.com/veraPDF/veraPDF-validation/commit/ddfee10116d45cc37750a988ff4385dad0c8510a"><code>ddfee10</code></a>
PDF/UA. Fix Table validation</li>
<li><a
href="https://github.com/veraPDF/veraPDF-validation/commit/22ea95799bb98b50a2063c542de50de4aeb4ce9e"><code>22ea957</code></a>
RC - v1.30</li>
<li>Additional commits viewable in <a
href="https://github.com/veraPDF/veraPDF-validation/compare/v1.28.2...v1.30.2">compare
view</a></li>
</ul>
</details>
<br />


> **Note**
> Automatic rebases have been disabled on this pull request as it has
been open for over 30 days.

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
2026-08-01 23:39:20 +01:00
dependabot[bot]andAnthony Stirling 18fb663b4d build(deps): bump bouncycastleVersion from 1.84 to 1.85 (#7132)
Bumps `bouncycastleVersion` from 1.84 to 1.85.
Updates `org.bouncycastle:bcprov-jdk18on` from 1.84 to 1.85
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/bcgit/bc-java/blob/main/docs/releasenotes.html">org.bouncycastle:bcprov-jdk18on's
changelog</a>.</em></p>
<blockquote>
<!-- raw HTML omitted -->
<!-- raw HTML omitted -->
<!-- raw HTML omitted -->
<!-- raw HTML omitted -->
<!-- raw HTML omitted -->
<p><!-- raw HTML omitted --><!-- raw HTML omitted -->2.2.1 Version<!--
raw HTML omitted --><!-- raw HTML omitted -->
Release: 1.85, 1.85.1<!-- raw HTML omitted -->
Date:      2026, July 12th</p>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/bcgit/bc-java/commits">compare view</a></li>
</ul>
</details>
<br />

Updates `org.bouncycastle:bcpkix-jdk18on` from 1.84 to 1.85
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/bcgit/bc-java/blob/main/docs/releasenotes.html">org.bouncycastle:bcpkix-jdk18on's
changelog</a>.</em></p>
<blockquote>
<!-- raw HTML omitted -->
<!-- raw HTML omitted -->
<!-- raw HTML omitted -->
<!-- raw HTML omitted -->
<!-- raw HTML omitted -->
<p><!-- raw HTML omitted --><!-- raw HTML omitted -->2.2.1 Version<!--
raw HTML omitted --><!-- raw HTML omitted -->
Release: 1.85, 1.85.1<!-- raw HTML omitted -->
Date:      2026, July 12th</p>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/bcgit/bc-java/commits">compare view</a></li>
</ul>
</details>
<br />

Updates `org.bouncycastle:bcutil-jdk18on` from 1.84 to 1.85
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/bcgit/bc-java/blob/main/docs/releasenotes.html">org.bouncycastle:bcutil-jdk18on's
changelog</a>.</em></p>
<blockquote>
<!-- raw HTML omitted -->
<!-- raw HTML omitted -->
<!-- raw HTML omitted -->
<!-- raw HTML omitted -->
<!-- raw HTML omitted -->
<p><!-- raw HTML omitted --><!-- raw HTML omitted -->2.2.1 Version<!--
raw HTML omitted --><!-- raw HTML omitted -->
Release: 1.85, 1.85.1<!-- raw HTML omitted -->
Date:      2026, July 12th</p>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/bcgit/bc-java/commits">compare view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
2026-08-01 22:41:26 +01:00
ConnorYoh 8990f55e50 feat(storage): encryption at rest for stored files (per-team envelope encryption) (#7155)
# Description of Changes

PR1 of the encrypt-at-rest initiative: user files stored by Stirling (My
Files, workflow files) are now AES-256 encrypted at rest across all
three storage backends, with keys that never leave the deployment.

**What was changed**

- New `EncryptingStorageProvider` decorator wraps whichever
`StorageProvider` backend is configured (local / database / S3). It
encrypts on `store` (Tink AES-256-GCM streaming AEAD, 1 MiB segments)
and transparently decrypts on `load`; legacy plaintext blobs are
detected by magic sniff and pass through untouched, so mixed state is
safe and no migration is required to enable.
- Envelope-encryption key hierarchy: each blob gets a random per-file
DEK, wrapped by a per-team KEK stored (master-key-wrapped) in a new
`file_encryption_keys` registry table; the master key resolves like the
existing credential key — `stirling.security.fileEncryptionKey`
property, `STIRLING_FILE_ENCRYPTION_KEY` env var, or an auto-generated
owner-only `file-encryption.key` in the config dir (cluster mode
requires an explicit shared key, fail-fast).
- Self-describing blob format (`SPDFEAR1` header) carrying the key id,
plaintext length, and the wrapped DEK; the header prefix is bound as GCM
associated data to both the DEK wrap and the payload, so headers cannot
be transplanted between blobs.
- Enabled via `storage.encryption.enabled=true`, gated on a
Pro/Enterprise licence — **write side only**: decryption activates
whenever key rows exist, so switching the flag off or a lapsed licence
can never make previously encrypted files unreadable.
- Key status lifecycle (`ACTIVE`/`RETIRED`/`DISABLED`): `DISABLED` is a
reversible per-team kill switch that fails closed on read; no API path
deletes key material. A revoked download surfaces as **403 Forbidden**
("access revoked"), not a 500, since it is a deliberate policy state
rather than a fault.
- Startup self-check: a master key that cannot unwrap existing key rows
refuses to boot rather than silently starting a second key hierarchy.
- S3 presigned download URLs are suppressed for decorated storage (they
would serve ciphertext); the controller already falls back to
app-streamed downloads.
- `StoredFile`/`StoredObject` gain a nullable `encryption_key_id`
(ddl-auto, no migration); persisted sizes remain plaintext sizes so
quotas and UI are unchanged.

**Why**

Enterprise security questionnaires (and HIPAA/GDPR/CMMC buyers) require
encryption at rest with documented key management; files were previously
plaintext in every backend. Design doc and vendor/standards research
(Purview, Box KeySafe, Google CSE, ISO 32000-2) informed the approach.

## Manually tested end-to-end

Beyond the automated suite, the full flow was exercised against a
running backend (local provider, `storage.encryption.enabled=true`,
login enabled) via the storage API:

1. **Startup** — master key auto-generated with the "back this up"
warning; logs `master key initialised (AES-256-GCM, fingerprint …)` and
`Storage encryption at rest active (writes encrypted)`.
2. **Encrypted at rest** — uploaded a PDF containing a known marker
string; the blob on disk (371 B vs 219 B plaintext) began with the
`SPDFEAR1` header + key id + ciphertext, contained **no `%PDF` signature
and no marker** — not openable as a PDF straight off disk.
3. **Transparent access** — downloading the file through the API
returned it **byte-identical** to the original, marker intact; stored
`sizeBytes` stayed the plaintext size.
4. **Kill switch + reversibility** — set the team key's status directly
in the DB and restarted:
- `DISABLED` → download **failed closed** (`403`, "access to this
content is revoked"), zero plaintext served.
- `ACTIVE` again → file **fully recovered, byte-identical**. Disabling
is a reversible switch on a preserved key row, not destruction.

(The 403 mapping in step 4 was added in this PR after the manual run
first surfaced it as a generic 500.)

## Coming in later PRs

- **PR2 — ops & lifecycle:** audit events for
encrypt/decrypt/key-lifecycle; admin endpoints for the kill switch
(disable/enable) and key status; a background "encrypt existing files"
migration job for turning the feature on over pre-existing plaintext;
master-key rotation (re-wrap KEK rows). Also plans a
key-backup/fingerprint verification command.
- **PR3 — admin UI:** settings section (status, per-team key list with
disable/enable), encrypted-file badge in My Files, i18n.
- **Later:** per-**source** encryption for the Processor pipeline (the
`SOURCE` key scope is already reserved in the schema); pluggable
external KMS / BYOK master-key backends (Vault, AWS/Azure/GCP KMS);
optional FIPS-validated crypto module build for CMMC; and encrypted
egress (PDF-native AES-256) for files leaving the platform.

**Reviewer notes**

- New dependency: `com.google.crypto.tink:tink:1.23.0` (Apache-2.0, pure
Java — bundled in the boot jar, no Docker changes). Pulls protobuf-java
4.33.6, which clears the Aikido-flagged CVE-2024-7254. `./gradlew
checkLicense --no-parallel` passes.
- The `file-encryption.key` file is generated in the config dir on first
use and must be backed up; losing it makes encrypted files unrecoverable
(loud log warning + fingerprint exposed for backup verification).
- Tests cover round-trips on re-openable and one-shot (S3-style)
backends, multi-segment files, legacy passthrough, decrypt-only mode,
disabled-key fail-closed (now asserting the 403 mapping), header/payload
tamper rejection, key-creation races, and presigned-URL suppression.

---

## Checklist

### General

- [x] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [x] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [x] I have performed a self-review of my own code
- [x] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [x] I have run `task check` to verify linters, typechecks, and tests
pass
- [x] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-07-31 14:57:19 +00:00
ConnorYoh 46865cd154 Add Auto Rotate tool that detects and fixes page orientation (#7152)
## What

New **Auto Rotate** tool: give it any PDF and it detects each page's
correct orientation and sets `/Rotate` so every page displays upright.
Lossless — only the page rotation metadata changes, content is never
re-rendered.

New endpoint: `POST /api/v1/misc/auto-rotate-pdf`, plus an editor tool
registered next to Rotate.

## How it works

Two-tier detection, per page, both expressed as an additive clockwise
`/Rotate` correction:

1. **Embedded-text fast path** (`AutoRotateDetection`): dominant glyph
direction via `PDFTextStripper`/`TextPosition.getDir()`. Trusted only
with >= 30 glyphs at >= 95% agreement. Near-instant for born-digital
PDFs and needs no external tools. Correction = `(glyphDir -
pageRotation) mod 360` — the sign conventions are pinned by
parameterized fixture tests covering all text-angle x `/Rotate`
combinations.
2. **Tesseract OSD fallback**: pages the text path can't decide are
rendered at 300 DPI grayscale and run through `tesseract --psm 0`.
Corrections apply only above a confidence threshold (default 14.0,
matching OCRmyPDF's `--rotate-pages-threshold`). Rendering honours the
existing `/Rotate`, so the verdict is always additive.

**Conservative by default**: blank pages, mixed-direction pages, and
low-confidence verdicts are skipped, never guessed — the failure mode to
avoid is making a correct page wrong.

### API surface

- `detectionMode`: `auto` (default) | `text` | `osd` — forcing one
method is useful for testing
- `confidenceThreshold`: minimum OSD confidence to apply a correction
- `dryRun=true`: returns a JSON per-page report instead of the PDF
- `pageRotations={"1":90,...}`: applies precomputed corrections without
detection

The frontend uses analyze-then-apply (dryRun, then pageRotations) so
detection runs exactly once per file, and the analysis report can be
shown in the UI.

### UI

The tool's results panel shows a **detection report** for
debugging/tuning: per page — method badge (Text / OCR / Skipped),
confidence score (glyph-dominance % for text, raw OSD score for OCR),
applied rotation, and a skip reason (too little text, mixed directions,
below threshold, OCR not installed...). Settings expose detection mode
and the OSD threshold.

### Dependency handling

Registered in `PageOps` only — deliberately **not** gated on the
`tesseract` group, because the text path works without Tesseract. The
controller checks `isGroupEnabled("tesseract")` at runtime; when it's
missing, scanned pages are skipped with a visible `tesseractUnavailable`
note instead of the whole tool disappearing.

## Testing

- 14 detection unit tests: all text-angle x `/Rotate` fixture
combinations (pins the direction conventions), dominance/glyph-count
guards, OSD output parsing
- 6 controller tests: dryRun report, correction application, explicit
pageRotations, tesseract-unavailable reporting, input validation
- Frontend: typecheck (core/desktop/proprietary), ESLint, Prettier, all
i18n audit tests
- **Live, text path**: fixture with pages at `/Rotate` 0/90/180/270 ->
all pages return upright; report UI verified in the browser
- **Live, OSD path**: image-only "scan" fixture (no text layer) with
pages upright/180/90 -> all detected by OSD at conf ~15-17 and
corrected; closed-loop re-analysis of the output reports 0 pages to
rotate with *higher* confidence than the input

Out of scope: skew correction (that's the OCR tool's `--deskew`); this
fixes 90-degree-multiple orientation only.
2026-07-31 11:27:12 +00:00
James Brunton 3bee6d212e Change pipelines to have 1 input and 1 output (#7121)
# Description of Changes

Change pipelines so that sources and triggers are grouped into a list of
inputs, so you can have a different trigger for each source in the list.
This is necessary because triggers are not universally supported by all
source types. If you wanted to have a pipeline pull from both a folder
and an S3 bucket, the current system allows you to choose "Folder Watch"
as the trigger, which will either do nothing or crash when it's paired
with the S3 bucket.

I've got reservations about actually allowing different triggers for
every source because it allows for user workflows that I don't believe
exist, like "I want this folder to be polled every minute and this other
one to be polled every hour, but they should run the same tools and
should output to the same place". Because of this (with agreement from
Connor, Anthony and Matt) I've changed this PR to artificially limit
pipelines to having 1 input & output at this stage. The backend is still
shaped to support multiple inputs & outputs so it should be trivial to
re-add support for them in the future if we decide we want to, but the
UI can be much simpler and easier to understand with just 1 input and
output.

<img width="1262" height="521" alt="image"
src="https://github.com/user-attachments/assets/809e6803-9f99-436d-9aeb-52dddf0906ff"
/>
2026-07-31 08:53:01 +00:00
ConnorYoh b4a264239c fix(saas): provision a new user and their personal team atomically (#7193)
New SaaS accounts were landing with `team_id = null`. That state is
unrecoverable: portal access derives from leading a team, and signup is
the only place one is assigned.

Five things had to be fixed, all on the signup path. Only the last is a
behaviour change you'd notice.

### 1. Shared-PK entity was routed to `merge()`
`SaasUserExtensions` pre-sets its `@MapsId` id in the constructor, so
Spring Data's id-nullness check treated a brand-new row as existing and
`save()` failed with `AssertionFailure: null identifier`. Now implements
`Persistable` and decides on the creation timestamp — the idiom already
used by `ProcessedFileEntity` and `SourceDocCountEntity`.

This was the blocker. It threw on every signup, and because the failure
was swallowed (see 3) every new account was stranded.

### 2. User and team were committed separately
`createUser()` is annotated `@Transactional` but is called as
`this.createUser(...)`, and self-invocation bypasses the proxy — so the
annotation did nothing. `saveUser()` and `ensurePersonalTeam()` each
committed in their own transaction, leaving a window where a **committed
user was visible with `team_id = null`**. Parallel requests entering
that window each provisioned a team, producing duplicates (observed:
teams 160/161 and 162/163 for one user).

Both writes now happen in one transaction via
`SaasTeamService.saveUserWithPersonalTeam()`. The window is gone, so
there is nothing left to race over.

### 3. A failed team create was swallowed
The old code logged at WARN and committed the user anyway. It now
propagates: the shared transaction rolls the user back, the request
401s, and a retry starts clean. Nothing half-built is committed.

This is the deliberate trade — a transient failure now surfaces instead
of silently producing an account that can never reach the portal.

### 4. Per-request healing removed
`recoverMissingTeam` (added in #7180) ran on **every authenticated
request** whose user had no team, with no mutual exclusion. Under a
burst of parallel requests it was itself a source of concurrent
provisioning. Provisioning belongs to signup alone.

### 5. Policy seeding could not run
`@TransactionalEventListener(AFTER_COMMIT)` leaves the *completed*
transaction bound to the thread, so `JpaPolicyStore.save`'s
`@Transactional` joined it instead of opening a live one — and its `FOR
UPDATE` lock threw `TransactionRequiredException`. Now seeded in
`BEFORE_COMMIT`: the lock has a live transaction, rollback safety is
unchanged (a rolled-back team still leaves no policy), and it stays on a
single pooled connection.

## Verified

`:saas:test` green, both spotless gates green, on top of current `main`.

Manually on a live signup: **one** team per user, and the
concurrent-signup race resolves correctly through the pre-existing
unique-constraint catch (`users_supabase_auth_id_key` violation →
refetch the winner).

12 filter tests needed updating. Two of them asserted behaviour this PR
deliberately removes (`personalTeamFailureSwallowed`,
`assignsTeamWhenMissing`), so they were rewritten to assert the new
contract rather than re-stubbed into passing.

## Not in scope

- **Existing stranded accounts** are not repaired — with the healer
gone, nothing fixes them on the request path. They need a one-off
backfill or deletion.
- **A DB-level invariant.** A partial unique index
(`UNIQUE(created_by_user_id) WHERE is_personal`) would make duplicate
personal teams impossible rather than merely unreachable. Wanted, but it
is a Supabase migration in the SaaS repo, so it is deliberately
separate.
- **Per-request auth cost.** The filter still does two remote-Postgres
round-trips per authenticated request; a frontend request storm makes
that expensive. Being handled separately.
2026-07-29 15:15:49 +00:00
James Brunton 999b5e5995 Add persistent outputs to Processor (#7071)
# Description of Changes
<img width="1270" height="487" alt="image"
src="https://github.com/user-attachments/assets/64894e2b-aab9-42ab-96c2-2c11ba427b52"
/>

Change policies to point towards a source for its output instead of a
dynamically defined output location for the pipeline. This allows for
easy reuse of outputs in different pipelines and makes it impossible to
break complex pipelines by accidentally updating the source but not the
output and vice versa. Also makes outputs a list to match the inputs, so
it's possible for a pipeline to output to multiple locations.

We should consider whether we want to continue calling these Sources
since they're now being used as both inputs and outputs, but that
decision is beyond the scope of this PR.

Also updates the existing S3 DB migration script and adds a new one to
migrate to the new schema. Neither of these scripts are possible with
SQL since it involves parsing and restructuring JSON. I've updated them
so that they only ever run once on startup and mark themselves as
completed.
2026-07-29 11:22:47 +00:00
ConnorYoh 66b80a80c0 fix(saas): personal teams must be allowed to share a name (new signups get no team) (#7180)
## The bug

Every SaaS signup after the very first one is created with `team_id =
NULL`, no team membership and no `home_team_id`. A brand-new account:

```
user_id | username               | team_id | authenticationtype | home_team_id | memberships
    952 | hedewot627@candaba.com | null    | web                | null         | null
```

Since #7070 derives Processor access from leading a team, these accounts
are silently redirected out of the Processor and back to the editor.

## Cause

`SaasTeamService.createPersonalTeam` names every personal team the
literal `"My Team"`, and `stirling_pdf.teams.name` was unique — so the
insert throws a duplicate-key error for the second account onwards. Team
creation is best-effort (caught, logged at WARN), so the account is
created anyway, permanently team-less.

Migration `20251211000000` had already dropped that constraint for
exactly this reason, but it dropped it **by name** while the entity
still declared `@Column(unique = true)`. With Flyway retired for `:saas`
(#7100), `ddl-auto=update` reconciles the schema — so Hibernate
re-created the constraint on the next boot under a generated name the
old `DROP` could never match.

The data bug predates #7070; that PR only made it visible.

## Changes

- **`Team.name` no longer unique.** `TeamController` already enforces
uniqueness for admin-created teams (`existsByNameIgnoreCase` on create
and rename, 409), so nothing user-facing changes. `findByName` is only
used for the `Default`/`Internal` system teams.
- **Existing team-less accounts recover on authentication.** Signup is
the only other place a team is assigned and nothing back-fills
`team_id`, so without this they stay locked out. Guests excluded by
design; healthy accounts short-circuit on a null check (`team` is
`EAGER`).
- **Tests:** team recovered, existing team untouched, guest stays
team-less.

## Deploy order

Needs `20260806000000_teams_name_drop_unique` (SaaS repo, `v3`) to drop
the constraint from the live schema — **deployed after this**, or
Hibernate re-adds it on the next boot.

## Verification

`:saas:compileJava`, `:proprietary:compileJava`, spotless on both, and
the `:saas` team/auth-filter tests (`TeamRecovery`: 3 tests, 0
failures).
2026-07-29 09:31:47 +00:00
ConnorYohandReece Browne ba404d3f90 PAYG bundle: server-authoritative price (inline amount_off coupon) + #7032 review nits (#7156)
## What

Follow-up to #7032. Makes the prepaid-bundle price
**server-authoritative** and removes the percent-coupon rounding drift,
by switching the 12-for-10 discount from a pre-made `percent_off` Stripe
coupon to an **edge-function-computed inline `amount_off` coupon**. Also
folds in Ethan's #7032 review nits.

This is a money-mechanism change, so it was verified against the Deno
tests and is ready for a V2-preview check before rollout.

## SaaS side — already on `v3` (purely additive)

The edge fn + migration were pushed **directly to `v3`** (commit
`4534ff1c1`), since the DB change is purely additive (a
backward-compatible function replacement — no table/column/data
changes):

- `create-payg-bundle-quote`: retrieves the Stripe Price for the bundle,
computes `subtotal = unit_amount x pool_credits` (falls back to
`round(unit_amount_decimal x pool_credits)`), `discount = round(subtotal
x 2 / 12)`, `total = subtotal - discount`; mints a single-use
fixed-amount coupon (`amount_off`, `duration: once`, `max_redemptions:
1`, `redeem_by = valid_until`) and applies it instead of the stored
percent coupon; persists `total` via `p_price_minor`.
- Migration `20260803000000_payg_bundle_quote_stripe_price_minor.sql`:
`payg_set_bundle_quote_stripe` gains `p_price_minor BIGINT DEFAULT NULL`
→ `price_minor = COALESCE(p_price_minor, price_minor)`.

**Deploy choreography (important):** the migration must apply **before**
the edge fn is deployed — the fn now calls the 4-arg
`payg_set_bundle_quote_stripe`. #7032's own Supabase migration is
already on `main`/`v3`.

## This PR (FE)

- **Server-authoritative price:** `bundlePriceMinor` now computes
`subtotal - round(subtotal x (granted-paid)/granted)` (round the
discount, then subtract) — identical to the edge fn — so the pre-mint
estimate matches the `amount_off` charged, and the persisted/frozen
total, to the penny (they previously diverged by a minor unit on
exact-half ties). Tie-case test added.

### Ethan's #7032 review nits

- **1** — comments in `ActivationChoiceModal` / `FreePlanView` no longer
assert the metered subscription is auto-provisioned off the saved card;
they describe it as a known, not-yet-wired follow-up.
- **2** — corrected the price-authority narrative (`stripe.ts`,
`BundleCheckoutModal`): the client-sent `p_price_minor` is a pre-mint
**display estimate only**; the edge fn overwrites `price_minor` with the
server total once the quote is minted. **Verified** the edge fn builds
the Stripe line from `bundle_price_id x pool_credits` with `amount_off`
from the retrieved Price — it never uses the client price.
- **4** — `ensureStripeQuote`'s reuse key now includes the
posture/size/pipeline ids (`buildStripeQuoteSig`), not just pool+PO, so
a same-pool sizing edit re-mints and re-persists instead of leaving
stale sizing on the row.
- **5** — `SpendLimitPicker`: a cleared field (maps to `0`) can no
longer proceed as a `$0` cap — the cap-step Continue is disabled and
`handleContinue` guards on it (empty = incomplete, distinct from the
explicit `null` "No limit").
- **6** — `"prepaid PDFs"` code fallbacks aligned to the `"prepaid
credits"` TOML (`usageMeters`, `PrepaidCapacityCard`).

## Testing

- SaaS Deno: **25/25** (coupon `amount_off == round(subtotal*2/12)`,
`p_price_minor == total` persisted, `unit_amount_decimal` fallback,
exact-half tie, zero-discount path, price/coupon failure paths).
- FE vitest: **50** billing/format tests pass; prettier + eslint clean;
tsc clean for all changed files.
- Pending: manual V2-preview check that the invoice shows a concrete
`-$X.00` discount line (labelled "12 months for the price of 10") equal
to the in-app total.

Closes the residual half of #7032 review finding #2 — once
merged/deployed, the in-app total, the persisted value, and the Stripe
invoice all agree.

---------

Co-authored-by: Reece Browne <74901996+reecebrowne@users.noreply.github.com>
2026-07-28 13:05:20 +00:00
ConnorYoh 3813ca360e PAYG prepaid usage bundles (#7032)
# Prepaid usage bundles

Teams on pay‑as‑you‑go can **buy a year of PDF processing up front, at a
discount** — *"12 months for the price of 10."* You pre‑buy a pool of
credits; they're spent **before** any metered billing and sit
**outside** the monthly spend limit; unused capacity expires after 12
months.

---

## What this PR delivers

**Buy → quote → invoice → pay (Stripe‑Quotes‑native).**
- A team lead sizes the pool in the calculator (persisted as a quote
row), which doubles as the quote page with a **"Download quote (PDF)"**
— the PDF is **Stripe's own rendered quote** (same mechanism procurement
uses), not an app‑generated document.
- **Finalise** turns the accepted quote into an invoice; the lead can
**download the invoice** or **pay online** (Stripe hosted invoice).
**Card and bank‑transfer / PO** are both supported (payment‑method
fork), on net terms.
- The billing page loads the in‑flight quote/invoice on open, so the CTA
resumes the right step (**View quote** / **Pay invoice to complete**)
and offers **Cancel purchase** (voids the invoice + quote and restarts).

**Prepaid is usable on its own — no subscription required.** The
entitlement gate honours a live prepaid pool in both cases:
- *Unsubscribed*: once the one‑time free grant is spent, a live pool
keeps the team **fully entitled** (all feature gates) rather than
degraded.
- *Subscribed*: a team **at/over its metered cap** but holding a live
pool stays fully entitled — prepaid draws are netted out of metered
spend, so the pool genuinely sits outside the cap.

Only when the free grant **and** the prepaid pool are both empty do
billable categories stop.

**Coordinated SaaS change (ships with this — `Stirling-PDF-SaaS` `v3`
branch):** the `invoice.paid` webhook credits the pool idempotently
(keyed on the invoice id) and settles the quote. Metered‑subscription
provisioning is best‑effort and **classified** — a permanent Stripe 4xx
(the single‑use hosted‑invoice card can't be attached) is a claimed
no‑op (HTTP 200, no retry) so Stripe doesn't redeliver forever; only
transient errors (5xx / connection / rate‑limit) retry. A failed credit
now retries rather than silently dropping a paid bundle.

### Flow

1. Lead sizes the pool and agrees to the terms → the browser sends team
+ capacity + consent, **never a price**.
2. A leader‑gated server function looks up the price and creates a
Stripe **quote** (line quantity = capacity).
3. Lead **finalises** → the quote becomes a Stripe **invoice**; download
it or pay online (card or bank transfer).
4. On `invoice.paid`, the webhook **credits the prepaid pool**
(idempotent) and settles the quote.
5. Usage then draws **free grant → prepaid pool → meter**; the pool is
usable with no subscription.

<img width="1280" height="920" alt="01-activation-fork"
src="https://github.com/user-attachments/assets/e8981dc7-809d-4fc5-bfde-71e619096b7b"
/>
<img width="1280" height="920" alt="02-calculator"
src="https://github.com/user-attachments/assets/81f6b886-1797-4c54-ba18-97d126be78e2"
/>
<img width="1120" height="600" alt="03-free-plan"
src="https://github.com/user-attachments/assets/c4057fde-d965-47dd-93e8-f2f0f612aa73"
/>
<img width="1105" height="1285" alt="04-subscribed-prepaid"
src="https://github.com/user-attachments/assets/9d1d0ef1-29d0-4bd4-9c17-ac5f68f88ca8"
/>


---

## In a follow‑up (not this PR)

1. **Authoritative price via an inline fixed‑amount coupon** *(in
progress in a separate PR).* Replace the percentage 12‑for‑10 coupon
with an edge‑function‑computed **`amount_off`** coupon: the invoice
shows a concrete "−$X.00" discount line, the total is deterministic (no
percentage‑rounding drift), and the persisted price becomes
**server‑authoritative**. Money‑mechanism change — needs validation
against the Stripe test env, so it warrants its own testable PR.
2. **Metered auto‑resume when the pool empties.** Save the paying card
at invoice time (`setup_future_usage`) for card payers → real
`charge_automatically`; a cardless `send_invoice` subscription for
bank‑transfer / PO. This makes the "processing continues at the metered
rate" promise true for everyone.
3. **Provisioning idempotency hardening** (SaaS repo). Idempotency key
on subscription creation + a conditional link RPC, so a webhook
redelivery or link‑RPC failure can't create duplicate or orphaned
subscriptions.
4. **Repo‑wide "credits" copy** across *all* of usage & billing (this PR
only makes its own additions consistent).

---

## Known edges (current state)

- **Cardless teams degrade when the pool empties.** An unsubscribed
bundle team that runs the pool dry hits DEGRADED (metered paused), not
automatic metered continuation — because no metered subscription gets
provisioned off a hosted‑invoice card. The consent copy states
processing "continues at the metered rate"; that promise is
intentionally **ahead of the mechanism** (follow‑up 2), and the 12‑month
term is the runway to deliver it. The prepaid capacity itself stays
fully usable in the meantime.
- **In‑app total vs charge can differ by ≤1¢** until follow‑up 1 lands.
The **shared approval document (the Stripe quote PDF) and the actual
invoice are already Stripe‑authoritative**; the persisted price shown
in‑app is still a front‑end estimate (percentage‑coupon rounding), so it
can differ from Stripe by a rounding cent. Resume‑time drift is fixed
(frozen to persisted); exact‑to‑the‑penny parity arrives with the
authoritative‑price follow‑up.
- **Provisioning idempotency is latent, not live.** The
duplicate/orphan‑subscription window only becomes reachable once
card‑linking (follow‑up 2) makes provisioning actually run; hardening is
tracked as follow‑up 3.
- **One job can overshoot the spend cap via a near‑empty pool.** A
subscribed team that has hit its metered cap but still holds a
*nearly‑exhausted* pool is let through (the pool overrides the cap
gate); if a job needs more than the pool has left, the pool drains to
zero and the **remainder meters**, so that single job's remainder can
bill just past the "never past your spend limit" ceiling. Bounded to one
job's overshoot and only at the pool's tail; the alternative — blocking
the job — would strand paid‑for capacity, so this is a deliberate trade.

---

## Testing

- **Java** — `EntitlementServiceTest` (18) incl.
unsubscribed‑live‑pool‑stays‑FULL,
subscribed‑over‑cap‑with‑pool‑stays‑FULL, and lazy‑read guards.
- **Frontend** — `useBundleFlowState` + `Usage` render tests; portal &
SaaS `tsc`; i18n audit; `lint:colors`; toml‑sort; prettier.
- **SaaS webhook** (`v3`) — Deno tests for terminal‑vs‑transient
provisioning classification (rate‑limit treated as retryable),
credit‑error‑retries, and an end‑to‑end no‑storm assertion on the
unusable‑card path.

*Preview:* the checkout runs in a Supabase function in
`Stirling-PDF-SaaS` (`v3`); a live V2 preview is linked in the
auto‑deploy comment below. Screenshots to be refreshed — the checkout
modal changed since the originals.
2026-07-24 12:53:16 +00:00
James Brunton 29002d0b82 Improve UX around source folders in Processor (#7101)
# Description of Changes
Adds implicitly defined folders to the list of locations that folder
sources can look in, including the legacy watchedFolder folders, and the
server storage location (if enabled). Also adds a settings UI for
defining the list of allowed folders instead of having to manually edit
`settings.yml` (please excuse the styling, that's the standard styling
of the Processor, hoping it gets fixed by one of the styling PRs).

<img width="888" height="786" alt="image"
src="https://github.com/user-attachments/assets/cf6d0705-adcf-463c-8e80-6901a652068b"
/>

<img width="1103" height="713" alt="image"
src="https://github.com/user-attachments/assets/b7244592-249a-4149-994f-3a2b750f25f9"
/>
2026-07-23 11:19:24 +00:00
Anthony Stirling 1e2895a79f Add external-API integrations plus pipeline steps (#7098)
New Generic API mode with examples and integrations setup around it 

- Adds an integration operations catalogue so external-API connections
(e.g. Microsoft Purview) can be used as policy pipeline steps
- New generic external-API step calls a configured connection during a
policy run, with a verdict gate to pass/fail documents on the response
- Purview sensitivity-labelling step applies labels to processed
documents, gated behind the Purview connection being configured (WIP to
be changed later)

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-07-23 11:18:39 +00:00
Anthony Stirling 8de94ff152 Ai customization settings (#7069)
# Description of Changes
AI settings customisation in settings menu, as part of this also tested
and fixed ollama and other 3rd party AI integrations

- Adds an admin AI settings UI for customizing AI behaviour, including
per-provider model and API-key configuration
- Backend pushes AI config changes to the Python engine at runtime via a
config-push bridge, so changes apply without a restart
- Config-push is gated off in SaaS; engine now drains background tasks
on shutdown instead of cancelling them
---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-07-23 08:29:06 +00:00
Anthony Stirling b3875d3149 Add heuristic classification (#7050)
# Description of Changes

- Adds a non-AI heuristic classification engine that classifies
documents client-side in the browser when AI is disabled
- Classification is billed as a policy run via a fast, non-blocking
meter endpoint; a default Classification policy is seeded per team
- Enables the policy engine by default

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-07-23 08:28:26 +00:00
Anthony StirlingandEthanHealy01 357eb77f94 Portal: multiple named personal API keys with per-key usage tracking (#6961)
# Description of Changes

Multiple named **personal** API keys per user, replacing the single
opaque per-user key.

- Create (name + one-time secret), list, and revoke named keys from the
portal Infrastructure → API Keys tab. Works self-hosted and SaaS
(`X-API-KEY`).
- Per-key usage stats (today / trailing 30 days / lifetime);
API-processed documents are attributed to the specific key in the
processor's Documents feed.
- The legacy single per-user key keeps working and is lazily represented
as a named key. Rotating it revokes its migrated shadow row so the old
secret stops authenticating.
- Per-user (not per-key) rate limiting plus a per-user active-key cap,
so minting keys can't multiply the daily quota. Name-length cap;
race-safe migration and usage recording.

Keys are strictly personal: one owner, full access, no sharing.
Team-shared / scoped keys and per-key access levels were intentionally
left out of this PR to keep it small and easy to review; they can follow
as a separate, focused change.

> Note: the screenshots from the original revision showed an earlier
team-scoped design and need refreshing.

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [x] I have performed a self-review of my own code
- [x] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [x] I have run `task check` to verify linters, typechecks, and tests
pass
- [x] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.

---------

Co-authored-by: EthanHealy01 <80844253+EthanHealy01@users.noreply.github.com>
2026-07-23 07:52:57 +00:00
Anthony Stirling 3bf0019d7c Webhook policy source (#7051)
# Description of Changes
Create custom webhooks as a source, allows file pushes toa custom made
endpoint with custom auth ID

- Adds webhook as a policy source: external systems push documents to a
receiver endpoint, which stages the files locally and triggers the
policy run
- Requests are authenticated with HMAC signatures; receiver hardened
with bounded body reads and server-minted IDs
- Uses the same team-scoped IntegrationConfig connection model as the S3
source, with matching portal UI (source type, icon, wizard)
- Includes a policies-gated Cucumber feature covering the receiver
end-to-end

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-07-22 12:33:53 +00:00
dependabot[bot]andLudy b5b9cc443f build(deps): bump com.github.junrar:junrar from 7.5.10 to 7.6.0 in /app/common (#7090)
Bumps [com.github.junrar:junrar](https://github.com/junrar/junrar) from
7.5.10 to 7.6.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/junrar/junrar/releases">com.github.junrar:junrar's
releases</a>.</em></p>
<blockquote>
<h2>Release v7.6.0</h2>
<h2>Changelog</h2>
<h2>🚀 Features</h2>
<ul>
<li>support random access for files in solid RAR4 archives (<a
href="https://github.com/junrar/junrar/commits/e0874d2">e0874d2</a>)</li>
</ul>
<h2>🏎 Perf</h2>
<ul>
<li>replace RarCRC.checkCrc with java.util.zip.CRC32 (<a
href="https://github.com/junrar/junrar/commits/5270d23">5270d23</a>)</li>
</ul>
<h2>🛠  Build</h2>
<p><strong>deps</strong></p>
<ul>
<li>bump gradle-wrapper to 9.5.1 (<a
href="https://github.com/junrar/junrar/commits/cb4b7fd">cb4b7fd</a>)</li>
<li>bump com.fasterxml.jackson.core:jackson-databind (<a
href="https://github.com/junrar/junrar/commits/0bb56b3">0bb56b3</a>)</li>
<li>bump com.fasterxml.jackson.datatype:jackson-datatype-jsr310 (<a
href="https://github.com/junrar/junrar/commits/ca621b2">ca621b2</a>)</li>
<li>bump org.jreleaser from 1.23.0 to 1.24.0 (<a
href="https://github.com/junrar/junrar/commits/90f0548">90f0548</a>)</li>
<li>bump commons-io:commons-io from 2.21.0 to 2.22.0 (<a
href="https://github.com/junrar/junrar/commits/83a5d08">83a5d08</a>)</li>
<li>bump com.github.ben-manes.versions from 0.53.0 to 0.54.0 (<a
href="https://github.com/junrar/junrar/commits/d5abcdb">d5abcdb</a>)</li>
</ul>
<p><strong>unscoped</strong></p>
<ul>
<li>replace deprecated action (<a
href="https://github.com/junrar/junrar/commits/338efcb">338efcb</a>)</li>
</ul>
<h2>Contributors</h2>
<p>We'd like to thank the following people for their contributions:
Gauthier, Gauthier Roebroeck, Robin Schimpf</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/junrar/junrar/blob/master/CHANGELOG.md">com.github.junrar:junrar's
changelog</a>.</em></p>
<blockquote>
<h1><a
href="https://github.com/junrar/junrar/compare/v7.5.10...v7.6.0">7.6.0</a>
(2026-05-13)</h1>
<h2>🚀 Features</h2>
<ul>
<li>support random access for files in solid RAR4 archives (<a
href="https://github.com/junrar/junrar/commits/e0874d2">e0874d2</a>)</li>
</ul>
<h2>🏎 Perf</h2>
<ul>
<li>replace RarCRC.checkCrc with java.util.zip.CRC32 (<a
href="https://github.com/junrar/junrar/commits/5270d23">5270d23</a>)</li>
</ul>
<h2>🛠  Build</h2>
<p><strong>deps</strong></p>
<ul>
<li>bump gradle-wrapper to 9.5.1 (<a
href="https://github.com/junrar/junrar/commits/cb4b7fd">cb4b7fd</a>)</li>
<li>bump com.fasterxml.jackson.core:jackson-databind (<a
href="https://github.com/junrar/junrar/commits/0bb56b3">0bb56b3</a>)</li>
<li>bump com.fasterxml.jackson.datatype:jackson-datatype-jsr310 (<a
href="https://github.com/junrar/junrar/commits/ca621b2">ca621b2</a>)</li>
<li>bump org.jreleaser from 1.23.0 to 1.24.0 (<a
href="https://github.com/junrar/junrar/commits/90f0548">90f0548</a>)</li>
<li>bump commons-io:commons-io from 2.21.0 to 2.22.0 (<a
href="https://github.com/junrar/junrar/commits/83a5d08">83a5d08</a>)</li>
<li>bump com.github.ben-manes.versions from 0.53.0 to 0.54.0 (<a
href="https://github.com/junrar/junrar/commits/d5abcdb">d5abcdb</a>)</li>
</ul>
<p><strong>unscoped</strong></p>
<ul>
<li>replace deprecated action (<a
href="https://github.com/junrar/junrar/commits/338efcb">338efcb</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/junrar/junrar/commit/cb4b7fdb84269e95741fa22120e08ceb15a06152"><code>cb4b7fd</code></a>
build(deps): bump gradle-wrapper to 9.5.1</li>
<li><a
href="https://github.com/junrar/junrar/commit/0bb56b3df4e3b6d14f73de92746a2eb47dd2d7a3"><code>0bb56b3</code></a>
build(deps): bump com.fasterxml.jackson.core:jackson-databind</li>
<li><a
href="https://github.com/junrar/junrar/commit/ca621b22421f9e846df23112b32113230610c8d9"><code>ca621b2</code></a>
build(deps): bump
com.fasterxml.jackson.datatype:jackson-datatype-jsr310</li>
<li><a
href="https://github.com/junrar/junrar/commit/e0874d213832bbeaa8eb265c79479b01b5fa7392"><code>e0874d2</code></a>
feat: support random access for files in solid RAR4 archives</li>
<li><a
href="https://github.com/junrar/junrar/commit/90f0548c728b0bf2e94f6e26f9e6396d9d0a7262"><code>90f0548</code></a>
build(deps): bump org.jreleaser from 1.23.0 to 1.24.0</li>
<li><a
href="https://github.com/junrar/junrar/commit/83a5d085dc84991226229fef1515cc902d129241"><code>83a5d08</code></a>
build(deps): bump commons-io:commons-io from 2.21.0 to 2.22.0</li>
<li><a
href="https://github.com/junrar/junrar/commit/338efcb5472be051989fbafd7179d561c71722eb"><code>338efcb</code></a>
ci: replace deprecated action</li>
<li><a
href="https://github.com/junrar/junrar/commit/5270d235ade54d96dfc9958ab06f495fbbd169e7"><code>5270d23</code></a>
perf: replace RarCRC.checkCrc with java.util.zip.CRC32</li>
<li><a
href="https://github.com/junrar/junrar/commit/d5abcdb9af988ddfa76e98d80787e15525488332"><code>d5abcdb</code></a>
build(deps): bump com.github.ben-manes.versions from 0.53.0 to
0.54.0</li>
<li><a
href="https://github.com/junrar/junrar/commit/edadb28896962fdcf754029bafd0994aaff24530"><code>edadb28</code></a>
chore(release): 7.5.10 [skip ci]</li>
<li>See full diff in <a
href="https://github.com/junrar/junrar/compare/v7.5.10...v7.6.0">compare
view</a></li>
</ul>
</details>
<br />

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Ludy <Ludy87@users.noreply.github.com>
2026-07-22 07:50:24 +00:00
brios e24a30828b refactor(api): replace deprecated APIs with their modern equivalents (#6434)
# Description of Changes


This PR resolves deprecation warnings and addresses compiler errors
resulting from the transition to Spring Security 7.x., as well Jackson 3
and general Java.

* Replaced all usages of `asText()`/`isTextual()` with
`asString()`/`isString()` in JSON parsing logic across
`FormPayloadParser.java`, `ApiEndpoint.java`, and
`KeygenLicenseVerifier.java` to ensure consistent and type-safe string
* Updated `CustomSaml2AuthenticatedPrincipal` to implement
`Saml2ResponseAssertionAccessor`, added a `responseValue` field, and
provided additional getter methods and type-safe attribute accessors.
* Switched from constructing `URL` objects directly from strings to
using `URI.create(...).toURL()` in `UIDataTessdataController.java` for
improved URL safety and parsing.

<!--
Please provide a summary of the changes, including:

- What was changed
- Why the change was made
- Any challenges encountered

Closes #(issue_number)
-->

---

## Checklist

### General

- [x] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [x] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [x] I have performed a self-review of my own code
- [x] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [x] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-07-21 18:49:59 +00:00
briosandAnthony Stirling e2ea720fc8 refactor(api): replace regex literals with compiled patterns for improved performance and readability (#6511)
# Description of Changes


This pull request refactors several utility classes and controllers to
replace inline regular expression usage with precompiled `Pattern`
constants. This change improves performance, consistency, and
maintainability by ensuring that regex patterns are compiled only once
and reused throughout the codebase. Additionally, it enhances code
clarity and security in filename and SQL content sanitization.

<!--
Please provide a summary of the changes, including:

- What was changed
- Why the change was made
- Any challenges encountered

Closes #(issue_number)
-->

---

## Checklist

### General

- [X] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [X] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [X] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [X] I have performed a self-review of my own code
- [X] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [X] I have run `task check` to verify linters, typechecks, and tests
pass
- [X] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.

---------

Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
2026-07-21 17:41:44 +00:00
Anthony Stirling 621731bda1 Validate RFC 3161 document timestamps and expose timestamping (#7095)
# Description of Changes

Fixes timestamp issue and adds timestamp to the signing/security policiy
---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-07-21 17:37:54 +00:00
James Brunton 5e3e89ccb2 Fix existing teams logic (#7070)
# Description of Changes
Paired with https://github.com/Stirling-Tools/Stirling-PDF-SaaS/pull/320

Fixes the following bugs we found when testing the SaaS release:
- Existing users couldn't join teams - this was because they were the
last leader of their team, so it'd be left orphaned). Users now have a
'home team', which can have no members if they join another team, but
they can then go back to it later.
- Existing leaders didn't have unlimited seats - `saas_teams_extensions`
had no row for them, so the app fell back to `max_seats=1`. The
migration script fixes it.
- Members without Processor access could still access the Processor - It
was just checking "Are you the leader of **any** team", instead of the
user's active team.
2026-07-21 13:57:45 +00:00
ConnorYohandJames Brunton f60a75c253 chore(saas): remove unused Flyway migration system (#7100)
## What this PR does

Removes the **Flyway** migration system from the SaaS build:

- drops `flyway-core` + `flyway-database-postgresql` from
`app/saas/build.gradle`
- deletes all `Vxx__*.sql` files under
`app/saas/src/main/resources/db/migration/`
- removes the `spring.flyway.*` config from
`application-saas.properties`
- clears the now-inert `SPRING_FLYWAY_ENABLED` override and stale Flyway
comments in `testing/compose/docker-compose-saas.yml`

`ddl-auto` is **left on `update`** (unchanged) — that's a separate
decision (see "Not in this PR").

## Why — Flyway never actually ran anywhere

From a full schema-management review
(`notes/FLYWAY_MIGRATION_REVIEW.md`), verified against the live
databases:

- **Prod & dev (v3):** no `flyway_schema_history` table exists in any
schema → Flyway has never executed. Schema is authored by the Supabase
migrations in `Stirling-PDF-SaaS` and applied by that repo's **GitHub
integration** (merge to `main` → prod).
- **Tests:** the saas module has zero `@SpringBootTest`; the only
real-DB integration tests (in `proprietary`) use `ddl-auto=create-drop`
and don't have Flyway on the classpath.
- **The mock-DB harness** (`testing/compose/docker-compose-saas.yml`,
PAYG cucumber) *explicitly disabled* Flyway, because the `Vxx`
migrations can't run against a clean Postgres — they assume Supabase has
already provisioned `users`/`teams` (`V2` ALTERs `users`, `V5`
references `teams`).

So Flyway was dead weight, and its 4 duplicate versions
(`V25/V26/V30/V31`) were a latent trap: re-enabling it would crash boot
on the collision. Everything it contained (schema + seeds like the
default pricing policy) is already mirrored by the Supabase migrations,
and by `saas-seed.sql` for the cucumber stack.

## ⚠️ Required follow-up (item #1) — capture the Flyway-only tables into
Supabase migrations

**This is documentation of the next step, not done in this PR.**

Seven tables were defined in Flyway with **no matching Supabase
migration**. They exist in prod today only because `ddl-auto=update`
created them from their entities. Before `ddl-auto` is ever tightened to
`validate` (see #3 below), and so any fresh Supabase branch is complete,
they must be added as Supabase migrations in
`Stirling-PDF-SaaS/supabase/migrations/`.

**Capture (CREATE) — 6 live tables** (definitions are visible in the
deleted files in this PR's diff):

| Table | Source (deleted here) | Backing entity |
|---|---|---|
| `resource_grants` | `V25__resource_grants.sql` | `ResourceGrant` |
| `integration_configs` | `V26__integration_configs.sql` |
`IntegrationConfig` |
| `policy_sources` | `V22__policy_engine_tables.sql` | `SourceEntity` |
| `policy_source_doc_counts` | `V23__policy_source_doc_counts.sql` |
`SourceDocCountEntity` |
| `policy_source_doc_totals` | `V23__policy_source_doc_counts.sql` |
`SourceDocTotalEntity` |
| `saas_user_extensions` | `V9__saas_user_team_extensions.sql` |
`SaasUserExtensions` |

Write them as `CREATE TABLE IF NOT EXISTS stirling_pdf.<name> (...)`
(idempotent — no-op against the existing prod/v3 tables). Preserve
column types/defaults/constraints from the deleted `Vxx` files.

**Drop (do NOT recreate) — 1 orphaned table:**

- `classification_labels` — created by `V30` and dropped by `V39` within
Flyway; its `ClassificationLabel` is now a plain `record`, not a JPA
entity. It lingers in prod only because Flyway's `V39` drop never ran.
The follow-up should emit a `DROP TABLE IF EXISTS
stirling_pdf.classification_labels` (mirroring `V39`'s intent), after
confirming nothing reads it.

## Not in this PR (deliberately)

- **#3 — flip saas `ddl-auto` `update` → `validate`.** Held pending team
confirmation; it has boot-risk and should be gated by a CI
"validate-boot against a fresh Supabase branch" first. Self-hosted stays
on `update` regardless.
- **#4 — `billing_subscriptions` split-brain** (prod `public` has 23,164
rows, `stirling_pdf` has 0, Java reads the empty one). Tracked
separately.

## Verification

- `:saas:compileJava` succeeds with Flyway removed (no code imports
`org.flywaydb.*`).
- No test or ArchUnit rule references Flyway or the migration files.
- No runtime/data impact: Flyway never ran against any live database.

---------

Co-authored-by: James Brunton <jbrunton96@gmail.com>
2026-07-21 09:32:56 +00:00
Anthony Stirling 79dc7d5615 Multi node cluster fixes (#7025)
- Exclude DataRedisRepositoriesAutoConfiguration (cluster crash-loop
fix)
- Share JWT signing keys via the DB + require a shared credential key in
cluster mode
- Make policy run status/listing visible across nodes


---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-07-20 20:51:30 +00:00
James BruntonandEthanHealy01 0fc2958daa Add explicit length to member column to avoid ddl resizing (#7109)
# Description of Changes
There's currently a column size inconsistency between the SaaS v3 DB and
the main Java code which causes the backend to fail to start up when
connected to a fresh DB. This is because the column previously was width
255, but now it's officially width 50, but the Java type is still
implicitly `varchar(255)` because there's no length attribute. If it's a
fresh DB, Postgres throws an error that it can't expand the column (this
doesn't error on an existing DB because the column is already wide
enough behind the scenes).

Co-authored-by: EthanHealy01 <80844253+EthanHealy01@users.noreply.github.com>
2026-07-20 15:23:11 +00:00
ConnorYoh c19d56de22 PAYG usage card: review follow-ups (avg/PDF, empty-state, unique wording) (#6967)
Small follow-up to #6957 addressing the three non-blocker findings from
its review. **Draft / stacked on #6957** — the diff shows #6957's
changes until it merges, then auto-narrows to just these 5 files. Mark
ready + rebase onto `main` once #6957 lands.

### 1. avg-per-PDF no longer blends unsynced units over synced-only docs
`avgCostMinor` now divides **synced** units (`spendUnitsThisPeriod`) by
synced docs, so numerator and denominator cover the same population.
Combined-billing `pendingUnits` (units-only, no doc count) previously
inflated the average for linked-instance teams. The "meter units" figure
still shows synced+pending (total current usage) — only the *average* is
synced-only.

### 2. Empty-state: unsynced-only reads cleanly
When `docs == 0` but there are pending meter units (combined-billing,
nothing synced yet), the card showed a bare **"0 PDFs"** headline with a
count-less summary and no split. It now shows a **"{n} meter units
pending sync from linked instances"** note instead. New `unitsPending`
i18n key + a `UnsyncedOnly` story. (Only reachable on the
combined-billing path; pure-SaaS teams are unaffected.)

### 3. uniquePdfs wording is now accurate
`document_fingerprint` is a hash of a charge's whole **input set**, so
the same file reused across *different* groupings (standalone, then
later in a merge `{A,B}`) counts per grouping — a close approximation of
"unique PDFs", exact for the single-input common case. Softened the FE
type doc + the `WalletLedgerEntry.document_fingerprint` javadoc to say
so (no behaviour change; counting model unchanged).

### Verification
FE typecheck / test / lint / format all clean; `:saas:compileJava`
green. No behaviour change beyond #1 (avg) and #2 (empty-state copy); #3
is doc-only.
2026-07-20 13:51:55 +00:00
LudyandCopilot bb830e5711 build: upgrade google-java-format and restore strict Spotless validation (#7091)
# Description of Changes

- Upgraded google-java-format from 1.28.0 to 1.35.0.
- Removed the broad `suppressLintsFor` workaround for the
`google-java-format` step.
- Ensured the shared `gradle/spotless.gradle` configuration is
recognized by the relevant CI path filters and repository automation.
- Kept the shared formatter configuration available to all backend
modules.
- Verified that google-java-format 1.35.0 runs successfully on JDK 25
for the Common, Core, and SaaS modules.
- Confirmed that the previous claim about a general Guava 32.x crash on
JDK 24/25 no longer justifies suppressing all formatter lint failures.

### Verification

Verified with Temurin JDK 25.0.3 and google-java-format 1.35.0. The
formatter still depends on Guava 32.1.3-jre, and no `suppressLintsFor`
configuration is present.

```bash
./gradlew \
  :common:spotlessJavaCheck \
  :stirling-pdf:spotlessJavaCheck \
  --rerun-tasks
```

Result:

```text
> Task :common:spotlessJava
> Task :common:spotlessJavaCheck
> Task :stirling-pdf:spotlessJava
> Task :stirling-pdf:spotlessJavaCheck

BUILD SUCCESSFUL in 26s
4 actionable tasks: 4 executed
```

Using `--rerun-tasks` ensured that the formatter was executed and that
the result did not come from the Gradle task cache.

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-07-20 12:05:53 +00:00
Ludy c6e84a2124 fix(admin-settings): correctly mask Telegram bot tokens in settings output (#6822)
# Description of Changes

- What was changed
- Fixed the sensitive-field detection logic in `AdminSettingsController`
so `botToken` is matched correctly after lowercasing the field name.
- This ensures Telegram bot tokens are masked consistently in admin
settings responses.
- Why the change was made
- The previous check used `lowerField.contains("botToken")`, which could
never match after converting the field name to lowercase.
- As a result, `botToken` values could remain visible in masked settings
output.

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-07-20 12:01:59 +00:00