8f9abc9873
get_due_jobs() fires purely off the stored next_run_at <= now, with no check that the stored instant is still an occurrence of the schedule's current expression. A direct jobs.json edit that narrows schedule.expr (e.g. daily "0 7 * * *" -> weekdays "0 7 * * 1-5") keeps the stored next_run_at computed under the old expression, so the job fires on days the new expression excludes. The within-grace fire and the catch-up "run once now" path both inherit the wrong instant. Add a best-effort stale-schedule guard on the fire path: when the stored next_run_at is not an occurrence of the current cron expression, re-anchor it via compute_next_run() from the current expression and skip the fire. Non-cron kinds, missing expr, croniter unavailability, and malformed input all report a match so the fire path keeps its existing semantics. Recomputation uses the current expression, so the re-anchor converges and cannot defer a valid job forever. Fixes #93049