9b9cfbc1fd
The age-only stale-claim sweep (t_3778a491, already on main) force-releases an in-memory _running_job_ids claim only once it is older than max(2*interval, 30m). A leaked claim that is YOUNG (inside its allowance) while the durable executions ledger already proves the last run ended stays wedged: the job is returned as due every tick, _submit_with_guard short- circuits on 'already running', and next_run_at keeps fast-forwarding with no execution — the exact 2026-08-14 recurring-router incident (t_20e23f84), which survived a gateway restart because the in-memory age bound alone could not see a run the ledger had already finished. sweep_stale_inflight now reconciles each in-flight claim against the durable executions ledger (cron/executions.db): if the job's MOST RECENT execution row is terminal (completed/failed/unknown), the run provably ended, so the claim is stale by construction regardless of its in-memory age and is force- released. This is a persisted-state recovery path: the ledger is written by the worker that ran the job and read by ANY ticker process (including one that started AFTER the leak), so a leaked claim is recoverable without force-run/resume and without depending on which process holds it in memory. A ledger-terminal release is authoritative — it does not write a synthetic mark_job_run failure (the ledger already records the outcome). Added TestLedgerTerminalReconciliation (4 tests): young+terminal -> released (RED on main, GREEN here), no-ledger-row -> not released, running-row -> not released, old+terminal -> released once without synthetic failure.