a06f2c662a
Extend voice-limit-guard to enforce a per-room publish-source policy (io.lotus.room_quality allow_screenshare/allow_camera) for ALL Matrix clients, alongside the existing participant limit. - At token issue, re-sign the LiveKit JWT's canPublishSources to drop forbidden sources (microphone always kept). Verifies our own secret signed the token first and fails open on mismatch, so a secret drift can never mint a token the SFU rejects. Limit check and source policy are independent (one's outage can't skip the other). - Live (mid-call) enforcement: a background reconcile loop calls LiveKit UpdateParticipant to revoke a forbidden source from participants who joined before the policy changed -- which unpublishes their in-progress screenshare/camera server-side within ~3s and blocks re-publish. Only removes sources (never grants), preserves other permission flags, fails open, and runs as a daemon thread that cannot crash or block token issuance. - Endpoint-specific room-id extraction (/get_token->room_id, /sfu/get->room) so a client sending both keys can't get a different room's policy applied. - Auto-deploy the guard on LXC 151 (py_compile-gated, backup + rollback). - Unit tests: JWT re-sign/verify + tamper, secret-mismatch, source narrowing, reconcile (never-grant / preserve-flags / disable-on-empty), fail-open. Numeric bitrate/fps caps are NOT server-enforceable on an SFU (LiveKit forwards, never transcodes) and remain a Lotus-client-cooperative setting; the screenshare/camera permission is the hard cross-client lever. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>