Reported from a live session: "the calls wouldn't connect because the keys
were changed. it wasn't obvious why the call failed." Confirmed by the
reporter — accepting the new key made everything work.
🔴 **It was not obvious because nothing said it.** The call's signalling
lambda was `runCatching { session?.sendCallSignal(...) }` with no
`onFailure`, so `IdentityChangedException` vanished. That exception is the
send path REFUSING to encrypt to an unreviewed identity — TOFU working
exactly as designed — and swallowing it meant the offer never left while the
screen sat in DIALLING until it timed out.
⚠️ **`describe()` had no case for it either**, so anywhere it WAS caught the
person got "Something went wrong reaching that server." The server was fine.
That sentence sent somebody hunting a network problem that did not exist,
which is the specific thing the neighbouring branches in that function exist
to prevent — they all say what to DO.
⚠️ **And the fix is on a screen the caller is not looking at.** Accepting a
changed key is a banner in the CHAT; somebody calling from the Calls tab
never sees it. So the message names the conversation, not just the problem.
This is a parity gap, not a new idea. iOS has named this on the call path
since it was built — "Their security code changed", with the review opened —
and its comment describes this exact bug: "sent someone hunting network
problems when the actual cause was their contact reinstalling".
`describeFailure` moves to file scope so the mapping can be tested without an
`Application`. It never touched instance state, so that is a move rather than
a rewrite — the alternative was a Robolectric run to assert a string.
Proven by removing the branch: all three tests go red reporting the old
sentence verbatim, "Something went wrong reaching that server."
1196 tests pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>