Commit Graph
16 Commits
Author SHA1 Message Date
Media 7e6b916963 feat: add isolated utility export modes 2026-08-12 18:48:03 -05:00
Media b934b69e5a feat: persist isolated utility export jobs 2026-08-12 18:21:29 -05:00
Media 8db46df69e fix: enforce Player download height limit 2026-08-12 18:18:38 -05:00
Media c2544c44c7 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).
2026-08-07 17:13:54 -05:00
Media ec826bb8f3 fix: restrict web ingest to official YouTube hosts and bound submission size
The submit form's URL validation accepted any http/https host. Restrict it
to an explicit allowlist of official YouTube hosts using normalized,
exact hostname comparison (no substring/suffix matching), reject embedded
userinfo and malformed URLs, and add server-side length bounds for the URL,
submitter, note, and total request body.
2026-08-07 17:04:05 -05:00
Media 270ede8b77 fix: allow genuinely new playlist ingest without fresh Player evidence
The evidence gate on enqueue_download blocked every download, including a
URL that could only land in a slot store.reserve_download already proves is
empty and unguarded. After a Controller-only restart with the Player still
connected and idle, no fresh detailed status ever arrives, so this could
deadlock all new ingest indefinitely even though nothing unsafe would occur.

Narrow the gate to only require fresh evidence when the URL already occupies
a non-empty slot (refresh/reuse). Deletion, expiry, and cleanup/recovery
gating are unchanged.
2026-08-07 17:01:39 -05:00
Media 5e81403be3 feat: overhaul video player controller 2026-08-06 20:08:12 -05:00
Media 72cc66df92 fix: pass mqtt credentials through compose 2026-08-06 19:30:22 -05:00
Media 63ea314b18 build: harden controller v2 runtime 2026-08-06 19:23:02 -05:00
Media 4e185b2e1c refactor: wire controller v2 into web runtime 2026-08-06 18:58:05 -05:00
Media 3ee7f94d72 feat: coordinate authoritative playback and audio routing 2026-08-06 18:52:01 -05:00
Media 8b3bdae8a0 feat: reduce authoritative player mqtt state 2026-08-06 17:41:07 -05:00
Media 45083207b4 feat: reserve downloads and protect slot cleanup 2026-08-06 17:34:11 -05:00
Media 3ff9ba4637 feat: add atomic state store and legacy migration 2026-08-06 17:31:02 -05:00
Media 9091f27283 test: characterize contracts and make startup explicit 2026-08-06 17:24:20 -05:00
Media fc27feaea1 initial commmit
A video player controller for the video player node
2026-06-14 14:34:34 -05:00