docs(systems): document host_unreachable phase + onPeerDeactivated + sentinel
This commit is contained in:
@@ -1015,6 +1015,21 @@ HTTP handler sites dispatch fire-and-forget (`.catch(log)`) so the response is n
|
||||
|
||||
Concurrent activations for the same peer are deduplicated via an in-flight promise map keyed by `peerId`.
|
||||
|
||||
### onPeerDeactivated
|
||||
|
||||
Mirror of `onPeerActivated` for the transition *out* of `active`. Invoked wherever `federation_peers.status` is written to `unreachable`, `needs_attention`, `rejected`, or `revoked`. Responsibility: sweep `ConnectionManager.federatedCalls` for entries whose `federatedCallHost` matches the deactivated peer and evict them — emitting `dm_call_undeliverable { phase: 'host_unreachable', terminal: true }` to each entry's `ringedUserIds`. See `docs/systems/voice.md` for the client teardown contract.
|
||||
|
||||
**Call sites (exhaustive — grep `onPeerDeactivated(` to audit):**
|
||||
- `utils/federationWorker.ts` handleOutboxDeliveryFailure when status flips to `unreachable`
|
||||
- `utils/federationWorker.ts` auth-failure path when status flips to `needs_attention`
|
||||
- `utils/federationWorker.ts` resolvePendingPeers case `'rejected'`
|
||||
- `routes/federation.ts` admin revoke endpoint
|
||||
- `utils/federationPeering.ts` performHandshake 403 `PEERING_REQUIRES_APPROVAL` path
|
||||
|
||||
Deduplicated by peerId using a **separate** `inFlightDeactivation` map (not shared with activation) so flapping peers retain clean activate-then-deactivate ordering.
|
||||
|
||||
A 30s periodic sentinel in `federationWorker.ts` (`runFederatedCallSentinelTick`) is the backstop — it scans active FederatedCallEntries, compares each host's current peer status against reality, and catches transitions missed by the hook sites.
|
||||
|
||||
#### Peer-state × outbox-enqueue × recovery matrix
|
||||
|
||||
| Status | `queueOutboxEvent` enqueue | Mutation log captures | Recovery on transition to `active` |
|
||||
|
||||
+13
-1
@@ -70,6 +70,7 @@ All `dm_call_*` signaling events (`start`, `accept`, `reject`, `end`) are relaye
|
||||
| `accept` | false | Host → peer fan-out of accept failed; local host call continues. | No state change; info toast. |
|
||||
| `reject` | false | Rejector's relay to host failed OR host's fan-out after a local reject failed; state already cleared. | No state change; info toast. |
|
||||
| `end` | false | Ender's relay to host failed OR host's fan-out after a local end failed; state already cleared. | No state change; info toast. |
|
||||
| `host_unreachable` | true | A FederatedCallEntry's `federatedCallHost` peer transitions out of `active`, OR the 30s sentinel detects a non-active host for an existing entry. | Clear `activeDmCall` + `incomingCall`, disconnect LK, warning toast (*"Call ended — {label} became unreachable."*). |
|
||||
|
||||
**Accept-rollback semantics.** `handleDmCallAccept` Path 2 transitions the `FederatedCallEntry` to active and broadcasts `dm_call_accepted` optimistically so the acceptor's UI flips immediately. If the B→host relay fails, the server clears the entry, fans `dm_call_undeliverable { phase: 'accept', terminal: true }` out to all ringed users on B (via `sendToFederatedCallUsers`), and the client tears its call state back down.
|
||||
|
||||
@@ -77,7 +78,18 @@ All `dm_call_*` signaling events (`start`, `accept`, `reject`, `end`) are relaye
|
||||
|
||||
**Ring-timeout fan-out.** When the host's 60 s ringing timeout fires without an accept, `dm_call_end` is fanned out to all remote peers so stranded Path-A/B ringees on other instances exit their ring state instead of lingering. Registered via `connectionManager.setRingTimeoutFanoutHook` from the WS events module.
|
||||
|
||||
**Remaining edge.** When a non-host participant (Bob on B) ends an active call and the relay to the host (Alice on A) fails, Alice's `activeDmCall` marker lingers until she manually ends — LK `ParticipantDisconnected` tears down her voice UI but does not clear the DM-call marker. This is a host-side cleanup concern, tracked separately.
|
||||
**Remaining edge.** When a non-host participant ends an active call and the relay to the host fails, the host's `activeDmCall` marker lingers until manual end — LK `ParticipantDisconnected` tears down the voice UI but does not clear the DM-call marker on the host side. This is the caller-side mirror of the remote-participant problem and is not covered by the Remote-Participant Host Unreachable Eviction mechanism above (which only reasons about FederatedCallEntry state). Tracked separately.
|
||||
|
||||
### Remote-Participant Host Unreachable Eviction
|
||||
|
||||
When a FederatedCallEntry's `federatedCallHost` becomes unreachable (peer status transitions to `unreachable`, `needs_attention`, `rejected`, or `revoked`), the entry owner evicts the stranded state and notifies its local ringed users with `dm_call_undeliverable { phase: 'host_unreachable', terminal: true }`. Two signals drive the eviction:
|
||||
|
||||
1. **Fast path (`onPeerDeactivated` hook):** every peer-status transition out of `active` invokes `ConnectionManager.evictFederatedCallsForHost(peerOrigin, ...)`. Call sites are listed in the `onPeerDeactivated` docstring (audit via `grep onPeerDeactivated(`).
|
||||
2. **Backstop (30s sentinel):** `runFederatedCallSentinelTick` in `federationWorker.ts` iterates active entries, looks up each distinct `federatedCallHost`'s current peer status, and evicts non-active matches.
|
||||
|
||||
Typical eviction latency is ~90s (time for outbox traffic to fail the unreachable threshold + one sentinel tick). Worst case on an idle instance with no outbox traffic is ~15.5min (health-check cadence + sentinel).
|
||||
|
||||
Covers the ringing and active states on the remote-participant side. The caller-side mirror — host's own `activeDmCall` lingering when its LK room empties silently — is a separate, documented out-of-scope edge.
|
||||
|
||||
### Dual-Path Processing
|
||||
|
||||
|
||||
Reference in New Issue
Block a user