workspace "Call Translator Architecture" "SIP/PSTN call translation architecture." { model { caller = person "Caller" "Starts a translated call." pstnCallee = person "Callee (PSTN user)" "Answers a normal phone call." voipClient = softwareSystem "VoIP Client" "SIP caller app." "External" businessBackend = softwareSystem "call_translator_backend" "Rails call orchestration backend." "External" { backendRailsApi = container "Rails API" "Device API, callbacks, WebSocket." "Ruby on Rails, Puma, Faraday, ActionCable" { backendJwtAuth = component "Device Auth" "JWT + agreement check." "Api::V2::DevicesController" backendAccountsController = component "Accounts API" "Account and SIP setup." "AccountsController" backendContactsController = component "Contacts API" "Confirmed SIP contacts." "ContactsController" backendDeviceCallEngineController = component "Call Init API" "Starts call sessions." "Devices::CallEngine::CallsController" backendCallEventsController = component "Events API" "Call engine callbacks." "CallEngine::CallsController" backendAccountProvisioning = component "SIP Provisioning" "Requests and stores SIP creds." "Accounts::V2::CreateOperation" backendCallInitialization = component "Call Initialization" "Validates and creates calls." "CallEngine::Calls::InitializeOperation" backendCallEventHandler = component "Event Handler" "Updates calls and transcripts." "CallEngine::Calls::HandleEventOperation" backendCallEngineClient = component "Call Engine Client" "Faraday internal client." "CallEngine::ApiService" backendPersistence = component "ActiveRecord Models" "Calls, accounts, messages." "ActiveRecord" backendBroadcastService = component "Broadcast Service" "Client notifications." "Devices::BroadcastService" backendActionCable = component "Device Channel" "WebSocket stream." "ActionCable" backendSmsService = component "SMS Service" "Confirmation SMS." "Sms::Service" } backendDb = container "Business Backend DB" "Rails data store." "PostgreSQL" { tags "Database" } backendRedis = container "Backend Redis" "Cable and jobs." "Redis" { tags "Database" } backendSidekiq = container "Sidekiq Worker" "Background jobs." "Sidekiq, sidekiq-cron" } sipTrunkProvider = softwareSystem "Outbound SIP Trunk Provider" "PSTN SIP trunk." "External" pstnNetwork = softwareSystem "PSTN / Mobile Network" "Phone network." "External" smsGateway = softwareSystem "EmailLabs SMS Gateway" "Confirmation SMS." "External" azureSpeech = softwareSystem "Azure Speech" "ASR and TTS." "External" translationApi = softwareSystem "Translation API" "Text translation." "External" callTranslator = softwareSystem "Call Translator PoC" "SIP bridge and live translation." { callEngine = container "calltranslator Ktor Call Engine" "Session, ARI, media pipeline." "Kotlin, Ktor, Coroutines" { backendApi = component "Internal Backend API" "Internal HTTP routes." "BackendApiController" apiAuthenticator = component "Bearer Auth" "Token check." "BackendApiAuthenticator" sipCredentialService = component "SIP Credentials" "Generates SIP users." "SipCredentialService" sipCredentialRepository = component "Realtime SIP Repo" "Writes PJSIP rows." "JdbcSipCredentialRepository" sessionCommandService = component "Session Service" "Creates sessions." "SessionCommandService" sessionRequestMapper = component "Request Mapper" "Builds callee endpoint." "SessionRequestMapper" joinTokenService = component "Join Tokens" "Issues and validates tokens." "JoinTokenService" activeSessionRegistry = component "Session Registry" "Active call state." "ActiveSessionRegistry" ariEventStream = component "ARI Events" "WebSocket listener." "AriEventStream" ariEventParser = component "ARI Parser" "Event parser." "AriEventParser" ariCallFlowController = component "ARI Call Flow" "Bridge and originate." "AriCallFlowController" ariGateway = component "ARI Gateway" "ARI HTTP client." "AriHttpClient" mediaTapService = component "Media Taps" "Snoop and external media." "AriMediaTapService" runnerCoordinator = component "Runner Coordinator" "Starts/stops runners." "AriConversationRunnerCoordinator" cleanupService = component "Cleanup Service" "Tears down calls." "AriSessionCleanupService" callerToCalleeRunner = component "Caller -> Callee Runner" "Caller audio path." "ConversationRunner" calleeToCallerRunner = component "Callee -> Caller Runner" "Callee audio path." "ConversationRunner" rtpAudioReceiver = component "RTP Receiver" "Receives ulaw RTP." "RtpAudioReceiver" speechTranslationPipeline = component "Translation Pipeline" "ASR, MT, TTS." "SpeechTranslationPipeline" recognizerManager = component "ASR Manager" "Speech recognition." "RecognizerManager" translationManager = component "MT Manager" "Text translation." "TranslationManager" synthesizerManager = component "TTS Manager" "Speech synthesis." "SynthesizerManager" playbackHandler = component "Playback Handler" "Routes synthesized audio." "SynthesizedPlaybackHandler" audioPlayer = component "ARI Audio Player" "Plays generated audio." "AudioPlayer" backendEventPublisher = component "Event Publisher" "Backend callbacks." "BackendEventPublisher" } asterisk = container "Asterisk PBX" "SIP, ARI, RTP bridge." "Asterisk, PJSIP, ARI, RTP, Docker" { pjsipStack = component "PJSIP Stack" "SIP ingress." "res_pjsip" ingressDialplan = component "Ingress Dialplan" "Extension 700." "extensions.conf" stasisApp = component "Stasis App" "ARI app boundary." "Stasis" callBridge = component "Call Bridge" "Caller/callee bridge." "ARI bridge" mediaTapChannels = component "Media Taps" "Snoop + externalMedia." "ARI" outboundTrunk = component "PSTN Trunk" "Outbound PJSIP trunk." "PJSIP" soundsDirectory = component "Sounds Directory" "Playback files." "/var/lib/asterisk/sounds" } realtimeDb = container "Asterisk Realtime DB" "PJSIP realtime data." "PostgreSQL 16" { tags "Database" } } caller -> voipClient "Audio" "SIP user interface" pstnCallee -> pstnNetwork "Answers and speaks" "PSTN/mobile voice" voipClient -> businessBackend "Creates accounts, fetches" "HTTPS/WebSocket" businessBackend -> callEngine "Creates SIP credentials" "HTTP JSON, bearer token" callEngine -> businessBackend "Transcript" "HTTP JSON callback" businessBackend -> voipClient "Returns generated SIP" "HTTPS" voipClient -> backendRailsApi "JWT auth" "HTTPS/WebSocket" backendRailsApi -> voipClient "Transcript" "HTTPS/WebSocket" backendRailsApi -> backendDb "Persist" "ActiveRecord/PostgreSQL" backendRailsApi -> backendRedis "Uses Redis for" "Redis" backendSidekiq -> backendRedis "Consumes and schedules" "Redis" backendSidekiq -> backendDb "Reads and updates" "ActiveRecord/PostgreSQL" backendRailsApi -> smsGateway "Sends account confirmation" "HTTPS" backendRailsApi -> callEngine "Creates SIP credentials" "HTTP JSON, bearer token" callEngine -> backendRailsApi "Events" "HTTP JSON, bearer token" voipClient -> asterisk "Registers generated SIP" "SIP UDP 15060" voipClient -> asterisk "Audio" "RTP UDP" asterisk -> sipTrunkProvider "Dial PSTN" "SIP/RTP" sipTrunkProvider -> pstnNetwork "Route" "SIP/PSTN interconnect" pstnNetwork -> sipTrunkProvider "Propagates answer, release" "PSTN/SIP interconnect" sipTrunkProvider -> asterisk "Propagates callee answer" "SIP/RTP" pstnNetwork -> pstnCallee "Route" "PSTN/mobile voice" callEngine -> realtimeDb "Dial PSTN" "JDBC" asterisk -> realtimeDb "Dial PSTN" "ODBC realtime" callEngine -> asterisk "Events" "ARI HTTP/WebSocket" asterisk -> callEngine "Events" "ARI WebSocket" asterisk -> callEngine "Bridge + taps" "RTP UDP ulaw" callEngine -> azureSpeech "Audio" "Azure Speech SDK" callEngine -> translationApi "Translate" "HTTPS JSON" businessBackend -> backendApi "Calls internal API" "HTTP JSON, bearer token" backendApi -> businessBackend "Returns internal API" "HTTP JSON" backendAccountsController -> backendJwtAuth "JWT auth" backendContactsController -> backendJwtAuth "JWT auth" backendDeviceCallEngineController -> backendJwtAuth "JWT auth" backendAccountsController -> backendAccountProvisioning "Creates or updates" backendContactsController -> backendPersistence "Queries confirmed contacts" backendDeviceCallEngineController -> backendCallInitialization "Initializes a translated" backendCallEventsController -> backendCallEventHandler "Events" voipClient -> backendAccountsController "Creates/reads accounts through" "HTTPS" voipClient -> backendContactsController "Fetches confirmed SIP" "HTTPS" voipClient -> backendDeviceCallEngineController "Requests translated call" "HTTPS" backendAccountsController -> voipClient "Returns account creation/show" "HTTPS" backendContactsController -> voipClient "Returns contact list" "HTTPS" backendDeviceCallEngineController -> voipClient "Return join data" "HTTPS" backendAccountProvisioning -> backendCallEngineClient "Requests generated SIP" backendCallEngineClient -> callEngine "Calls call-engine internal" "HTTP JSON, bearer token" callEngine -> backendCallEngineClient "Returns call-engine API" "HTTP JSON" backendAccountProvisioning -> backendPersistence "Persist" backendAccountProvisioning -> backendSmsService "Sends confirmation code" backendSmsService -> smsGateway "Sends SMS" "HTTPS" backendCallInitialization -> backendPersistence "Validate" backendCallInitialization -> backendCallEngineClient "Creates call-engine session" backendCallEventHandler -> backendPersistence "Locks call, validates" backendCallEventHandler -> backendBroadcastService "Transcript" backendBroadcastService -> backendActionCable "Notify" backendActionCable -> backendRedis "Publishes/subscribes ActionCable messages" "Redis" backendActionCable -> voipClient "Transcript" "WebSocket" backendPersistence -> backendDb "Reads and writes" "ActiveRecord/PostgreSQL" callEngine -> backendCallEventsController "Transcript" "HTTP JSON, bearer token" backendApi -> apiAuthenticator "JWT auth" backendApi -> sipCredentialService "Create SIP creds" backendApi -> sessionCommandService "Delegates /internal/sessions commands" sipCredentialService -> sipCredentialRepository "Persist" sipCredentialRepository -> realtimeDb "Dial PSTN" "JDBC" sessionCommandService -> sessionRequestMapper "Return SIP creds" sessionCommandService -> activeSessionRegistry "Creates, reads," sessionCommandService -> joinTokenService "Generates expiring join" sessionCommandService -> ariCallFlowController "Delete session" ariEventStream -> ariEventParser "Parses inbound ARI" ariEventParser -> ariCallFlowController "Events" ariCallFlowController -> joinTokenService "Validate" ariCallFlowController -> activeSessionRegistry "Binds caller/callee channels" ariCallFlowController -> ariGateway "Dial PSTN" ariCallFlowController -> mediaTapService "Starts bidirectional taps" ariCallFlowController -> cleanupService "Cleans up failed" ariCallFlowController -> backendEventPublisher "Events" mediaTapService -> ariGateway "Creates snoop channels" mediaTapService -> activeSessionRegistry "Stores tap bridge" mediaTapService -> runnerCoordinator "Launches one runner" runnerCoordinator -> callerToCalleeRunner "Runs caller-to-callee media" runnerCoordinator -> calleeToCallerRunner "Runs callee-to-caller media" cleanupService -> ariGateway "Hangs up channels" cleanupService -> runnerCoordinator "Stops conversation runners" cleanupService -> activeSessionRegistry "Removes active session" cleanupService -> backendEventPublisher "Events" callerToCalleeRunner -> rtpAudioReceiver "Audio" calleeToCallerRunner -> rtpAudioReceiver "Audio" callerToCalleeRunner -> speechTranslationPipeline "Audio" calleeToCallerRunner -> speechTranslationPipeline "Audio" speechTranslationPipeline -> recognizerManager "Audio" speechTranslationPipeline -> translationManager "Translate" speechTranslationPipeline -> synthesizerManager "Synthesize" speechTranslationPipeline -> backendEventPublisher "Transcript" speechTranslationPipeline -> playbackHandler "Registers synthesis request" playbackHandler -> audioPlayer "Audio" audioPlayer -> asterisk "Persist" "Filesystem + ARI HTTP" recognizerManager -> azureSpeech "Audio" "Azure Speech SDK" translationManager -> translationApi "Requests translation" "HTTPS JSON" synthesizerManager -> azureSpeech "Requests streaming text-to-speech" "Azure Speech SDK" backendEventPublisher -> businessBackend "Events" "HTTP JSON" asterisk -> ariEventStream "Events" "ARI WebSocket" ariGateway -> asterisk "Sends ARI HTTP" "ARI HTTP" asterisk -> rtpAudioReceiver "Audio" "RTP UDP ulaw" pjsipStack -> realtimeDb "Looks up caller" "ODBC realtime" pjsipStack -> ingressDialplan "Route" "Asterisk dialplan" ingressDialplan -> stasisApp "Starts Stasis" stasisApp -> ariEventStream "Channel left" "ARI WebSocket" ariGateway -> stasisApp "Controls channels that" "ARI HTTP" ariGateway -> callBridge "Creates bridge, answers" "ARI HTTP" callBridge -> outboundTrunk "Dial PSTN" "PJSIP" outboundTrunk -> sipTrunkProvider "Dial PSTN" "SIP/RTP" callBridge -> mediaTapChannels "Source channels are" "ARI snoop" mediaTapChannels -> rtpAudioReceiver "Audio" "RTP UDP" audioPlayer -> soundsDirectory "Creates and deletes" "Filesystem" soundsDirectory -> callBridge "Provides sound: media" "Asterisk media" } views { systemContext callTranslator "SystemContext" { include * autolayout lr description "External people and systems involved in a translated PSTN call." } container callTranslator "Containers" { include * autolayout lr description "Runtime containers for the proof of concept and the protocols between them." } component callEngine "CallEngineComponents" { include * autolayout lr description "Major Kotlin/Ktor call engine components: internal API, session/token state, ARI call control, media taps, conversation pipeline, playback, and backend callbacks." } component asterisk "AsteriskComponents" { include * autolayout lr description "Asterisk-side components responsible for SIP ingress, realtime PJSIP lookup, Stasis/ARI control, bridges, media taps, and outbound PSTN trunking." } container businessBackend "BusinessBackendContainers" { include * autolayout lr description "Rails backend runtime containers discovered from /Users/michal/RubymineProjects/call-translator-backend: Rails API, PostgreSQL, Redis, and Sidekiq." } component backendRailsApi "BusinessBackendComponents" { include * autolayout lr description "Rails backend components for device authentication, account/SIP provisioning, call initialization, call-engine event handling, persistence, SMS confirmation, and ActionCable broadcasts." } dynamic callTranslator "PstnSessionSetup" "Business backend creates a PSTN call session; the VoIP client joins by SIP; the Ktor engine validates the join and originates the PSTN callee leg." { voipClient -> businessBackend "Dial PSTN" businessBackend -> callEngine "Create session" callEngine -> businessBackend "Return join data" businessBackend -> voipClient "Return join details" voipClient -> asterisk "REGISTER" asterisk -> realtimeDb "JWT auth" voipClient -> asterisk "INVITE 700" asterisk -> callEngine "StasisStart" callEngine -> asterisk "Dial PSTN" asterisk -> sipTrunkProvider "Dial PSTN" sipTrunkProvider -> pstnNetwork "Route" pstnNetwork -> pstnCallee "Route" pstnCallee -> pstnNetwork "Answer" asterisk -> callEngine "StasisStart" callEngine -> asterisk "Answer call" callEngine -> businessBackend "Events" autolayout lr } dynamic callTranslator "BidirectionalMediaTranslation" "After the bridge is up, Asterisk sends two media taps to the Ktor engine; each direction is recognized, translated, synthesized, and played to the opposite participant." { callEngine -> asterisk "Create caller" asterisk -> callEngine "Audio" callEngine -> azureSpeech "Recognize" callEngine -> translationApi "Translate" callEngine -> azureSpeech "Synthesize" callEngine -> asterisk "Audio" asterisk -> callEngine "Audio" callEngine -> azureSpeech "Recognize" callEngine -> translationApi "Translate" callEngine -> azureSpeech "Synthesize" callEngine -> asterisk "Audio" callEngine -> businessBackend "Transcript" autolayout lr } dynamic callTranslator "SessionCleanup" "Any hangup, channel destruction, failure, or explicit backend session deletion cleans up ARI resources and active engine state." { businessBackend -> callEngine "Delete session" callEngine -> asterisk "Cleanup" callEngine -> asterisk "Cleanup" pstnCallee -> pstnNetwork "Cleanup" pstnNetwork -> sipTrunkProvider "Release call" sipTrunkProvider -> asterisk "Dial PSTN" asterisk -> callEngine "Channel left" callEngine -> asterisk "Cleanup" callEngine -> businessBackend "Events" autolayout lr } dynamic callEngine "CallEngineCredentialAndPstnSessionInternals" "Ktor call engine component interactions for SIP credential provisioning, PSTN session creation, inbound join validation, callee origination, and started callback publishing." { businessBackend -> backendApi "Create SIP creds" backendApi -> apiAuthenticator "JWT auth" backendApi -> sipCredentialService "Create or fetch" sipCredentialService -> sipCredentialRepository "Dial PSTN" sipCredentialRepository -> realtimeDb "Persist" businessBackend -> backendApi "Create session" backendApi -> sessionCommandService "Create active translator" sessionCommandService -> sessionRequestMapper "Dial PSTN" sessionCommandService -> activeSessionRegistry "Create session state" sessionCommandService -> joinTokenService "Generate expiring join" backendApi -> businessBackend "Return join data" asterisk -> ariEventStream "StasisStart" ariEventStream -> ariEventParser "Parse ARI JSON" ariEventParser -> ariCallFlowController "Events" ariCallFlowController -> joinTokenService "Validate" ariCallFlowController -> activeSessionRegistry "Bind caller channel" ariCallFlowController -> ariGateway "Answer caller, create" ariGateway -> asterisk "ARI answer/bridge/originate commands" ariCallFlowController -> ariGateway "Dial PSTN" asterisk -> ariEventStream "StasisStart" ariCallFlowController -> ariGateway "Answer call" ariCallFlowController -> mediaTapService "Bridge + taps" ariCallFlowController -> backendEventPublisher "Events" backendEventPublisher -> businessBackend "Events" autolayout lr } dynamic callEngine "CallEngineMediaTranslationInternals" "Ktor call engine component interactions for the two directional RTP translation runners." { ariCallFlowController -> mediaTapService "Bridge + taps" mediaTapService -> ariGateway "Create caller snoop" mediaTapService -> ariGateway "Create callee snoop" ariGateway -> asterisk "Install snoop/external-media channels" mediaTapService -> activeSessionRegistry "Store tap bridge" mediaTapService -> runnerCoordinator "Launch caller-to-callee" asterisk -> rtpAudioReceiver "Audio" callerToCalleeRunner -> speechTranslationPipeline "Audio" speechTranslationPipeline -> recognizerManager "Recognize" recognizerManager -> azureSpeech "Audio" speechTranslationPipeline -> translationManager "Translate" translationManager -> translationApi "Request translation" speechTranslationPipeline -> synthesizerManager "Synthesize" synthesizerManager -> azureSpeech "Synthesize" speechTranslationPipeline -> playbackHandler "Register callee playback" playbackHandler -> audioPlayer "Audio" audioPlayer -> asterisk "Persist" asterisk -> rtpAudioReceiver "Audio" calleeToCallerRunner -> speechTranslationPipeline "Audio" speechTranslationPipeline -> recognizerManager "Recognize" speechTranslationPipeline -> translationManager "Translate" speechTranslationPipeline -> synthesizerManager "Synthesize" speechTranslationPipeline -> playbackHandler "Register caller playback" playbackHandler -> audioPlayer "Audio" speechTranslationPipeline -> backendEventPublisher "Transcript" backendEventPublisher -> businessBackend "Events" autolayout lr } dynamic callEngine "CallEngineCleanupInternals" "Ktor call engine component interactions for hangup, failure, and explicit backend session deletion cleanup." { businessBackend -> backendApi "Delete session" backendApi -> sessionCommandService "Delete active session" sessionCommandService -> activeSessionRegistry "Transition session" sessionCommandService -> ariCallFlowController "End session" asterisk -> ariEventStream "Channel left" ariEventStream -> ariEventParser "Events" ariEventParser -> ariCallFlowController "Events" ariCallFlowController -> activeSessionRegistry "Validate" ariCallFlowController -> cleanupService "Cleanup" cleanupService -> ariGateway "Cleanup" cleanupService -> ariGateway "Cleanup" cleanupService -> runnerCoordinator "Stop conversation runners" cleanupService -> activeSessionRegistry "Remove session indexes" cleanupService -> backendEventPublisher "Events" backendEventPublisher -> businessBackend "Events" autolayout lr } dynamic backendRailsApi "BusinessBackendAccountProvisioning" "Rails account creation requests generated SIP credentials from the Ktor call engine and stores them with the caller account." { voipClient -> backendAccountsController "Create account" backendAccountsController -> backendJwtAuth "JWT auth" backendAccountsController -> backendAccountProvisioning "Create or update account" backendAccountProvisioning -> backendCallEngineClient "Create SIP credentials" backendCallEngineClient -> callEngine "Create SIP creds" callEngine -> backendCallEngineClient "Return join data" backendAccountProvisioning -> backendPersistence "Persist" backendPersistence -> backendDb "Persist" backendAccountProvisioning -> backendSmsService "Send confirmation code" backendSmsService -> smsGateway "POST SMS request" backendAccountsController -> voipClient "Return account creation" autolayout lr } dynamic backendRailsApi "BusinessBackendCallInitialization" "Rails validates a client call request, asks the Ktor call engine for a session, persists the call, and returns SIP join details to the VoIP client." { voipClient -> backendDeviceCallEngineController "Init call" backendDeviceCallEngineController -> backendJwtAuth "JWT auth" backendDeviceCallEngineController -> backendCallInitialization "Initialize translated call" backendCallInitialization -> backendPersistence "Validate" backendPersistence -> backendDb "Read identities_accounts, calls" backendCallInitialization -> backendCallEngineClient "Create call-engine session" backendCallEngineClient -> callEngine "Create session" callEngine -> backendCallEngineClient "Return join data" backendCallInitialization -> backendPersistence "Return join data" backendPersistence -> backendDb "Persist" backendDeviceCallEngineController -> voipClient "Return join data" autolayout lr } dynamic backendRailsApi "BusinessBackendCallEventHandling" "Rails handles call-engine callbacks, updates call state, persists transcripts, adjusts remaining time, and pushes notifications to the client." { callEngine -> backendCallEventsController "Post event" backendCallEventsController -> backendCallEventHandler "Validate" backendCallEventHandler -> backendPersistence "Transcript" backendPersistence -> backendDb "Persist" backendCallEventHandler -> backendBroadcastService "Transcript" backendBroadcastService -> backendActionCable "Notify" backendActionCable -> backendRedis "Publish ActionCable message" backendActionCable -> voipClient "Notify" autolayout lr } styles { element "Person" { shape Person background #08427b color #ffffff } element "Software System" { background #1168bd color #ffffff } element "Container" { background #438dd5 color #ffffff } element "Component" { background #85bbf0 color #000000 } element "Database" { shape Cylinder background #2f95c8 color #ffffff } element "External" { background #999999 color #ffffff } } } }