490ee1607a
_notif_gateway_owns_heartbeat decided by the immutable sessions.source column, so a heartbeat on an ARCHIVED gateway-sourced row (Telegram /reset, idle/daily auto-reset, compression rotation) was skipped by the Desktop poller and never registered by the gateway either — restore_heartbeat_watches only claims a key whose current session_id is that row. The tick belonged to nobody and stayed due forever, where origin/main's Desktop fired it. Ownership now uses the same predicate the gateway does: a gateway_routing entry whose current session_id is this session, with an origin and not suspended (SessionDB.gateway_routing_entry_for_session; both the session's profile store and the launch store are consulted so multiplexed and per-profile gateways are covered). No entry is fail-open, as on main. The check runs after the cheap is_active/is_due gate so idle sessions never touch the DB per poll. Probe (SessionStore telegram -> force_new, heartbeat on the archived sid): before: desktop fired False / gateway watches [] / still due True; after: desktop fired True / still due False; the current gateway sid is still left to the gateway (desktop fired False) — the hijack fix stays intact.