Skip to content

feat(ami): M7 CP3 — AMI Originate waits for answer before running the app#159

Merged
ryanmurf merged 1 commit into
masterfrom
feat/m7-cp3-originate-wait-for-answer
Jul 17, 2026
Merged

feat(ami): M7 CP3 — AMI Originate waits for answer before running the app#159
ryanmurf merged 1 commit into
masterfrom
feat/m7-cp3-originate-wait-for-answer

Conversation

@ryanmurf

Copy link
Copy Markdown
Owner

M7 CP3 (flagship) — AMI Originate waits for answer before running the app

handle_originate ran the dialplan app/exten immediately after driver.call()
put the INVITE on the wire — before the far end answered. For a PJSIP origination
the app executed (and the leg usually tore down) before the carrier's 200 OK,
orphaning a delayed answer.

Fix

For a PJSIP Originate, after the INVITE is sent the handler waits (up to the
Originate Timeout) for the store channel to reach Up — set by the 2xx answer
path — before running the app. On rejection/hangup/timeout it abandons the leg
(CANCEL a still-pending INVITE via cancel_or_bye_outbound_leg) without
running the app and reports OriginateResponse: Failure.

Flagship receiver-side proof (isolated offline-carrier harness)

tests/cp3-originate-wait/run.sh — the carrier DELAYS its 200 and tags every
captured datagram phase=pre (before it sent the 200) or phase=post:

PRE-ANSWER SILENCE (only INVITE before the 200, no ACK/BYE/CANCEL/RTP): PASS
POST-ANSWER RUN   (answer ACKed + app BYE after the 200):              PASS
RTP datagrams pre-answer (must be 0):  0

INVITE phase=pre  ... cseq=1 INVITE rel=0.765
SENT-200          ... rel=2.766
ACK    phase=post ... cseq=1 ACK    rel=2.766
BYE    phase=post ... cseq=2 BYE    rel=3.771

Nothing but the INVITE reaches the carrier in the 2-second pre-answer window; the
ACK and the app-driven BYE arrive only after the 200.

Captured RED

Disable the wait (app runs immediately):

PRE-ANSWER SILENCE ... : PASS
POST-ANSWER RUN    ... : FAIL
INVITE phase=pre ... rel=0.776
SENT-200         ... rel=2.776   <- no ACK, no BYE ever follow

The delayed 200 is orphaned (never ACKed) because the app ran and abandoned the
leg before answer → POST-ANSWER assertion RED.

cargo test --workspace --exclude pjsip-shim + clippy … -D warnings green.
Rust 1.97.0. (CP3 is an end-to-end Originate-timing behavior; the harness is its
RED-capable proof.)

… app

`handle_originate` ran the dialplan app/exten IMMEDIATELY after `driver.call()`
put the INVITE on the wire, without waiting for the far end to answer. For a
PJSIP origination that meant the app executed (and the leg often tore down)
before the carrier's 200 OK — emitting activity before answer and orphaning a
delayed 200.

Now, for a PJSIP Originate, after the INVITE is sent the handler waits (up to the
Originate Timeout) for the store channel to reach `Up` — which the 2xx path sets
on answer — before running the app. On rejection/hangup/timeout it abandons the
leg (CANCEL a still-pending INVITE via `cancel_or_bye_outbound_leg`) WITHOUT
running the app and reports OriginateResponse: Failure.

Flagship receiver-side proof: tests/cp3-originate-wait — the offline carrier
DELAYS its 200 and tags every captured datagram phase=pre / phase=post. GREEN:
the pre-answer window carries ONLY the INVITE (no ACK/BYE/CANCEL, no RTP); the
answer is ACKed and the app's BYE arrive phase=post (timeline: INVITE@0.77s,
200@2.77s, ACK@2.77s, BYE@3.77s). Captured RED: disabling the wait -> the app
runs immediately, the delayed 200 is orphaned (no post-answer ACK, no BYE) ->
POST-ANSWER assertion FAIL.
@ryanmurf
ryanmurf merged commit b51a195 into master Jul 17, 2026
3 checks passed
@ryanmurf
ryanmurf deleted the feat/m7-cp3-originate-wait-for-answer branch July 17, 2026 13:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant