fix(matrix-rtc): keep re-keying ghosts fast until crypto sync catches up #39

Merged
robocub merged 1 commit from fix/ghost-crypto-readiness-race into master 2026-06-23 21:33:44 +00:00
Collaborator

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.rs get_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 — and distribute_key_loop then drained the burst and marked recipients keyed unconditionally, 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 keyed 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 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 tokio test-util dev-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

## 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.rs` `get_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** — and `distribute_key_loop` then drained the burst and marked recipients `keyed` *unconditionally*, 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 `keyed` 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 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 tokio `test-util` dev-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](https://claude.com/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>
robocub merged commit acc73ad7ad into master 2026-06-23 21:33:44 +00:00
robocub referenced this pull request from a commit 2026-06-23 21:36:27 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
dark/nether-voicebridge!39
No description provided.