sterilize: fix finalize WHERE — allow review→applying→done transitions

Bug: my anti-zombie guard from 4707e6a was too strict — WHERE clause
required state IN ('running','applying') to update. But the normal
flow goes running→review→applying→done. Once a job entered review,
NO state transition could fire — including the legitimate apply
sequence triggered by user clicking "apply selected".

Symptom Cobb hit: clicked apply on job 6, the daemon thread did the
work (11 of 13 proposals applied cleanly to Mealie), but the row
stayed at state='review' so the UI never moved off the review screen.
The 11 successful applies are real — Mealie has the updated
recipeIngredient food links. The bookkeeping just didn't follow.

Fix: change WHERE clause from a positive whitelist (running/applying)
to a negative blocklist (NOT IN done/failed/cancelled). This still
prevents the original failure mode (daemon overwriting a user-cancelled
job) — terminal states still can't be overwritten — but lets review
transition to applying when the user approves.

Same fix applied to finalize_consolidate_job since it copy-pasted the
same too-strict guard.
This commit is contained in:
Kayos 2026-04-30 17:55:13 -07:00
parent d97fdbc407
commit 30928b482f

View file

@ -972,13 +972,14 @@ class DB:
) )
def finalize_sterilize_job(self, job_id: int, *, state: str) -> None: def finalize_sterilize_job(self, job_id: int, *, state: str) -> None:
"""Move job to a terminal state (review/done/failed/cancelled). """Move job to a new state. Will NOT overwrite a terminal state
(done / failed / cancelled) that's the anti-zombie guard that
keeps user cancels from being silently replaced when the daemon
thread limps to the finish line.
Will NOT overwrite a job that's already terminal — if a runner is Allowed source states: running, applying, review. The review state
about to call finalize('done') but the row was set to 'cancelled' is part of the normal flow (walk done review user approves
externally, we leave the cancellation in place. This is the applying), so transitions out of review must work."""
anti-zombie guard that keeps user cancels from being silently
replaced when the daemon thread limps to the finish line."""
with self.conn() as c, c.cursor() as cur: with self.conn() as c, c.cursor() as cur:
cur.execute( cur.execute(
""" """
@ -989,7 +990,7 @@ class DB:
last_progress_at = NOW(), last_progress_at = NOW(),
current_slug = NULL current_slug = NULL
WHERE id=%s WHERE id=%s
AND state IN ('running','applying') AND state NOT IN ('done','failed','cancelled')
""", """,
(state, state, job_id), (state, state, job_id),
) )
@ -1169,6 +1170,9 @@ class DB:
) )
def finalize_consolidate_job(self, job_id: int, *, state: str) -> None: def finalize_consolidate_job(self, job_id: int, *, state: str) -> None:
"""Same anti-zombie guard as finalize_sterilize_job — won't overwrite
a terminal state (done/failed/cancelled), but allows the normal
runningreviewapplyingdone flow."""
with self.conn() as c, c.cursor() as cur: with self.conn() as c, c.cursor() as cur:
cur.execute( cur.execute(
"""UPDATE cauldron_consolidate_jobs """UPDATE cauldron_consolidate_jobs
@ -1178,7 +1182,7 @@ class DB:
last_progress_at = NOW(), last_progress_at = NOW(),
current_cluster = NULL current_cluster = NULL
WHERE id=%s WHERE id=%s
AND state IN ('running','applying')""", AND state NOT IN ('done','failed','cancelled')""",
(state, state, job_id), (state, state, job_id),
) )