diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0477848..9109cbd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,32 @@ may change in any release. See the status note in the README.
## [Unreleased]
+## [0.5.7] — 2026-10-31
+
+A maintenance release: dependency updates since 0.5.6, one security fix in the frontend, and a test flake fixed.
+Nothing in the configuration, the API or the schema changes, and there is no new migration. The database image moves
+to `pgvector/pgvector:0.8.7-pg18`, a patch release of the extension on the same Postgres major, so the data
+directory and its mount stay as they are.
+
+### Changed
+
+- **Spring Boot 4.1.2, Gradle 9.8.2, bun 1.4.4, Testcontainers 2.0.5, Spotless 8.10.4, Vitest 5 with Angular 22.2**,
+ and the rest of the backend and frontend minor and patch updates. The frontend image builds on Node.js 24, and its
+ runtime is `nginx-unprivileged` 1.31.
+- **The database image is `pgvector/pgvector:0.8.7-pg18`**, in Compose and for the tests alike; Renovate had moved
+ only the Compose file, so for a while the tests checked a different image than the one that ships.
+- **`@angular-architects/ngrx-toolkit` is replaced by `@ngrx-toolkit/core`**, the package's new name from v22 on.
+- **Spring AI stays at 2.0.1.** 2.0.2 brings an openai-java (from 4.66.0) whose stream close releases the transport
+ under an in-flight read, so stopping a turn or hitting its deadline no longer ends the model call. Renovate proposes
+ Spring AI in a pull request of its own from now on.
+
+### Fixed
+
+- **dompurify 3.4.17**, which closes three advisories on 3.4.16 and earlier (critical: `SAFE_FOR_TEMPLATES` bypass in
+ shadow roots; high: an `IN_PLACE` hook left event handlers armed; low: the SMIL `href` guard skipped for XHTML).
+- **The suggestion prewarm tests no longer fail on a loaded runner.** The model stub counted a connection dropped
+ before its body as a request and handed it the next scripted reply.
+
## [0.5.6] — 2026-10-13
The chat's model picker shows what each model costs and what the chat has spent, every turn records its tokens and
@@ -1778,7 +1804,8 @@ Found while building the demo, all of them in paths only a container exercises:
left six.
- The shortlist card printed the description's Markdown syntax in its teaser.
-[Unreleased]: https://github.com/codeministry/leadgen/compare/v0.5.6...HEAD
+[Unreleased]: https://github.com/codeministry/leadgen/compare/v0.5.7...HEAD
+[0.5.7]: https://github.com/codeministry/leadgen/releases/tag/v0.5.7
[0.5.6]: https://github.com/codeministry/leadgen/releases/tag/v0.5.6
[0.5.5]: https://github.com/codeministry/leadgen/releases/tag/v0.5.5
[0.5.4]: https://github.com/codeministry/leadgen/releases/tag/v0.5.4
diff --git a/CLAUDE.md b/CLAUDE.md
index aec01db..6e071b4 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -42,7 +42,7 @@ Violating one of these is expensive, and most of them fail silently.
bulk-archive: these run against the live instance from the terminal or as direct SQL. A
one-off admin task that leaves a commit behind has been done wrong, because the next
release then carries a migration nobody asked for.
-- **The database image is the pinned `pgvector/pgvector:0.8.6-pg18`, set in `docker-compose.yml` and `Databases.java` and nowhere else.** — reasoning in `docs/decisions/retrieval.md`.
+- **The database image is the pinned `pgvector/pgvector:0.8.7-pg18`, set in `docker-compose.yml` and `Databases.java` and nowhere else.** — reasoning in `docs/decisions/retrieval.md`.
- **Mount the database volume at `/var/lib/postgresql`, never `…/data`; a major bump moves tag, `PGDATA`, mount and data at once.** — reasoning in `docs/decisions/retrieval.md`.
- **The vector column is 2000 wide, a wider vector is truncated at the seam, and thresholds move only after measuring with `docs/samples/measure_embeddings.ts`.** — reasoning in `docs/decisions/retrieval.md`.
- **No CV tailoring.** Fixed PDFs in `config/documents/`, selected by the language
diff --git a/backend/build.gradle.kts b/backend/build.gradle.kts
index a5f2cf4..8abdc24 100644
--- a/backend/build.gradle.kts
+++ b/backend/build.gradle.kts
@@ -14,7 +14,7 @@ plugins {
}
group = "de.codeministry"
-version = "0.5.6"
+version = "0.5.7"
/*
* Java 25 through the toolchain rather than the ambient JDK: Gradle runs on
diff --git a/backend/smoke/docker-compose.smoke.yml b/backend/smoke/docker-compose.smoke.yml
index 6e50974..a2cd932 100644
--- a/backend/smoke/docker-compose.smoke.yml
+++ b/backend/smoke/docker-compose.smoke.yml
@@ -1,6 +1,6 @@
# The smoke stack, as an OVERLAY on `docker-compose.yml` and never as a file of its own.
#
-# `postgres` is inherited verbatim, which is the point: `pgvector/pgvector:0.8.6-pg18` is
+# `postgres` is inherited verbatim, which is the point: `pgvector/pgvector:0.8.7-pg18` is
# named in exactly two places in this repository and a third copy is a third thing to
# forget on the next major. Run it as
#
diff --git a/backend/src/main/java/de/codeministry/leadgen/config/ConfigProperties.java b/backend/src/main/java/de/codeministry/leadgen/config/ConfigProperties.java
index a0b9d9e..c64946e 100644
--- a/backend/src/main/java/de/codeministry/leadgen/config/ConfigProperties.java
+++ b/backend/src/main/java/de/codeministry/leadgen/config/ConfigProperties.java
@@ -57,7 +57,7 @@ public record ConfigProperties(
// One home per default, read by the bound constructor and the hand-built one alike, so
// the two cannot drift. `@DefaultValue` takes a compile-time constant, hence strings.
- private static final String DEFAULT_VERSION = "0.5.6";
+ private static final String DEFAULT_VERSION = "0.5.7";
private static final String DEFAULT_CONFIG_POLL_INTERVAL = "PT2S";
private static final String DEFAULT_SCORE_BATCH_POLL_INTERVAL = "PT5M";
private static final String DEFAULT_INGEST_CRON = "-";
diff --git a/backend/src/main/resources/application.yaml b/backend/src/main/resources/application.yaml
index bed6041..3480181 100644
--- a/backend/src/main/resources/application.yaml
+++ b/backend/src/main/resources/application.yaml
@@ -25,7 +25,7 @@ spring:
name: leadgen
# What a client reads in the `initialize` result: the running build, from the one place
# the build names itself (leadgen.version below).
- version: ${LEADGEN_VERSION:0.5.6}
+ version: ${LEADGEN_VERSION:0.5.7}
# STREAMABLE and not SSE: Spring AI 2.0 deprecates SSE, and Streamable HTTP is what
# current MCP clients negotiate.
protocol: STREAMABLE
@@ -43,10 +43,10 @@ spring:
# not have to exist — without it the defaults apply.
leadgen:
# What the header shows, and the only place the running build names itself. Without this
- # line the default on ConfigProperties applies and every image reports 0.5.6 —
+ # line the default on ConfigProperties applies and every image reports 0.5.7 —
# the Gradle version is not injected into the jar, and the Dockerfile copies libs/*.jar
# by glob, so nothing else carries it either. The deploy sets it from the image tag.
- version: ${LEADGEN_VERSION:0.5.6}
+ version: ${LEADGEN_VERSION:0.5.7}
config-dir: ${LEADGEN_CONFIG_DIR:config}
security:
# Asserts that something outside this process decides who reaches the port, which is
diff --git a/backend/src/test/java/de/codeministry/leadgen/Databases.java b/backend/src/test/java/de/codeministry/leadgen/Databases.java
index a4fd1b7..38ef025 100644
--- a/backend/src/test/java/de/codeministry/leadgen/Databases.java
+++ b/backend/src/test/java/de/codeministry/leadgen/Databases.java
@@ -35,7 +35,7 @@ public final class Databases {
* no target here that could miss it. The trap is written out beside the mount it applies
* to.
*/
- public static final String IMAGE = "pgvector/pgvector:0.8.6-pg18";
+ public static final String IMAGE = "pgvector/pgvector:0.8.7-pg18";
private Databases() {}
diff --git a/backend/src/test/java/de/codeministry/leadgen/LeadGenerationApplicationTests.java b/backend/src/test/java/de/codeministry/leadgen/LeadGenerationApplicationTests.java
index 7523ff3..54c0605 100644
--- a/backend/src/test/java/de/codeministry/leadgen/LeadGenerationApplicationTests.java
+++ b/backend/src/test/java/de/codeministry/leadgen/LeadGenerationApplicationTests.java
@@ -69,7 +69,7 @@ void flywayCreatedTheBaselineSchema() {
@Test
void theVectorExtensionIsThereAndTheColumnHasAWidth() {
- // The image is the thing under test here. `pgvector/pgvector:0.8.6-pg18` is a plain
+ // The image is the thing under test here. `pgvector/pgvector:0.8.7-pg18` is a plain
// postgres with the extension added, and on any image without it `V22` fails at startup
// naming the extension rather than the image — which is a long way from the compose file
// that actually decides it.
diff --git a/backend/src/test/java/de/codeministry/leadgen/web/StatusControllerTest.java b/backend/src/test/java/de/codeministry/leadgen/web/StatusControllerTest.java
index cd226f5..ba667c8 100644
--- a/backend/src/test/java/de/codeministry/leadgen/web/StatusControllerTest.java
+++ b/backend/src/test/java/de/codeministry/leadgen/web/StatusControllerTest.java
@@ -19,7 +19,7 @@
// into the `spring-boot-webmvc-test` module. The old import compiles against
// Boot 3 and simply does not exist here.
@WebMvcTest(StatusController.class)
-@TestPropertySource(properties = {"spring.application.name=lead-generation", "leadgen.version=0.5.6"})
+@TestPropertySource(properties = {"spring.application.name=lead-generation", "leadgen.version=0.5.7"})
class StatusControllerTest {
@Autowired
@@ -30,6 +30,6 @@ void returnsApplicationNameAndVersion() {
Assertions.assertThat(mvc.get().uri("/api/v1/status"))
.hasStatusOk()
.bodyJson()
- .isEqualTo("{\"application\":\"lead-generation\",\"version\":\"0.5.6\"}");
+ .isEqualTo("{\"application\":\"lead-generation\",\"version\":\"0.5.7\"}");
}
}
diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md
index f4f494f..7bb99dd 100644
--- a/docs/ARCHITECTURE.md
+++ b/docs/ARCHITECTURE.md
@@ -44,7 +44,7 @@ flowchart LR
direction TB
web["web
nginx serving the Angular build,
/api proxied to api:8080"]
api["api
Spring Boot: the pipeline,
the REST API, the schedulers"]
- db[("postgres
pgvector/pgvector:0.8.6-pg18")]
+ db[("postgres
pgvector/pgvector:0.8.7-pg18")]
web --> api --> db
end
subgraph outside["outside the machine"]
diff --git a/docs/decisions/native-image.md b/docs/decisions/native-image.md
index ecdf5db..7efadff 100644
--- a/docs/decisions/native-image.md
+++ b/docs/decisions/native-image.md
@@ -168,7 +168,7 @@ healthy service.
It is a bash script and a compose **overlay**, not a Gradle task and not a compose file of
its own. Not a Gradle task because it runs a container on a runner and dragging the
configuration cache and the toolchain into that buys nothing. An overlay because
-`pgvector/pgvector:0.8.6-pg18` is named in exactly two places in this repository and a
+`pgvector/pgvector:0.8.7-pg18` is named in exactly two places in this repository and a
third copy is a third thing to forget on the next major — `postgres` is inherited verbatim
and only GreenMail, WireMock and the service under test are added. That service is called
`smoke-api` and not `api`, because `api` carries a `build:` block an overlay cannot remove
diff --git a/docs/decisions/retrieval.md b/docs/decisions/retrieval.md
index 661e4ce..732d668 100644
--- a/docs/decisions/retrieval.md
+++ b/docs/decisions/retrieval.md
@@ -429,7 +429,7 @@ test there was, and it was available at any point.
## Moved from the root CLAUDE.md
-- **The database image is `pgvector/pgvector:0.8.6-pg18`, not plain postgres.** Deduplication's
+- **The database image is `pgvector/pgvector:0.8.7-pg18`, not plain postgres.** Deduplication's
two similarity strategies compare vectors, `V22` creates the extension, and an image without
it fails that migration with an error naming the extension rather than the image. It is named
in `docker-compose.yml` and once for the tests in
diff --git a/frontend/package.json b/frontend/package.json
index 5517f6d..8b83c12 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -1,6 +1,6 @@
{
"name": "frontend",
- "version": "0.5.6",
+ "version": "0.5.7",
"scripts": {
"start": "ng serve",
"build": "ng build",
diff --git a/frontend/src/app/app.spec.ts b/frontend/src/app/app.spec.ts
index 7cdb1b1..91bfe27 100644
--- a/frontend/src/app/app.spec.ts
+++ b/frontend/src/app/app.spec.ts
@@ -21,7 +21,7 @@ describe('App', () => {
const fixture = TestBed.createComponent(App);
fixture.detectChanges();
- httpMock.expectOne('/api/v1/status').flush({application: 'lead-generation', version: '0.5.6'});
+ httpMock.expectOne('/api/v1/status').flush({application: 'lead-generation', version: '0.5.7'});
// Run ingest and the model choice left the header for the workflow screen (operator,
// 2026-09-27), so the shell does not create the scoring-model store: the model list is
// not asked for here. The ingest store is created at the root (fix 2F-5), so its
@@ -34,7 +34,7 @@ describe('App', () => {
await fixture.whenStable();
fixture.detectChanges();
- expect(fixture.nativeElement.textContent).toContain('lead-generation 0.5.6');
+ expect(fixture.nativeElement.textContent).toContain('lead-generation 0.5.7');
httpMock.verify();
});