[Feature] TrackCall never exposes hangup cause, even though apiserver already receives and stores it
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-159—TrackCallResponsecarries onlyStatus status, a 7-value dial-level enum (TRYING, CANCEL, ANSWER, BUSY, PROGRESS, NOANSWER, FAILED).FAILEDcollapses 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), andmods/apiserver/src/events/transformEvent.ts:53-56already mapshangupCause → statuson 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:
- Add an additive field to
TrackCallResponse, e.g.CallStatus hangup_cause = 2— backward compatible, reuses the existingCallStatusenum. - From the same
watchNatscallback inmods/apiserver/src/index.ts:49that writes the CDR, also publish{ ref, status }on thecalls.trackNATS subject (CALLS_TRACK_CALL_SUBJECT,mods/apiserver/src/envs.ts:81). It runs in the same process as the gRPC services, so it reachescreateTrackCall's subscriber (mods/apiserver/src/calls/createTrackCall.ts:38-40) directly. - 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 ref — mods/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
-
TrackCallResponsecarries a hangup-cause field distinguishing at least the causes currently collapsed intoFAILED(CHANUNAVAIL vs. CONGESTION), and ideally the fullerCallStatusvocabulary already defined incalls.proto. - The apiserver publishes this cause onto the
TrackCallstream 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
extraHeadersis resolved or explicitly tracked as a blocking issue.
Priority
P2
Source: fonoster/fonoster