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:
"""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
about to call finalize('done') but the row was set to 'cancelled'
externally, we leave the cancellation in place. This is the
anti-zombie guard that keeps user cancels from being silently
replaced when the daemon thread limps to the finish line."""
Allowed source states: running, applying, review. The review state
is part of the normal flow (walk done review user approves
applying), so transitions out of review must work."""
with self.conn() as c, c.cursor() as cur:
cur.execute(
"""
@ -989,7 +990,7 @@ class DB:
last_progress_at = NOW(),
current_slug = NULL
WHERE id=%s
AND state IN ('running','applying')
AND state NOT IN ('done','failed','cancelled')
""",
(state, state, job_id),
)
@ -1169,6 +1170,9 @@ class DB:
)
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:
cur.execute(
"""UPDATE cauldron_consolidate_jobs
@ -1178,7 +1182,7 @@ class DB:
last_progress_at = NOW(),
current_cluster = NULL
WHERE id=%s
AND state IN ('running','applying')""",
AND state NOT IN ('done','failed','cancelled')""",
(state, state, job_id),
)