Files
Toju/emergency-fix/09-identity-cross-signal.md
T
myxelium e49b3ec112 chore: dev-stack switches, shared e2e harness, and desktop shell rules
- `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.
2026-08-14 03:19:29 +02:00

114 lines
5.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 09 — Cross-signal identity (calls, DMs, voice routing)
> **User theme:** “Calls dont ring / Im 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 signals presence |
| `targetUserId` on WS relay | Must equal callees **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 lessons 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 dont hunt phantom files.
---
## Proof of done
1. User1 home A, User2 home B, meet in room on A: DM-header call rings Bs 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) |