- `LIVE_RELOAD=false npm run dev` keeps the renderer alive across a machine suspend; the reload client otherwise destroys the session under test. - `dev-peer.sh` plus a separate userdata dir runs a second local peer. - `tools/voice-probe.js` samples peer state and RTP counters from a live window, persisting to localStorage so a renderer reload cannot erase it. - e2e helpers for voice pairs, peer-role election, and a TURN relay. - Electron single-instance and dev-client-load decisions move into rules files with colocated specs.
5.1 KiB
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 /
#prefixtag) — 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 +resolveActorUserIdForServerinfrastructure/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
- Instrument one cross-home repro: log home id, actor ids, peer map keys,
targetUserId, conversation ids. - Outbound delivery — implement lesson APIs for real: collect aliases, pick routable, always attempt send, surface unreachable error (stop fake In Voice).
- DM canonicalize + merge — single thread; remap inbound; merge duplicates on load.
- Voice initiator / routing — per-signal local actor id (
06). - Rewrite lesson examples if names differ so future agents don’t hunt phantom files.
Proof of done
- User1 home A, User2 home B, meet in room on A: DM-header call rings B’s modal; B answers; both hear audio.
- Same pair: DM replies appear in one thread on both clients.
- People-card call when only shared presence is under actor id: still rings or shows unreachable (never silent In Voice).
- Unit tests for alias collect/pick and conversation merge.
- 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) |