From c79a2efaabf13c702f5c592c660526d24f23122e Mon Sep 17 00:00:00 2001 From: mARTin-B78 Date: Fri, 4 Sep 2026 09:24:14 +0200 Subject: [PATCH] fix: bound durable background job retention --- CHANGELOG.md | 2 ++ VERSION | 2 +- core/database.py | 5 +++++ tests/test_security.py | 16 ++++++++++++++++ 4 files changed, 24 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index de646a7..e3d1f9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,8 @@ Follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ยท versioned wi - **Durable Background Job Metadata.** Transcription, voice design, casting, and consistent-image jobs now persist bounded status metadata in SQLite; unfinished jobs are reported as interrupted after a server restart, while prompts, secrets, audio, and image payloads are excluded from persistence. +- **Bounded Durable Job Retention.** Completed background-job metadata is pruned oldest-first at a fixed limit so + restart-safe status tracking cannot become an unbounded database growth path. - **STT Cancellation Safety.** Queued transcription cancels before consuming the single STT slot, and timed-out requests keep the device slot held until the underlying worker actually exits. - **Credential Boundary Review.** Provider credentials are now documented as server-only; only the explicitly diff --git a/VERSION b/VERSION index d28b1eb..a6c2798 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.22.9 +1.23.0 diff --git a/core/database.py b/core/database.py index 64eb0f2..602eb2a 100644 --- a/core/database.py +++ b/core/database.py @@ -254,6 +254,11 @@ def background_job_create(job_id: str, kind: str, *, fingerprint: str = "", crea "VALUES (?,?,?,?,?,?,?,?,?,?,?)", (str(job_id), str(kind)[:80], "queued", "Queued", 0, 0, None, None, str(fingerprint)[:128], now, now), ) + conn.execute( + "DELETE FROM background_jobs WHERE job_id IN (" + "SELECT job_id FROM background_jobs WHERE done=1 ORDER BY updated ASC " + "LIMIT -1 OFFSET 255)" + ) conn.commit() diff --git a/tests/test_security.py b/tests/test_security.py index e382c2f..b2c1604 100644 --- a/tests/test_security.py +++ b/tests/test_security.py @@ -194,6 +194,22 @@ class SecurityValidationTests(unittest.TestCase): conn.execute("DELETE FROM background_jobs WHERE job_id=?", (job_id,)) conn.commit() + def test_background_job_persistence_prunes_old_completed_rows(self): + from core.database import _open, background_job_create, background_job_update + prefix = "prune-job-" + try: + for index in range(260): + job_id = prefix + str(index) + background_job_create(job_id, "test") + background_job_update(job_id, stage="done", done=True) + with _open() as conn: + count = conn.execute("SELECT COUNT(*) FROM background_jobs WHERE job_id LIKE ?", (prefix + "%",)).fetchone()[0] + self.assertLessEqual(count, 256) + finally: + with _open() as conn: + conn.execute("DELETE FROM background_jobs WHERE job_id LIKE ?", (prefix + "%",)) + conn.commit() + def test_fish_cache_eviction_is_bounded(self): from pathlib import Path from tempfile import TemporaryDirectory