ensurePeered() now checks the local autoAcceptPeering setting before
initiating new peering. When disabled, only admin-explicit peer/initiate
and approval-request approve bypass this check. Closes the bypass where
client peer/ensure or outbox worker could auto-initiate outward peering
even when the admin intended to control all peering.
- C1: Include 'unreachable' peers in queueOutboxEvent query to prevent
UNIQUE constraint violation when creating placeholders
- I1: Add 'rejected' to StatusFilter in FederationPanel so admins can
see and manage rejected peers with delete/re-initiate actions
- I2: Map ensurePeered 'failed' to 'pending' in peer/ensure response
to match spec and client expectations
Adds `auto_accept_peering` integer column (default 1/true) to the
`instance_settings` singleton table, controlling whether this instance
auto-accepts incoming peering requests. Includes generated migration
`0001_clear_earthquake.sql` applied automatically on server boot.
Remove the owner-only gate on POST /api/dm/:id/members. The S2S relay
already accepts member_add from any HMAC-verified peer, and the UI
already shows the add button to all group DM members. Only the
server-side check was blocking non-owners.
Remove all manual migrations — Drizzle-kit now manages schema DDL.
Data-fix migrations have all completed on both instances.
Startup initialization (settings row, worker ID, first admin)
moves to idempotent ensureDefaults().
When a profile_update relay arrives with avatar/banner URLs, download
the files to local storage instead of storing remote absolute URLs.
Falls back to absolute URL on any download failure. Cleans up old
local files when replaced.
sendToFederatedCallUsers sent dm_call_ended/rejected back to the user
who initiated the action. They already disconnected in their click
handler — the redundant event triggered disconnectFn() again, causing
connect and disconnect sounds to play simultaneously.
Added excludeUserId parameter to sendToFederatedCallUsers, used in
handleDmCallEnd and handleDmCallReject Path 2.
sendFederatedCallStart was sending `https://${domain}/livekit` as the
LiveKit URL. The LiveKit SDK requires `wss://` for WebSocket connections.
The caller (local) worked because it gets the URL from config.livekit.url
(wss://). The federated acceptor failed because it used the relay URL
(https://) — the SDK can't connect over HTTPS.
Now uses config.livekit.url directly, falling back to wss:// if unset.
Four fixes addressing the full state management problem:
1. Passive ready handler: no longer auto-connects to LiveKit on
page refresh. Prevents identity conflicts when the same user
has multiple sessions fighting for one LiveKit identity slot.
The user must re-accept to join; state is shown but not acted on.
2. SoundController sync guard: incomingCallLoading/outgoingCallLoading
refs prevent multiple playSound calls during async audio load.
If call is cancelled while sound loads, stops it immediately on
completion. Eliminates the "5 ringtones at once" bug.
3. Host dm_call_accepted broadcasts now include federatedCallId so
all clients (including remote instances) can match the event.
4. Removed all diagnostic console.log statements.
Root cause: sendToFederatedCallUsers used sendToDmMembers when dmChannelId
was set, which broadcast to ALL DM members including the caller's replicated
stub. The caller's multi-instance WS received dm_call_accepted with the
REMOTE instance's dmChannelId, causing token request for a non-existent
channel (403) and preventing the caller from connecting.
Fix 1: sendToFederatedCallUsers always uses ringedUserIds (exact recipients)
instead of sendToDmMembers (all members including caller stub).
Fix 2: dm_call_accepted handler only sets activeDmCall if the client is the
caller (wasOutgoingCall) or already connected to LiveKit. Other instances of
the same user just clear ringing without entering stuck "Connecting..." state.
1. Accept/reject/end from remote instance now resolves federatedId
to local dmChannelId via DB lookup, so the host can find its
VoiceRoom when the event arrives with only a federatedCallId.
Previously silently failed with "No active call" error.
2. Batch all dm_call_incoming state updates into a single
useVoiceStore.setState() call. Prevents SoundController from
starting multiple ringtone instances (async playSound guard
race when 4 separate set() calls each triggered the subscription).
3. Always overwrite callOrigin/federatedCallId (with null if absent)
on dm_call_incoming. Prevents stale values from a previous
federated call routing local accepts to the wrong instance.
Three fixes for multi-instance call state consistency:
1. Client dm_call_accepted handler only auto-connects to LiveKit if
the user was the caller (outgoingCall was set). Other instances of
the same user just clear ringing state without connecting.
2. Server processDmCallAcceptEvent remote path skips duplicate
broadcast when FederatedCallEntry is already active (prevents
state conflicts from host fan-out arriving after local accept).
3. Ready payload handler clears stuck incomingCall when restoring
an already-active call after page refresh.
When findOrCreateDmChannel creates or finds a local channel for a
federatedId that has an active FederatedCallEntry with null dmChannelId,
update the entry. Prevents stale null references for Path B calls.
The outbox worker's event reconstruction whitelist was missing these two
fields, causing read_state_update and dm_close/dm_reopen relay events to
arrive at remote instances with empty payloads and get rejected.
Three bugs that combined to corrupt DM identities during initial sync:
1. Sync endpoint omitted federatedId for group DMs, causing the receiver
to treat them as 1-on-1 DMs and compute wrong pair hashes — creating
phantom channels that collide with real ones.
2. backfillHomeUserId unconditionally overwrote existing homeUserIds,
so a single wrong match would permanently corrupt a user's identity
and cascade to all subsequent lookups.
3. Migration duplicate-stub Criterion 1 ("shared 1-on-1 DM membership")
incorrectly merged different users from the same domain who were
simply having a conversation, destroying one user's identity.
The relay broadcast loop skipped members whose homeInstance matched the
source instance, assuming they already received the message on their home
server. This broke delivery for federated users (e.g. youruser@nova on
orbit) who are actively connected to the remote instance.
Client-side dedup in addRealtimeMessage already handles double delivery
via sourceMessageId cross-matching, making the server-side skip both
unnecessary and harmful.