federation.md — new undeliverable bucket subsection with three-way
classification table, Path A/B semantics, and wire backward-compat note.
voice.md — no_recipient row in failure-surface table.
websocket.md — DmCallUndeliverableReason union updated to include no_recipient.
dm-system.md — cross-reference to voice.md for no_recipient reason.
New subsection under client-federation.md's origin-aware routing section
covering dmAlternatives, failoverDmOriginsFromDisconnected, rekey flow,
trigger points, the intentional cache-flush trade-off, voice-out-of-scope,
no-re-home policy, and the WS routing contract. dm-system.md gets a
one-line cross-reference from the Client routing bullet.
processMemberAddEvent, processMemberRemoveEvent, and processOwnershipTransferEvent inserted system messages unconditionally. Outbox retries and initial-sync replays (triggered whenever an admin re-approves a peering request, which recreates the peer row with lastSyncedAt=0) duplicated the system message on every delivery. Each new snowflake ID exceeded the user's last_read_message_id, flipping the channel back to unread after every deploy.
Processors now short-circuit on a matching (source_instance, source_message_id) row, and persist those fields when inserting. processMemberAddEvent emits the tagged system message in both bootstrap and incremental paths so bootstrap replays don't fall through and insert a second one; the bootstrap's dm_channel_created broadcast carries that message as lastMessage so sidebar previews and unread anchors agree across instances.
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.
Add federation.md section 8b covering dm_close/dm_reopen relay events:
payload, outbound queueing via queueDmCloseRelay, inbound processDmCloseEvent/
processDmReopenEvent handlers (lookup-only identity resolution, silent
no-ops on missing channel/user/membership), and the processCreateEvent
closed-state reopen bug fix.
Update dm-system.md soft-close section with federation behaviour: relay
to peers for both close and reopen, relayed-message reopen in
processCreateEvent, and the federatedId-only guard for legacy DMs.