Skip to content

TELCORE-304: FST coverage for mod_lumenvox CPA/AMD - #632

Open
dev-ryanc wants to merge 4 commits into
telnyx/telephony/deploy-developmentfrom
linear-telcore-304
Open

TELCORE-304: FST coverage for mod_lumenvox CPA/AMD#632
dev-ryanc wants to merge 4 commits into
telnyx/telephony/deploy-developmentfrom
linear-telcore-304

Conversation

@dev-ryanc

Copy link
Copy Markdown
Collaborator

Test-only. Adds FST coverage for the CPA/AMD support in
team-telnyx/mod_lumenvox#3 (TELCORE-304). Touches one file,
tests/unit/switch_mod_lumenvox.c.

Rebased on 1c78225256; no overlap with the RTP/APR work that landed there.

What is covered

Six new cases plus a fix to an existing one:

  • CPA/AMD grammar-URI classification, including case and ?query handling
  • Rejection of mixed CPA+AMD in one recognition
  • The start-input-timers=false path end to end — cause 002, the
    mod_unimrcp sentinel body, and the matching header
  • Real CPA classification from audioHUMAN RESIDENCE asserted from a
    speech sample, which also gives VadSettings.eos_delay_ms its first coverage

Three are regression tests for defects found by inspection: a deferred timeout
consumed by the first interaction and lost to a restart, a deadline surviving
pause, and repeated arming disturbing an armed deadline.

asr_grammar_management now sets start-recognize=false. Loading a grammar
starts recognition (matching mod_unimrcp), which had silently made that test's
bookkeeping assertions depend on server-side ASR provisioning.

Test strength was verified, not assumed

Three of these originally asserted less than their names claimed — they checked
that nothing bad happened without establishing the mechanism was live, so they
passed when the feature was absent. asr_cpa_amd_grammar_classification only
checked that load_grammar returned SUCCESS, which it does for any non-empty
string.

They now drive observable behaviour, and I confirmed it by mutation: stubbing
classify_grammar to return ASR unconditionally makes the classification case
fail, where the previous version passed.

How to run

Not wired into CI — Makefile.am:9-11 excludes it because LumenVox is
unreachable from CI, and every meaningful case is lv_live()-gated. CI still
only exercises the fail-closed path.

export LV_TEST_TARGET=lumenvox-api-service.query.ch1-dev.dev.telnyx.io:5000
export LV_TEST_DEPLOYMENT=2c62306f-5049-4cea-82c9-0e694f79f7f3
export LV_TEST_OPERATOR=00000000-0000-0000-0000-000000000000
export LV_TEST_AUDIO=/path/to/speech.raw   # raw 8 kHz mono S16LE, >7 s

LV_TEST_AUDIO is optional; without it the classification case is skipped. Any
TTS will do — AWS Polly with OutputFormat=pcm, SampleRate=8000.

Last run: 15/15 against ch1-dev, on a core built from this branch's base.

…imer

Adds FST cases for the mod_lumenvox CPA/AMD surface: grammar-URI
classification including case and ?query handling, rejection of mixed
CPA+AMD in one recognition, and the start-input-timers=false path end to
end — cause 002 with the mod_unimrcp sentinel body and the matching
ASR-Completion-Cause header.

Three of them are regression tests for defects found by inspection: the
deferred timeout being consumed by the first interaction and so lost to a
restart, a deadline surviving pause and firing for a recognition no longer
running, and repeated arming disturbing an armed deadline.

asr_grammar_management now sets start-recognize=false. Loading a grammar
starts recognition immediately (matching mod_unimrcp), which would otherwise
make that test's bookkeeping assertions depend on the ASR engine being
provisioned server-side.
…oyment

asr_cpa_classifies_speech feeds real speech through switch_core_asr_feed and
asserts the deployment classifies it: a populated NLSML interpretation rather
than the no-result sentinel, and ASR-Completion-Cause 000.

Gated on LV_TEST_AUDIO (raw 8 kHz mono S16LE) as well as LV_TEST_TARGET,
since no suitable audio ships in the tree. Verified against ch1-dev with a
Polly-generated sample: "Hello?" classifies as HUMAN RESIDENCE at 450 ms of
speech. Confirmed non-vacuous by pointing LV_TEST_AUDIO at a missing file and
watching the case fail.

The assertion is deliberately on the transcript, never on the completion
cause. Every CPA outcome is cause 000 -- including UNKNOWN SILENCE -- so a
test that checked the cause would pass on an empty call.
asr_cpa_classifies_speech now sets cpa-human-business-time and
speech-complete-timeout before loading the grammar, so a field the server
rejects shows up as a failure to classify.

That matters more since rejected requests became non-fatal: a malformed
interaction-create request no longer fails the channel, it logs a warning and
the start times out, so nothing else in the suite would notice a bad param.
speech-complete-timeout in particular reaches CpaSettings via
VadSettings.eos_delay_ms and had no coverage at all.

Not yet exercised — the dev deployment became unreachable before this could
run against it.
All three checked that nothing bad happened without first establishing the
mechanism was live, so each passed when the feature was absent.

asr_cpa_amd_grammar_classification only asserted that load_grammar returned
SUCCESS, which it does for any non-empty string; the classification was stored
and, with start-recognize=false, never read. It would have passed unchanged if
classify_grammar always returned ASR. Classification is only observable
through the mixed-mode guard -- a start succeeds when the enabled grammars
agree and fails when they disagree -- so it now drives three starts chosen so
that losing the URI matching flips the result: canonical plus ?query-suffixed
must start, upper-case AMD against CPA must not, and an ordinary grammar
against CPA must not.

Verified by mutation: stubbing classify_grammar to return ASR unconditionally
makes this case fail, where before it passed.

asr_start_input_timers_is_idempotent asserted that three calls returned
SUCCESS, which every path does including one that re-armed, and then that no
result had arrived within milliseconds of a 5 s deadline. It now uses a 400 ms
deadline, re-arms at 250 ms, and asserts the result still lands before 600 ms
-- a re-arm would push it to ~650 ms.

asr_paused_deadline_does_not_fire could not distinguish a disarmed deadline
from one that was never armed. It now runs a positive control first, on its
own handle, showing the deadline does fire without the pause.

asr_cpa_classifies_speech additionally sets cpa-human-business-time and
speech-complete-timeout, giving VadSettings.eos_delay_ms its first coverage.
@dev-ryanc
dev-ryanc marked this pull request as ready for review August 3, 2026 10:53

@damirn damirn left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

neat!

@tajamulTelnyx tajamulTelnyx left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

5 participants