discord_radio: retained desired redelivery no longer undoes a local pause (node-26#34)

A local pause records the applied seq; only a strictly newer seq from C2
lifts it. The retained message the broker redelivers on reconnect carries
the same seq, so it now leaves the bots logged out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Logan Cusano
2026-10-27 20:06:55 -04:00
co-authored by Claude Opus 5.5
parent ab106264e0
commit a456b502db
2 changed files with 28 additions and 2 deletions
+16 -2
View File
@@ -183,6 +183,10 @@ class RadioManager:
self._mqtt_down_since: Optional[float] = None self._mqtt_down_since: Optional[float] = None
self._failsafe = False self._failsafe = False
self._paused = False self._paused = False
# The applied seq at the moment of a local pause (node-26#34). The
# broker redelivers the retained desired message on every reconnect
# with the SAME seq; only a strictly newer seq from C2 lifts the pause.
self._paused_at_seq: Optional[int] = None
self._ticks = 0 self._ticks = 0
# ------------------------------------------------------------------ # ------------------------------------------------------------------
@@ -210,15 +214,25 @@ class RadioManager:
logger.error(f"Malformed Discord desired stream entry ignored: {s!r}") logger.error(f"Malformed Discord desired stream entry ignored: {s!r}")
self._ensure_watchdog() self._ensure_watchdog()
self._desired = wanted self._desired = wanted
self._paused = False if self._paused and self._redelivery_of_paused_state(seq):
logger.info(f"Discord desired seq {seq} is a redelivery — staying paused locally.")
else:
self._paused = False
self._paused_at_seq = None
await self.reconcile() await self.reconcile()
if seq is not None: if seq is not None:
self._applied_seq = seq self._applied_seq = seq
await self.publish_actual() await self.publish_actual()
def _redelivery_of_paused_state(self, seq: Optional[int]) -> bool:
"""True when `seq` is not newer than what was applied at pause time."""
return seq is not None and self._paused_at_seq is not None and seq <= self._paused_at_seq
async def pause_locally(self) -> None: async def pause_locally(self) -> None:
"""Local-dashboard stop: log every bot out until C2 sends a new list.""" """Local-dashboard stop: log every bot out until C2 sends a NEW list
(a seq strictly greater than the one applied now — node-26#34)."""
self._paused = True self._paused = True
self._paused_at_seq = self._applied_seq
await self.reconcile() await self.reconcile()
await self.publish_actual() await self.publish_actual()
+12
View File
@@ -263,6 +263,18 @@ async def test_local_pause_logs_out_until_next_desired(env):
assert env.mgr.is_connected assert env.mgr.is_connected
async def test_retained_redelivery_does_not_undo_local_pause(env):
"""node-26#34: the broker redelivers the retained desired message (same
seq) on every reconnect; that must not log the bots back in."""
await env.mgr.apply_desired(_desired(4, (1, 11, "t1")))
await env.mgr.pause_locally()
await env.mgr.apply_desired(_desired(4, (1, 11, "t1")))
assert not env.mgr.is_connected
assert env.published[-1]["streams"]["1:11"]["state"] == "paused_locally"
await env.mgr.apply_desired(_desired(5, (1, 11, "t1")))
assert env.mgr.is_connected
async def test_no_message_content_intent_on_real_client(): async def test_no_message_content_intent_on_real_client():
client = discord_radio._default_client_factory() client = discord_radio._default_client_factory()
try: try: