fix(telegram): disable DM topic mode when last binding is pruned
Follow-up to #31501. When the send-fallback prune removes a chat's final telegram_dm_topic_bindings row, also flip telegram_dm_topic_mode.enabled to 0 in the same transaction. Without this, a user who turns topics off in the Telegram client (rather than via /topic off) leaves enabled=1 with zero lanes: _recover_telegram_topic_thread_id keeps treating the chat as topic-enabled and lobby messages keep hunting for bindings that no longer exist. Clearing the flag makes recovery fully stand down once the dead topics are gone. Adds 3 regression tests covering the last-binding clear, the multi-binding no-op, and the unmatched-prune no-op.
This commit is contained in:
parent
11246dbe21
commit
6681f28d5b
2 changed files with 101 additions and 2 deletions
|
|
@ -4615,8 +4615,19 @@ class SessionDB:
|
|||
topic, causing tool progress, approvals, and replies to land
|
||||
in the wrong place. Issue #31501.
|
||||
|
||||
Returns the number of rows deleted (0 when the binding was
|
||||
already absent or the topic-mode tables haven't been
|
||||
When this prune removes the chat's *last* remaining binding,
|
||||
the chat's row in ``telegram_dm_topic_mode`` is also flipped to
|
||||
``enabled = 0`` in the same transaction. Otherwise the chat
|
||||
would be left in topic mode with zero lanes — and
|
||||
``gateway.run._recover_telegram_topic_thread_id`` keeps treating
|
||||
the chat as topic-enabled, lobby messages keep hunting for a
|
||||
binding that no longer exists, and a user who disabled topics in
|
||||
the Telegram client (rather than via ``/topic off``) stays stuck
|
||||
until the next send happens to fail. Clearing the flag makes
|
||||
recovery fully stand down once the dead topics are gone.
|
||||
|
||||
Returns the number of binding rows deleted (0 when the binding
|
||||
was already absent or the topic-mode tables haven't been
|
||||
migrated yet — both are silent no-ops; we never raise from
|
||||
a cleanup hot path).
|
||||
"""
|
||||
|
|
@ -4637,6 +4648,29 @@ class SessionDB:
|
|||
except sqlite3.OperationalError:
|
||||
# Tables don't exist yet — nothing to prune.
|
||||
deleted["count"] = 0
|
||||
return
|
||||
if not deleted["count"]:
|
||||
return
|
||||
# If that was the chat's last binding, disable topic mode for
|
||||
# the chat so recovery stops steering lobby messages at a now
|
||||
# empty lane set. Same transaction → no read-after-prune race.
|
||||
try:
|
||||
remaining = conn.execute(
|
||||
"""
|
||||
SELECT 1 FROM telegram_dm_topic_bindings
|
||||
WHERE chat_id = ? LIMIT 1
|
||||
""",
|
||||
(chat_id,),
|
||||
).fetchone()
|
||||
if remaining is None:
|
||||
conn.execute(
|
||||
"UPDATE telegram_dm_topic_mode "
|
||||
"SET enabled = 0, updated_at = ? WHERE chat_id = ?",
|
||||
(time.time(), chat_id),
|
||||
)
|
||||
except sqlite3.OperationalError:
|
||||
# telegram_dm_topic_mode absent — binding prune still stands.
|
||||
pass
|
||||
|
||||
self._execute_write(_do)
|
||||
return deleted["count"]
|
||||
|
|
|
|||
|
|
@ -155,6 +155,71 @@ class TestDeleteTelegramTopicBinding:
|
|||
db.close()
|
||||
|
||||
|
||||
class TestPruneClearsTopicModeWhenLastBindingGone:
|
||||
"""Proactive cleanup (#31501 follow-up): pruning the chat's final
|
||||
binding must also flip ``telegram_dm_topic_mode.enabled`` to 0 so
|
||||
recovery fully stands down — covers the user who disabled topics in
|
||||
the Telegram client without ever running ``/topic off``."""
|
||||
|
||||
def test_clears_enabled_when_last_binding_pruned(self, tmp_path):
|
||||
db = SessionDB(db_path=tmp_path / "state.db")
|
||||
db.enable_telegram_topic_mode(
|
||||
chat_id="5595856929", user_id="5595856929",
|
||||
)
|
||||
_seed_binding(db, thread_id="15287")
|
||||
assert db.is_telegram_topic_mode_enabled(
|
||||
chat_id="5595856929", user_id="5595856929",
|
||||
) is True
|
||||
|
||||
removed = db.delete_telegram_topic_binding(
|
||||
chat_id="5595856929", thread_id="15287",
|
||||
)
|
||||
|
||||
assert removed == 1
|
||||
assert db.is_telegram_topic_mode_enabled(
|
||||
chat_id="5595856929", user_id="5595856929",
|
||||
) is False
|
||||
db.close()
|
||||
|
||||
def test_keeps_enabled_while_other_bindings_remain(self, tmp_path):
|
||||
# Deleting one of several topics must NOT disable topic mode —
|
||||
# the chat still has healthy lanes that recovery should serve.
|
||||
db = SessionDB(db_path=tmp_path / "state.db")
|
||||
db.enable_telegram_topic_mode(
|
||||
chat_id="5595856929", user_id="5595856929",
|
||||
)
|
||||
_seed_binding(db, thread_id="15287", session_id="sess-stale")
|
||||
_seed_binding(db, thread_id="15418", session_id="sess-fresh")
|
||||
|
||||
db.delete_telegram_topic_binding(
|
||||
chat_id="5595856929", thread_id="15287",
|
||||
)
|
||||
|
||||
assert db.is_telegram_topic_mode_enabled(
|
||||
chat_id="5595856929", user_id="5595856929",
|
||||
) is True
|
||||
db.close()
|
||||
|
||||
def test_noop_prune_leaves_enabled_untouched(self, tmp_path):
|
||||
# A prune that matches no row must not flip the flag — there's
|
||||
# still a live binding the (wrong) thread_id didn't match.
|
||||
db = SessionDB(db_path=tmp_path / "state.db")
|
||||
db.enable_telegram_topic_mode(
|
||||
chat_id="5595856929", user_id="5595856929",
|
||||
)
|
||||
_seed_binding(db, thread_id="15287")
|
||||
|
||||
removed = db.delete_telegram_topic_binding(
|
||||
chat_id="5595856929", thread_id="99999",
|
||||
)
|
||||
|
||||
assert removed == 0
|
||||
assert db.is_telegram_topic_mode_enabled(
|
||||
chat_id="5595856929", user_id="5595856929",
|
||||
) is True
|
||||
db.close()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Adapter glue — _prune_stale_dm_topic_binding
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue