# 09 — Cross-signal identity (calls, DMs, voice routing) > **User theme:** “Calls don’t ring / I’m In Voice alone / DMs fork / voice fails only with people on other homes.” > **Severity:** P0. > **Coupling:** Depends on silent provision (`04`) creating foreign actor ids at all. --- ## How identity is supposed to work (user view) - You have one profile on this device. - People may see slightly different usernames on other signal hosts (suffix / `#prefix` tag) — still you. - Calling or DMing someone you met in any community should reach **them**, not a ghost. - You should never end up with two chat threads that are secretly the same person. - Voice in a shared room should work even if you registered on different home signal servers. --- ## How identity is supposed to work (system) | Id | Meaning | |----|---------| | Home user id | Local profile / NgRx `User.id` | | Foreign actor id | Credential `userId` for that signal URL | | Peer map key | Usually the `oderId` seen on that signal’s presence | | `targetUserId` on WS relay | Must equal callee’s **connected** `oderId` on that server | | DM `conversationId` | Should canonicalize to one thread per human pair on this device | Self-admission for inbound events must accept **all** aliases: home id, entity id, peer id, every valid provisioned credential user id. Outbound delivery must pick a **routable** id (one the signaling server can map to an open connection), not merely the home id stored on a people card. --- ## What is implemented vs claimed | Capability | Status (2026-08-12 tree) | |------------|--------------------------| | Inbound direct-call alias admission + normalize | **Present** — `direct-call-participant-identity.rules.ts` | | DM self-id alias sets | **Present** — `direct-message-identity.rules.ts` (narrower than lesson’s conversation merge) | | Outbound `collectRecipientDeliveryCandidateIds` / `pickRoutableRecipientId` | **Absent** — lesson describes as if shipped | | `DirectCallService.resolveRoutableRecipientId` / `recipientUnreachable` UX | **Absent** | | `resolveDirectConversationId` / `mergeAliasDirectConversations` | **Absent** | | Room join uses `resolveActorUserIdForServer` | **Present** — `room-signaling-connection.ts` | | Initiator election uses per-signal actor id | **Weak / home-biased** — see `06` | **Treat LESSONS entries for outbound call routing and DM canonicalize as specifications of unfinished work**, not as completed history. --- ## Failure modes ### 1 — Outbound call: silent “In Voice” Caller joins call session; `PeerDeliveryService` cannot resolve signaling peer id (home id ≠ connected actor id); ring never sent or wrong `targetUserId`; delivery result ignored. ### 2 — Inbound call: dropped ring (historical) Fixed for admission aliases; still verify e2e `dm-header-call-ring` across secondary signal registration. Do not assume outbound is fixed because inbound is. ### 3 — DM thread fork Incoming messages keyed by foreign actor conversation id → UI shows empty home-id thread; replies land elsewhere. ### 4 — Voice peer election / routing alias miss Home vs actor mismatch → no PC or one-way audio (`06`). ### 5 — People search / friends store home ids only Cards display home identity; without alias expansion at send time, every cross-signal action is fragile. --- ## Key files - `domains/direct-call/` (+ `direct-call-participant-identity.rules.ts`) - `domains/direct-message/` (+ `direct-message-identity.rules.ts`, `PeerDeliveryService`) - `domains/authentication/` credential store + `resolveActorUserIdForServer` - `infrastructure/realtime/signaling-transport-handler.ts` (home vs per-URL credentials) - Lessons (aspirational): outbound routing + DM canonicalize sections in `LESSONS.md` - E2E intent: `e2e/tests/voice/dm-header-call-ring.spec.ts` (scope expand if needed) --- ## Recommended implementation sequence 1. **Instrument** one cross-home repro: log home id, actor ids, peer map keys, `targetUserId`, conversation ids. 2. **Outbound delivery** — implement lesson APIs for real: collect aliases, pick routable, always attempt send, surface unreachable error (stop fake In Voice). 3. **DM canonicalize + merge** — single thread; remap inbound; merge duplicates on load. 4. **Voice initiator / routing** — per-signal local actor id (`06`). 5. Rewrite lesson examples if names differ so future agents don’t hunt phantom files. --- ## Proof of done 1. User1 home A, User2 home B, meet in room on A: DM-header call rings B’s modal; B answers; both hear audio. 2. Same pair: DM replies appear in **one** thread on both clients. 3. People-card call when only shared presence is under actor id: still rings or shows **unreachable** (never silent In Voice). 4. Unit tests for alias collect/pick and conversation merge. 5. E2E cross-signal call + DM (expand scope with user approval for `e2e/`). --- ## Interview choices | Option | Focus first | |--------|-------------| | **A (recommended)** | Outbound routable id + unreachable UX | | **B** | DM canonicalize/merge first | | **C** | Voice initiator per-signal id first | | **D** | Full identity service refactor (large — avoid in emergency) |