#881·fonoster

[Feature] TrackCall never exposes hangup cause, even though apiserver already receives and stores it

Author: psandersCreated Sep 7, 2026Updated Sep 7, 2026
Labelsenhancement

Summary

TrackCall cannot tell a client why a call failed, even though Fonoster already receives that information and already stores it.

Problem / Motivation

  • mods/common/src/protos/calls.proto:147-159TrackCallResponse carries only Status status, a 7-value dial-level enum (TRYING, CANCEL, ANSWER, BUSY, PROGRESS, NOANSWER, FAILED). FAILED collapses Asterisk's CHANUNAVAIL and CONGESTION together (mods/apiserver/src/utils/mapDialStatus.ts:21).
  • Meanwhile Routr publishes call-end events carrying a real hangupCause; the apiserver's NATS subscriber already receives them (mods/apiserver/src/index.ts:49), and mods/apiserver/src/events/transformEvent.ts:53-56 already maps hangupCause → status on the way to InfluxDB.
  • A richer vocabulary is already defined in the same proto file — CallStatus (calls.proto:42-53): NORMAL_CLEARING, CALL_REJECTED, UNALLOCATED, NO_USER_RESPONSE, NO_ROUTE_DESTINATION, NO_ANSWER, USER_BUSY, NOT_ACCEPTABLE_HERE, SERVICE_UNAVAILABLE, INVALID_NUMBER_FORMAT.

So the reason exists in-process, at the moment the call ends, in the same service that owns the TrackCall subscriber map (mods/apiserver/src/calls/createTrackCall.ts, subscribed on the calls.track NATS subject defined in mods/apiserver/src/envs.ts:81) — it is simply never routed onto the stream. Clients can only get it by polling GetCall for the CDR after the fact.

Production evidence

From a live apiserver log (2026-08-30), the NATS call-end event already carries the cause, and it goes only to InfluxDB:

received nats event  {"callId":"DGoldhnEZ6enSoxJ2JmzNZBoGiDytSAI","extraHeaders":{},"endTime":"...","hangupCause":"NO_ANSWER"}
writing event to InfluxDB {"name":"cdr","tag":"DGoldhnEZ6...","data":{"endedAt":...,"status":"NO_ANSWER"}}

Proposed Solution

A suggestion, not a prescription:

  1. Add an additive field to TrackCallResponse, e.g. CallStatus hangup_cause = 2 — backward compatible, reuses the existing CallStatus enum.
  2. From the same watchNats callback in mods/apiserver/src/index.ts:49 that writes the CDR, also publish { ref, status } on the calls.track NATS subject (CALLS_TRACK_CALL_SUBJECT, mods/apiserver/src/envs.ts:81). It runs in the same process as the gRPC services, so it reaches createTrackCall's subscriber (mods/apiserver/src/calls/createTrackCall.ts:38-40) directly.
  3. Surface the new field in the SDK — mods/sdk/src/utils.ts:107-124 (dialStatusToString) currently stringifies only the 7 dial statuses.

Dependency to call out

The call-end NATS event arrives with empty extraHeaders (confirmed above: "extraHeaders":{}), so transformEvent assigns it no refmods/apiserver/src/events/transformEvent.ts only sets ref inside the extraHeaders && Object.keys(extraHeaders).length > 0 branch (line 65 onward). Today only the in-process refByCallId cache in mods/apiserver/src/events/createInfluxDbPub.ts:46-63 rescues the correlation. Publishing { ref, status } for step 2 above needs that same correlation, so this issue depends on the ref-correlation problem being solved first (or being solved together with this one). If a sibling issue exists for that ref-correlation gap, please cross-reference it here.

Acceptance Criteria

  • TrackCallResponse carries a hangup-cause field distinguishing at least the causes currently collapsed into FAILED (CHANUNAVAIL vs. CONGESTION), and ideally the fuller CallStatus vocabulary already defined in calls.proto.
  • The apiserver publishes this cause onto the TrackCall stream at call-end time, not only to InfluxDB.
  • The SDK exposes the new field (e.g. alongside/extending dialStatusToString).
  • The ref-correlation dependency for calls with empty extraHeaders is resolved or explicitly tracked as a blocking issue.

Priority

P2