45b42202fa
Follow-up on the salvaged #111034: - `_start_multiplex` published the enumerated home list before the gate had filtered it, so a raising `profile_gate` (the Desktop stand-down probe from #100489) kept the thread alive but ticked every profile UNGATED — racing the gateway that owns them for the same cron store. The list is now assigned only after gating; a gate failure yields zero ticks for that cycle. - `cron/scheduler_thread.py::SupervisedTickerThread` wraps the gateway ticker thread; `_start_gateway_housekeeping` gets a per-tick "Cron ticker supervisor" chore that respawns a ticker that ended without a stop request and logs the outage at ERROR. Every guard inside `start()` keeps the loop alive, but nothing outside it could notice a thread that had already ended. - Tests trimmed to the invariants, proven red on origin/main: a REAL corrupt `executions.db` (no patched recover) no longer kills the ticker; a raising gate keeps the thread alive with zero ticks; housekeeping restarts a dead ticker and leaves a stopped one alone. Root cause: the unguarded pre-loop `recover_interrupted_executions()` + `record_ticker_heartbeat()` were added byd9dd05b69d(#61791, "truthful execution ledger", 2026-07-09). The reporter's build (e440bf35) also carried #107485's `completed_occurrence()` in the due scan, which opens the same ledger on every tick — the first traceback in their errors.log is that in-loop hit (caught); the restart then hit the SAME corrupt ledger from the pre-loop recovery scan, which nothing caught: thread dead, no heartbeat, no error marker.