Soprano: switch to the omnimsg endpoint contract - #3
Open
Hou (SciencePotato) wants to merge 4 commits into
Open
Conversation
added 4 commits
August 24, 2026 09:40
One endpoint for every channel: POST {base}/messages/omnimsg with text/destination/messageTypes/correlationId/shutterMode. Soprano renders voice TTS from the same text, so the text2voice block and the provisioned-source fields go away; destination is E.164 without the leading +. SOPRANO_SHUTTER_MODE opts into Soprano-side shutter for connectivity tests.
QA4 answers 201 with status FILTERED when an account/destination filter stops delivery, on both the omnimsg and the legacy per-channel path. Pin it instead of leaning on the fail-closed default.
Adds an omnimsg payload test per language (endpoint, messageTypes, destination without the leading +, passcode in text) - the rewrite previously rode on suites that never asserted the new shape. Makes the .NET destination null-safe like the other two, and strips every leading + in JavaScript so all three agree. Documents SOPRANO_SHUTTER_MODE as diagnostics-only, since a 2xx with a matching nonce stops SAS falling back, and records that Soprano voice ignores locale.
Two deviations from the CYOT endpoint contract. The type field was parsed and logged but never validated, so a future contract version would have been interpreted as v1; it is now pinned per language. ttlSeconds <= 0 was logged and delivered anyway, which spends a message on a code that can no longer authenticate; all three now return 400 and dispatch nothing. CONTRACT.md said the old behaviour was intended, so it is corrected alongside.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Switches the Soprano adapter to the
omnimsgcontract from Soprano's CYOT integration guide, in all three language implementations.What changed
POST {EPP_PROVIDER_ENDPOINT}/messages/omnimsgreplaces/messages/smsand/messages/voice.text,destination(E.164 with the leading+stripped),messageTypes: ["sms"|"voice"],correlationId,shutterMode.messageType,clientReference, thevoice.text2voiceblock, and theendpoints/sourcesender fields — omnimsg renders the voice TTS from the sametextand takes the sender from the account provisioning.SOPRANO_SOURCE_TYPE,SOPRANO_VOICE_LANGUAGEandSOPRANO_VOICE_GENDERare no longer read.SOPRANO_SHUTTER_MODEapp setting — sendsshutterMode: trueso Soprano processes the request but delivers nothing, for connectivity and credential testing. This is distinct from the pipeline's own evaluation-mode shutter, which still returns before any HTTP call is made.FILTEREDis mapped toFail— see below.parseResponseis unchanged; it already handled{id, destination, status}.Verified end to end against Soprano QA4
Both channels confirmed working on the live QA4 environment, against a destination Soprano confirmed is routable:
201—{"id":400004307033,"destination":"61434024858","status":"ENROUTE"}200/Continue/accepted201—{"id":400004307034,…,"status":"ENROUTE"}200/Continue/acceptedResponse body from the pipeline:
{ "status": "accepted", "outcome": "Continue", "provider": "soprano", "channel": "sms", "providerMessageId": "400004307033", "providerStatus": "ENROUTE", "requestId": "req-..." }Why
FILTEREDis mapped toFailDestinations outside the account's routing come back as
201withstatus: "FILTERED"rather than an error — accepted, then silently dropped, with no reason field. Observed on US destinations and on one AU number, identically on the previous/messages/smspath and with or without a sender field, so it is a routing decision rather than a payload problem. Mapping it explicitly toFaildocuments the behaviour instead of relying on the fail-closed default.For contrast, a genuinely malformed request is reported properly: an invalid destination returns
400witherrorCode 400102andInvalid destination address, which flows through toproviderStatusDescription.Open question for Soprano
Does
FILTEREDever indicate a policy block (blocklisted MSISDN) in production? If so it may belong onBlock/403 rather thanFail/502.Note: Entra ID v2.0 bearer auth is not wired end-to-end — there is no outbound token acquisition, so
oauth2mode fails closed and QA4 runs on theX-MEMS-*headers.Tests
javascript 37 passed, python 24 passed, dotnet 23 passed.