fix: close Audio Router self-echo PUBACK/subscription-delivery race

publish_router_mode registered its self-echo correlation marker before
publishing but removed it in a finally block the instant the QoS 1 publish()
call observed its PUBACK. A broker may legally deliver a Controller's own
subscription copy of that same publish after the PUBACK, since MQTT does not
order the two; when that happened, the Controller mistook its own late echo
for live operator intent and relinquished automatic restoration ownership.

Keep the marker claimable for a short, bounded mqtt.router_echo_grace_seconds
window (default 2s) after the PUBACK instead of clearing it immediately.
Genuine external/operator commands are unaffected: once the grace window
elapses, a matching payload is still correctly treated as operator intent
and ownership is relinquished as before.

Verified with deterministic fake-clock unit tests plus a real Mosquitto
broker integration test (tests/test_router_echo_mosquitto_integration.py,
skipped automatically when Docker is unavailable).
This commit is contained in:
Media
2026-08-07 17:13:54 -05:00
parent ec826bb8f3
commit c2544c44c7
8 changed files with 367 additions and 37 deletions
+5
View File
@@ -10,6 +10,11 @@ mqtt:
command_queue_size: 512
# QoS 1 is accepted only after the broker PUBACKs within this bound.
publish_ack_timeout_seconds: 2
# Audio Router self-echo correlation stays claimable for this long after
# a Controller-originated mode-set publish is PUBACK'd, so a subscription
# echo that is delivered after the PUBACK is still recognized as ours
# instead of being mistaken for operator intent.
router_echo_grace_seconds: 2
topics:
player_command: youtube/player/cmd