fix(matrix-rtc): keep re-keying ghosts fast until crypto sync catches up #39
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "fix/ghost-crypto-readiness-race"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem
On an encrypted bridge, each Discord speaker's ghost is its own crypto client. Its media-key send resolves recipient devices from that client's own store (
key_transport.rsget_user_devices, local-only), which only populates once the client's background sync has tracked the room's members. When a cold ghost sent before that sync caught up it reached nobody — anddistribute_key_loopthen drained the burst and marked recipientskeyedunconditionally, dropping to the 60s heartbeat. That speaker was then silent in the encrypted call for up to a minute.Symptom: "certain users don't come through" on the encrypted side. 57
ghost media-key send failed — no recipient devices known yet (ghost crypto sync not caught up)events were found in the production VPS journal; a warm local store never reproduced it (it's a cold-start / load race).Fix
Only spend a burst slot and record
keyedwhen a send reaches at least one device. When it reaches nobody, leavekeyed/burstuntouched and stay on the 3s fast-retry interval until the ghost's crypto sync catches up — bounding the worst-case silent window to ~3s instead of ~60s.Test
Adds
key_loop_fast_retries_until_crypto_sync_catches_up(tokio paused clock): with a transport that reaches nobody, the old logic drains the burst and goes quiet (~6 sends in 40s, then a 60s gap); the fix keeps fast-retrying (~14 sends). Verified the test fails on the pre-fix logic and passes on the fix. Adds a tokiotest-utildev-dependency.Validation
cargo test -p nvb-matrix-rtc(26 passed), clippy clean, full workspace build. Built a debug binary and ran it on a VPS staging instance against the real encrypted room: all 3 bridges connect, Dark Voice resolves to Olm, every ghost delivers, no regression. (The cold-start failure path is load/timing-dependent and didn't trigger in the light staging call, so the fix's new path is validated by the unit test rather than live.)🤖 Generated with Claude Code
On an encrypted bridge each Discord speaker's ghost is its own crypto client; its media-key send resolves recipient devices from that client's own store (key_transport `get_user_devices`), which only populates once the client's background sync has tracked the room's members. When a cold ghost sent before that sync caught up it reached nobody — and `distribute_key_loop` then drained the burst and marked recipients `keyed` unconditionally, dropping to the 60s heartbeat. Result: that speaker was silent in the encrypted call for up to a minute ("certain users don't come through"; 57 such failures observed on a busy VPS, while a warm local store never reproduced it). Fix: only spend a burst slot / record recipients when a send reaches at least one device. When it reaches nobody, leave `keyed`/`burst` untouched and stay on the 3s fast-retry interval until the ghost's sync catches up — bounding the worst-case silent window to ~3s instead of ~60s. Adds a regression test (`key_loop_fast_retries_until_crypto_sync_catches_up`) that fails on the old logic (~6 sends in 40s, then a 60s gap) and passes on the fix (~14 sends), plus a tokio `test-util` dev-dependency for the paused clock. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>