2026-08-26 06:41:07 +08:00
package orchestration
import (
"context"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
2026-08-27 21:02:37 +08:00
"log/slog"
2026-08-26 06:41:07 +08:00
"time"
"data-terminal-cloud/internal/billing"
"data-terminal-cloud/internal/conversation"
"data-terminal-cloud/internal/outbox"
"data-terminal-cloud/internal/ratelimit"
"data-terminal-cloud/internal/voiceprovider"
"github.com/google/uuid"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgxpool"
)
var (
ErrProviderTaskNotFound = errors . New ( "provider task was not found" )
ErrProviderEventConflict = errors . New ( "provider event key was reused with different content" )
ErrProviderRateLimited = errors . New ( "provider start rate limit reached" )
ErrProviderCapacity = errors . New ( "provider active task capacity reached" )
)
type providerStartRateLimiter interface {
Allow ( context . Context , string , string ) ( ratelimit . Result , error )
}
type StartProcessor struct {
pool * pgxpool . Pool
provider voiceprovider . Client
health * ProviderHealth
initialLeaseTTL time . Duration
startLimiter providerStartRateLimiter
maxActiveTasks int
now func () time . Time
}
// ConfigureStartProtection enables the provider-wide RPS bucket and durable
// active-task capacity reservation used by every worker replica.
func ( p * StartProcessor ) ConfigureStartProtection ( limiter providerStartRateLimiter , maxActiveTasks int ) error {
if limiter == nil || maxActiveTasks <= 0 {
return errors . New ( "provider start limiter and positive active task capacity are required" )
}
p . startLimiter = limiter
p . maxActiveTasks = maxActiveTasks
return nil
}
func NewStartProcessor ( pool * pgxpool . Pool , provider voiceprovider . Client , health * ProviderHealth , initialLeaseTTL time . Duration ) ( * StartProcessor , error ) {
if pool == nil || provider == nil || health == nil || initialLeaseTTL <= 0 {
return nil , errors . New ( "database pool, voice provider client, provider health and positive lease TTL are required" )
}
return & StartProcessor { pool : pool , provider : provider , health : health , initialLeaseTTL : initialLeaseTTL , now : time . Now }, nil
}
func ( p * StartProcessor ) Handle ( ctx context . Context , event outbox . Event ) error {
var request voiceprovider . StartRequest
var conversationStatus conversation . Status
var taskStatus , tenantID , providerName string
err := p . pool . QueryRow ( ctx , `
SELECT c.public_id::text, c.status, r.provider_task_ref, r.room_id,
r.device_rtc_user_id, r.agent_rtc_user_id, r.status,cs.sanitized_summary,
c.engine_mode,c.tenant_id::text,r.provider,cs.prompt_bundle_version,
COALESCE(bundle.public_id::text,''),COALESCE(bundle.payload->>'persona_version_id',''),
COALESCE(bundle.payload->>'voice_version_id',''),COALESCE(bundle.payload->>'model_version_id',''),
COALESCE(bundle.payload->>'routing_version_id',''),COALESCE(persona.payload->>'system_prompt',''),
COALESCE(persona.payload->>'tone',''),COALESCE(voice.payload->>'voice_resource_ref',''),
COALESCE(model.payload->>'model_id','')
FROM conversation c JOIN rtc_task r ON r.conversation_id=c.id
JOIN context_snapshot cs ON cs.id=c.context_snapshot_id AND cs.tenant_id=c.tenant_id
LEFT JOIN runtime_config_version bundle
ON bundle.tenant_id=c.tenant_id AND bundle.config_kind='AGENT_BUNDLE'
AND cs.prompt_bundle_version='cfg:' || bundle.public_id::text
LEFT JOIN runtime_config_version persona
ON persona.tenant_id=bundle.tenant_id AND persona.config_kind='PERSONA'
AND persona.public_id::text=bundle.payload->>'persona_version_id'
LEFT JOIN runtime_config_version voice
ON voice.tenant_id=bundle.tenant_id AND voice.config_kind='VOICE'
AND voice.public_id::text=bundle.payload->>'voice_version_id'
LEFT JOIN runtime_config_version model
ON model.tenant_id=bundle.tenant_id AND model.config_kind='MODEL'
AND model.public_id::text=bundle.payload->>'model_version_id'
WHERE c.id=$1::uuid` , event . AggregateID ,
). Scan ( & request . ConversationID , & conversationStatus , & request . TaskRef , & request . RoomID ,
& request . DeviceUserID , & request . AgentUserID , & taskStatus , & request . ContextSummary , & request . EngineMode , & tenantID , & providerName ,
& request . PromptBundleVersion , & request . AgentBundleVersion , & request . PersonaVersion , & request . VoiceVersion ,
& request . ModelVersion , & request . RoutingVersion , & request . SystemPrompt , & request . PersonaTone ,
& request . VoiceResourceRef , & request . ModelResourceRef )
if errors . Is ( err , pgx . ErrNoRows ) {
return ErrProviderTaskNotFound
}
if err != nil {
return fmt . Errorf ( "read provider start task: %w" , err )
}
if conversationStatus != conversation . StatusStartingAgent {
return nil
}
if taskStatus != "STARTING" && taskStatus != "UNKNOWN" {
return fmt . Errorf ( "provider task status %s cannot be started" , taskStatus )
}
allowed , err := p . health . AllowStart ( ctx , tenantID , providerName )
if err != nil {
return fmt . Errorf ( "read provider health: %w" , err )
}
if ! allowed {
return outbox . Retryable ( ErrProviderUnavailable )
}
var response voiceprovider . StartResponse
if taskStatus == "UNKNOWN" {
queryResult , queryErr := p . provider . Query ( ctx , voiceprovider . QueryRequest {
TaskRef : request . TaskRef , ConversationID : request . ConversationID ,
})
switch {
case queryErr == nil && queryResult . Status == "ACTIVE" :
response = voiceprovider . StartResponse { TaskRef : queryResult . TaskRef , Status : queryResult . Status }
case queryErr == nil && queryResult . Status == "STARTING" :
_ , _ = p . pool . Exec ( ctx , `UPDATE rtc_task SET status='STARTING',last_checked_at=$2,updated_at=$2 WHERE conversation_id=$1::uuid` , event . AggregateID , p . now (). UTC ())
return outbox . Retryable ( errors . New ( "provider start is still pending confirmation" ))
case queryErr == nil :
return fmt . Errorf ( "provider query returned unsupported start reconciliation status %s" , queryResult . Status )
case ! voiceprovider . IsNotFound ( queryErr ):
if voiceprovider . IsTemporary ( queryErr ) {
return outbox . Retryable ( fmt . Errorf ( "query unknown provider start before retry: %w" , queryErr ))
}
return fmt . Errorf ( "query unknown provider start before retry: %w" , queryErr )
}
// A provider-confirmed 404 is the only UNKNOWN case in which Start may
// safely be sent again. The same stable task reference is retained.
}
newReservation := false
if response . Status == "" && p . maxActiveTasks > 0 {
newReservation , err = p . reserveProviderCapacity ( ctx , event . AggregateID , providerName )
if err != nil {
return err
}
}
if response . Status == "" && p . startLimiter != nil {
limitResult , limitErr := p . startLimiter . Allow ( ctx , "provider_start" , providerName )
if limitErr != nil {
return fmt . Errorf ( "apply provider start rate limit: %w" , limitErr )
}
if ! limitResult . Allowed {
return outbox . Retryable ( fmt . Errorf ( "%w: retry after %s" , ErrProviderRateLimited , limitResult . RetryAfter ))
}
}
if response . Status == "" && ! newReservation {
_ , err = p . pool . Exec ( ctx , `
UPDATE rtc_task SET start_attempts=start_attempts+1, last_checked_at=$2, updated_at=$2
WHERE conversation_id=$1::uuid` , event . AggregateID , p . now (). UTC ())
if err != nil {
return fmt . Errorf ( "record provider start attempt: %w" , err )
}
}
if response . Status == "" {
response , err = p . provider . Start ( ctx , request )
if err != nil {
if voiceprovider . IsTemporary ( err ) {
_ , _ = p . pool . Exec ( ctx , `UPDATE rtc_task SET status='UNKNOWN', updated_at=$2 WHERE conversation_id=$1::uuid AND status IN ('STARTING','UNKNOWN')` , event . AggregateID , p . now (). UTC ())
if healthErr := p . health . RecordFailure ( ctx , tenantID , providerName , "PROVIDER_START_TEMPORARY" ); healthErr != nil {
return fmt . Errorf ( "record provider start failure: %v; provider error: %w" , healthErr , err )
}
return outbox . Retryable ( err )
}
2026-08-27 21:02:37 +08:00
slog . ErrorContext ( ctx , "provider voice chat start rejected" ,
"provider" , providerName ,
"error" , err ,
)
2026-08-26 06:41:07 +08:00
return p . failRejectedStart ( ctx , event . AggregateID , tenantID , "provider_start_rejected" , "provider permanently rejected voice chat start" )
}
}
if err := p . health . RecordSuccess ( ctx , tenantID , providerName ); err != nil {
return fmt . Errorf ( "record provider start success: %w" , err )
}
if response . Status != "STARTING" && response . Status != "ACTIVE" {
return p . failRejectedStart ( ctx , event . AggregateID , tenantID , "provider_start_invalid_response" , "provider returned an unsupported voice chat start status" )
}
now := p . now (). UTC ()
if response . Status == "STARTING" {
_ , err = p . pool . Exec ( ctx , `
UPDATE rtc_task SET status=CASE WHEN status='ACTIVE' THEN 'ACTIVE' ELSE 'STARTING' END,
last_checked_at=$2, updated_at=$2 WHERE conversation_id=$1::uuid` , event . AggregateID , now )
if err != nil {
return fmt . Errorf ( "record provider start response: %w" , err )
}
return nil
}
tx , err := p . pool . BeginTx ( ctx , pgx . TxOptions {})
if err != nil {
return err
}
defer tx . Rollback ( context . Background ())
leaseExpiresAt := now . Add ( p . initialLeaseTTL )
if _ , err := tx . Exec ( ctx , `UPDATE rtc_task SET status='ACTIVE', lease_expires_at=$2,
last_checked_at=$3, updated_at=$3 WHERE conversation_id=$1::uuid` , event . AggregateID , leaseExpiresAt , now ); err != nil {
return fmt . Errorf ( "record provider active response: %w" , err )
}
tag , err := tx . Exec ( ctx , `UPDATE conversation SET status='ACTIVE', started_at=COALESCE(started_at,$2),
lease_expires_at=$3, version=version+1, updated_at=$2
WHERE id=$1::uuid AND status='STARTING_AGENT'` , event . AggregateID , now , leaseExpiresAt )
if err != nil {
return fmt . Errorf ( "reconcile active conversation: %w" , err )
}
if tag . RowsAffected () == 1 {
if _ , err := tx . Exec ( ctx , `INSERT INTO conversation_state_history (
conversation_id, from_status, to_status, reason, source
) VALUES ($1::uuid, 'STARTING_AGENT', 'ACTIVE', 'provider idempotent start response confirmed active task', 'worker')` , event . AggregateID ); err != nil {
return err
}
}
return tx . Commit ( ctx )
}
func ( p * StartProcessor ) failRejectedStart ( ctx context . Context , conversationID , tenantID , failureCode , reason string ) error {
tx , err := p . pool . BeginTx ( ctx , pgx . TxOptions {})
if err != nil {
return err
}
defer tx . Rollback ( context . Background ())
now := p . now (). UTC ()
tag , err := tx . Exec ( ctx , `
UPDATE conversation SET status='FAILED',ended_at=$2,lease_expires_at=NULL,
failure_code=$3,version=version+1,updated_at=$2
WHERE id=$1::uuid AND status='STARTING_AGENT'` , conversationID , now , failureCode )
if err != nil {
return fmt . Errorf ( "fail permanently rejected provider start: %w" , err )
}
if tag . RowsAffected () == 0 {
return tx . Commit ( ctx )
}
if _ , err = tx . Exec ( ctx , `UPDATE rtc_task SET status='FAILED',lease_expires_at=NULL,
last_checked_at=$2,updated_at=$2 WHERE conversation_id=$1::uuid` , conversationID , now ); err != nil {
return fmt . Errorf ( "fail rejected provider RTC task: %w" , err )
}
if _ , err = tx . Exec ( ctx , `INSERT INTO conversation_state_history (
conversation_id,from_status,to_status,reason,source
) VALUES ($1::uuid,'STARTING_AGENT','FAILED',$2,'worker')` , conversationID , reason ); err != nil {
return fmt . Errorf ( "record rejected provider start state: %w" , err )
}
detail , _ := json . Marshal ( map [ string ] string { "failure_code" : failureCode })
if _ , err = tx . Exec ( ctx , `INSERT INTO audit_event (
tenant_id,actor_type,action,resource_type,resource_id,purpose,result,detail
) VALUES ($1::uuid,'system','conversation.agent_start_failed','conversation',$2::uuid,
'provider_start','failure',$3::jsonb)` , tenantID , conversationID , detail ); err != nil {
return fmt . Errorf ( "audit rejected provider start: %w" , err )
}
return tx . Commit ( ctx )
}
func ( p * StartProcessor ) reserveProviderCapacity ( ctx context . Context , conversationID , providerName string ) ( bool , error ) {
tx , err := p . pool . BeginTx ( ctx , pgx . TxOptions {})
if err != nil {
return false , err
}
defer tx . Rollback ( context . Background ())
if _ , err = tx . Exec ( ctx , `SELECT pg_advisory_xact_lock(hashtextextended($1,0))` , "provider-capacity:" + providerName ); err != nil {
return false , fmt . Errorf ( "lock provider capacity: %w" , err )
}
var attempts int
if err = tx . QueryRow ( ctx , `SELECT start_attempts FROM rtc_task WHERE conversation_id=$1::uuid FOR UPDATE` , conversationID ). Scan ( & attempts ); err != nil {
return false , fmt . Errorf ( "read provider capacity reservation: %w" , err )
}
if attempts > 0 {
return false , tx . Commit ( ctx )
}
var active int
if err = tx . QueryRow ( ctx , `
SELECT count(*) FROM rtc_task
WHERE provider=$1 AND (
status IN ('ACTIVE','STOPPING') OR
(status IN ('STARTING','UNKNOWN') AND start_attempts>0)
)` , providerName ). Scan ( & active ); err != nil {
return false , fmt . Errorf ( "count provider capacity: %w" , err )
}
if active >= p . maxActiveTasks {
return false , outbox . Retryable ( fmt . Errorf ( "%w: provider=%s active=%d maximum=%d" , ErrProviderCapacity , providerName , active , p . maxActiveTasks ))
}
if _ , err = tx . Exec ( ctx , `UPDATE rtc_task SET start_attempts=1,last_checked_at=$2,updated_at=$2 WHERE conversation_id=$1::uuid` , conversationID , p . now (). UTC ()); err != nil {
return false , fmt . Errorf ( "reserve provider capacity: %w" , err )
}
if err = tx . Commit ( ctx ); err != nil {
return false , err
}
return true , nil
}
type StopProcessor struct {
pool * pgxpool . Pool
provider voiceprovider . Client
health * ProviderHealth
outbox * outbox . Store
now func () time . Time
}
func NewStopProcessor ( pool * pgxpool . Pool , provider voiceprovider . Client , health * ProviderHealth ) ( * StopProcessor , error ) {
if pool == nil || provider == nil || health == nil {
return nil , errors . New ( "database pool, voice provider client and provider health are required" )
}
return & StopProcessor { pool : pool , provider : provider , health : health , outbox : outbox . NewStore ( pool ), now : time . Now }, nil
}
func ( p * StopProcessor ) Handle ( ctx context . Context , event outbox . Event ) error {
var request voiceprovider . StopRequest
var conversationStatus conversation . Status
var taskStatus , tenantID , providerName string
err := p . pool . QueryRow ( ctx , `
SELECT c.public_id::text, c.status, COALESCE(c.stop_reason,'provider_failure'), r.provider_task_ref, r.room_id, r.status, c.tenant_id::text, r.provider
FROM conversation c JOIN rtc_task r ON r.conversation_id=c.id
WHERE c.id=$1::uuid` , event . AggregateID ,
). Scan ( & request . ConversationID , & conversationStatus , & request . Reason , & request . TaskRef , & request . RoomID , & taskStatus , & tenantID , & providerName )
if errors . Is ( err , pgx . ErrNoRows ) {
return ErrProviderTaskNotFound
}
if err != nil {
return fmt . Errorf ( "read provider stop task: %w" , err )
}
if conversationStatus != conversation . StatusStopping || taskStatus == "COMPLETED" {
return nil
}
if taskStatus != "STOPPING" && taskStatus != "UNKNOWN" && taskStatus != "FAILED" {
return fmt . Errorf ( "provider task status %s cannot be stopped" , taskStatus )
}
now := p . now (). UTC ()
_ , err = p . pool . Exec ( ctx , `
UPDATE rtc_task SET stop_attempts=stop_attempts+1, last_checked_at=$2, updated_at=$2
WHERE conversation_id=$1::uuid` , event . AggregateID , now )
if err != nil {
return fmt . Errorf ( "record provider stop attempt: %w" , err )
}
response , err := p . provider . Stop ( ctx , request )
if err != nil {
_ , _ = p . pool . Exec ( ctx , `UPDATE rtc_task SET status='UNKNOWN', updated_at=$2
WHERE conversation_id=$1::uuid AND status IN ('STOPPING','UNKNOWN','FAILED')` , event . AggregateID , p . now (). UTC ())
if voiceprovider . IsTemporary ( err ) {
if healthErr := p . health . RecordFailure ( ctx , tenantID , providerName , "PROVIDER_STOP_TEMPORARY" ); healthErr != nil {
return fmt . Errorf ( "record provider stop failure: %v; provider error: %w" , healthErr , err )
}
}
if voiceprovider . IsTemporary ( err ) {
return outbox . Retryable ( err )
}
return err
}
if err := p . health . RecordSuccess ( ctx , tenantID , providerName ); err != nil {
return fmt . Errorf ( "record provider stop success: %w" , err )
}
if response . Status != "STOPPING" && response . Status != "COMPLETED" {
return fmt . Errorf ( "provider returned unsupported stop status %s" , response . Status )
}
now = p . now (). UTC ()
if response . Status == "STOPPING" {
_ , err = p . pool . Exec ( ctx , `UPDATE rtc_task SET status=CASE WHEN status='COMPLETED' THEN 'COMPLETED' ELSE 'STOPPING' END,
last_checked_at=$2, updated_at=$2 WHERE conversation_id=$1::uuid` , event . AggregateID , now )
if err != nil {
return fmt . Errorf ( "record provider stop response: %w" , err )
}
return nil
}
tx , err := p . pool . BeginTx ( ctx , pgx . TxOptions {})
if err != nil {
return err
}
defer tx . Rollback ( context . Background ())
if _ , err := tx . Exec ( ctx , `UPDATE rtc_task SET status='COMPLETED', lease_expires_at=NULL,
last_checked_at=$2, updated_at=$2 WHERE conversation_id=$1::uuid` , event . AggregateID , now ); err != nil {
return err
}
tag , err := tx . Exec ( ctx , `UPDATE conversation SET status='COMPLETED', ended_at=COALESCE(ended_at,$2),
lease_expires_at=NULL, version=version+1, updated_at=$2 WHERE id=$1::uuid AND status='STOPPING'` , event . AggregateID , now )
if err != nil {
return err
}
if tag . RowsAffected () == 1 {
if _ , err := tx . Exec ( ctx , `INSERT INTO conversation_state_history (
conversation_id, from_status, to_status, reason, source
) VALUES ($1::uuid, 'STOPPING', 'COMPLETED', 'provider idempotent stop response confirmed completed task', 'worker')` , event . AggregateID ); err != nil {
return err
}
}
deduplicationKey := "content-extraction-v1:" + event . AggregateID
if _ , err := p . outbox . Enqueue ( ctx , tx , outbox . NewEvent {
TenantID : & tenantID , AggregateType : "conversation" , AggregateID : event . AggregateID ,
EventType : "conversation.content_extraction_requested" , DeduplicationKey : & deduplicationKey ,
Payload : json . RawMessage ( `{"generation_version":1}` ),
}); err != nil {
return fmt . Errorf ( "enqueue conversation content extraction: %w" , err )
}
return tx . Commit ( ctx )
}
type ProviderEventResult struct {
ConversationID string `json:"conversation_id"`
Status conversation . Status `json:"status"`
Duplicate bool `json:"duplicate"`
Ignored bool `json:"ignored"`
TurnsAccepted int `json:"turns_accepted,omitempty"`
UsageAccepted int `json:"usage_accepted,omitempty"`
}
type ProviderEventProcessor struct {
pool * pgxpool . Pool
outbox * outbox . Store
initialLeaseTTL time . Duration
now func () time . Time
}
func NewProviderEventProcessor ( pool * pgxpool . Pool , initialLeaseTTL time . Duration ) ( * ProviderEventProcessor , error ) {
if pool == nil || initialLeaseTTL <= 0 {
return nil , errors . New ( "database pool and positive initial lease TTL are required" )
}
return & ProviderEventProcessor { pool : pool , outbox : outbox . NewStore ( pool ), initialLeaseTTL : initialLeaseTTL , now : time . Now }, nil
}
func ( p * ProviderEventProcessor ) Process ( ctx context . Context , event voiceprovider . CallbackEvent , rawBody [] byte ) ( ProviderEventResult , error ) {
tx , err := p . pool . BeginTx ( ctx , pgx . TxOptions {})
if err != nil {
return ProviderEventResult {}, err
}
defer tx . Rollback ( context . Background ())
var internalID , tenantID , subjectID , publicID , taskProvider , retentionMode string
var currentStatus conversation . Status
err = tx . QueryRow ( ctx , `
SELECT c.id::text, c.tenant_id::text, c.subject_id::text, c.public_id::text, c.status, r.provider, c.retention_mode
FROM conversation c JOIN rtc_task r ON r.conversation_id=c.id
WHERE r.provider_task_ref=$1
FOR UPDATE OF c, r` , event . TaskRef ,
). Scan ( & internalID , & tenantID , & subjectID , & publicID , & currentStatus , & taskProvider , & retentionMode )
if errors . Is ( err , pgx . ErrNoRows ) {
return ProviderEventResult {}, ErrProviderTaskNotFound
}
if err != nil {
return ProviderEventResult {}, fmt . Errorf ( "read callback task: %w" , err )
}
if event . Provider != taskProvider || event . ConversationID != publicID {
return ProviderEventResult {}, ErrProviderTaskNotFound
}
digest := sha256 . Sum256 ( rawBody )
digestText := hex . EncodeToString ( digest [:])
eventID := uuid . NewString ()
sanitized , _ := json . Marshal ( map [ string ] any {
"event_type" : event . EventType , "status" : event . Status , "occurred_at" : event . OccurredAt , "turn_count" : len ( event . Turns ), "usage_count" : len ( event . Usage ),
})
tag , err := tx . Exec ( ctx , `
INSERT INTO provider_event (
id, tenant_id, conversation_id, provider, provider_event_key,
provider_task_ref, event_type, occurred_at, payload_digest, sanitized_payload
) VALUES ($1::uuid, $2::uuid, $3::uuid, $4, $5, $6, $7, $8, $9, $10::jsonb)
ON CONFLICT (provider, provider_event_key) DO NOTHING` ,
eventID , tenantID , internalID , event . Provider , event . EventID , event . TaskRef ,
event . EventType , event . OccurredAt , digestText , sanitized ,
)
if err != nil {
return ProviderEventResult {}, fmt . Errorf ( "store provider event: %w" , err )
}
if tag . RowsAffected () == 0 {
var storedDigest string
if err := tx . QueryRow ( ctx , `SELECT payload_digest FROM provider_event WHERE provider=$1 AND provider_event_key=$2` , event . Provider , event . EventID ). Scan ( & storedDigest ); err != nil {
return ProviderEventResult {}, err
}
if storedDigest != digestText {
return ProviderEventResult {}, ErrProviderEventConflict
}
if err := tx . Commit ( ctx ); err != nil {
return ProviderEventResult {}, err
}
return ProviderEventResult { ConversationID : publicID , Status : currentStatus , Duplicate : true }, nil
}
result := ProviderEventResult { ConversationID : publicID , Status : currentStatus }
if event . EventType == "voice_chat.usage" && event . Status == "FINAL" {
metrics := make ([] billing . UsageMetricInput , 0 , len ( event . Usage ))
for _ , metric := range event . Usage {
metrics = append ( metrics , billing . UsageMetricInput { UsageCategory : metric . UsageCategory , MetricCode : metric . MetricCode , Quantity : metric . Quantity , Unit : metric . Unit })
}
recorded , usageErr := billing . RecordProviderUsageTx ( ctx , tx , tenantID , internalID , eventID , event . Provider , event . EventID , event . OccurredAt , metrics )
if errors . Is ( usageErr , billing . ErrUsageConflict ) {
return ProviderEventResult {}, ErrProviderEventConflict
}
if usageErr != nil {
return ProviderEventResult {}, usageErr
}
if _ , usageErr = tx . Exec ( ctx , `UPDATE provider_event SET processing_status='processed',processed_at=$2 WHERE id=$1::uuid` , eventID , p . now (). UTC ()); usageErr != nil {
return ProviderEventResult {}, usageErr
}
if usageErr = tx . Commit ( ctx ); usageErr != nil {
return ProviderEventResult {}, usageErr
}
result . UsageAccepted = len ( recorded )
return result , nil
}
if event . EventType == "voice_chat.transcript.final" && event . Status == "FINAL" {
return p . processTranscriptEvent ( ctx , tx , eventID , internalID , tenantID , subjectID , retentionMode , event , result )
}
if event . EventType == "voice_chat.stopped" && event . Status == "COMPLETED" {
return p . completeStoppedEvent ( ctx , tx , eventID , internalID , tenantID , currentStatus , result )
}
if event . EventType == "voice_chat.failed" && event . Status == "FAILED" {
return p . processFailedEvent ( ctx , tx , eventID , internalID , currentStatus , result )
}
if event . EventType != "voice_chat.started" || event . Status != "ACTIVE" || currentStatus != conversation . StatusStartingAgent {
result . Ignored = true
if _ , err := tx . Exec ( ctx , `UPDATE provider_event SET processing_status='ignored', processed_at=$2 WHERE id=$1::uuid` , eventID , p . now (). UTC ()); err != nil {
return ProviderEventResult {}, err
}
if err := tx . Commit ( ctx ); err != nil {
return ProviderEventResult {}, err
}
return result , nil
}
if err := conversation . ValidateTransition ( currentStatus , conversation . StatusActive ); err != nil {
return ProviderEventResult {}, err
}
now := p . now (). UTC ()
_ , err = tx . Exec ( ctx , `
UPDATE conversation SET status='ACTIVE', started_at=COALESCE(started_at,$2),
lease_expires_at=$3, version=version+1, updated_at=$2
WHERE id=$1::uuid AND status='STARTING_AGENT'` , internalID , now , now . Add ( p . initialLeaseTTL ))
if err != nil {
return ProviderEventResult {}, fmt . Errorf ( "activate conversation from provider event: %w" , err )
}
_ , err = tx . Exec ( ctx , `
UPDATE rtc_task SET status='ACTIVE', lease_expires_at=$2, last_checked_at=$3, updated_at=$3
WHERE conversation_id=$1::uuid` , internalID , now . Add ( p . initialLeaseTTL ), now )
if err != nil {
return ProviderEventResult {}, fmt . Errorf ( "activate RTC task: %w" , err )
}
_ , err = tx . Exec ( ctx , `
INSERT INTO conversation_state_history (
conversation_id, from_status, to_status, reason, source, provider_event_id
) VALUES ($1::uuid, 'STARTING_AGENT', 'ACTIVE', 'provider confirmed voice chat start', 'provider', $2::uuid)` , internalID , eventID )
if err != nil {
return ProviderEventResult {}, fmt . Errorf ( "record provider active state: %w" , err )
}
_ , err = tx . Exec ( ctx , `UPDATE provider_event SET processing_status='processed', processed_at=$2 WHERE id=$1::uuid` , eventID , now )
if err != nil {
return ProviderEventResult {}, err
}
if err := tx . Commit ( ctx ); err != nil {
return ProviderEventResult {}, err
}
result . Status = conversation . StatusActive
return result , nil
}
func ( p * ProviderEventProcessor ) processTranscriptEvent (
ctx context . Context ,
tx pgx . Tx ,
eventID , conversationID , tenantID , subjectID , retentionMode string ,
event voiceprovider . CallbackEvent ,
result ProviderEventResult ,
) ( ProviderEventResult , error ) {
now := p . now (). UTC ()
if retentionMode == "none" {
result . Ignored = true
if _ , err := tx . Exec ( ctx , `UPDATE provider_event SET processing_status='ignored', processed_at=$2 WHERE id=$1::uuid` , eventID , now ); err != nil {
return ProviderEventResult {}, err
}
if err := tx . Commit ( ctx ); err != nil {
return ProviderEventResult {}, err
}
return result , nil
}
for _ , turn := range event . Turns {
providerRef := turn . ProviderRef
if providerRef == "" {
providerRef = turn . TurnKey
}
tag , err := tx . Exec ( ctx , `
INSERT INTO conversation_turn (
tenant_id,subject_id,conversation_id,turn_key,sequence_no,speaker,start_at,end_at,
final_text,interrupted,provider_ref,source_type
) VALUES ($1::uuid,$2::uuid,$3::uuid,$4,$5,$6,$7,$8,$9,$10,$11,'provider')
ON CONFLICT (conversation_id,turn_key) DO NOTHING` , tenantID , subjectID , conversationID ,
turn . TurnKey , turn . SequenceNo , turn . Speaker , turn . StartAt , turn . EndAt , turn . FinalText , turn . Interrupted , providerRef )
if err != nil {
return ProviderEventResult {}, fmt . Errorf ( "store final transcript turn: %w" , err )
}
if tag . RowsAffected () == 0 {
var sequenceNo int64
var speaker , finalText string
var startAt , endAt time . Time
var interrupted bool
if err := tx . QueryRow ( ctx , `
SELECT sequence_no,speaker,start_at,end_at,final_text,interrupted
FROM conversation_turn WHERE conversation_id=$1::uuid AND turn_key=$2` , conversationID , turn . TurnKey ). Scan (
& sequenceNo , & speaker , & startAt , & endAt , & finalText , & interrupted ); err != nil {
return ProviderEventResult {}, err
}
if sequenceNo != turn . SequenceNo || speaker != turn . Speaker || ! startAt . Equal ( turn . StartAt ) ||
! endAt . Equal ( turn . EndAt ) || finalText != turn . FinalText || interrupted != turn . Interrupted {
return ProviderEventResult {}, ErrProviderEventConflict
}
continue
}
result . TurnsAccepted ++
}
// A provider may deliver its final transcript after the stopped callback.
// In that ordering the original extraction job may already have run before
// any turns existed, so accepted late turns must create a new idempotent
// generation request rather than leaving the conversation without content.
if result . TurnsAccepted > 0 && conversation . IsTerminal ( result . Status ) {
var generationVersion int64
if err := tx . QueryRow ( ctx , `SELECT COALESCE(max(sequence_no),1) FROM conversation_turn WHERE conversation_id=$1::uuid` , conversationID ). Scan ( & generationVersion ); err != nil {
return ProviderEventResult {}, fmt . Errorf ( "read late transcript generation: %w" , err )
}
digest := sha256 . Sum256 ([] byte ( event . Provider + ":" + event . EventID ))
deduplicationKey := "content-extraction-v1:" + conversationID + ":provider:" + hex . EncodeToString ( digest [: 16 ])
payload , err := json . Marshal ( map [ string ] int64 { "generation_version" : generationVersion })
if err != nil {
return ProviderEventResult {}, err
}
if _ , err := p . outbox . Enqueue ( ctx , tx , outbox . NewEvent {
TenantID : & tenantID , AggregateType : "conversation" , AggregateID : conversationID ,
EventType : "conversation.content_extraction_requested" , DeduplicationKey : & deduplicationKey ,
Payload : payload ,
}); err != nil {
return ProviderEventResult {}, fmt . Errorf ( "enqueue late provider transcript extraction: %w" , err )
}
}
if _ , err := tx . Exec ( ctx , `UPDATE provider_event SET processing_status='processed', processed_at=$2 WHERE id=$1::uuid` , eventID , now ); err != nil {
return ProviderEventResult {}, err
}
if err := tx . Commit ( ctx ); err != nil {
return ProviderEventResult {}, err
}
return result , nil
}
func ( p * ProviderEventProcessor ) completeStoppedEvent (
ctx context . Context ,
tx pgx . Tx ,
eventID , internalID , tenantID string ,
currentStatus conversation . Status ,
result ProviderEventResult ,
) ( ProviderEventResult , error ) {
if conversation . IsTerminal ( currentStatus ) || currentStatus == conversation . StatusRoomIssued || currentStatus == conversation . StatusPreparing {
result . Ignored = true
if _ , err := tx . Exec ( ctx , `UPDATE provider_event SET processing_status='ignored', processed_at=$2 WHERE id=$1::uuid` , eventID , p . now (). UTC ()); err != nil {
return ProviderEventResult {}, err
}
if err := tx . Commit ( ctx ); err != nil {
return ProviderEventResult {}, err
}
return result , nil
}
now := p . now (). UTC ()
if currentStatus == conversation . StatusStartingAgent || currentStatus == conversation . StatusActive {
if err := conversation . ValidateTransition ( currentStatus , conversation . StatusStopping ); err != nil {
return ProviderEventResult {}, err
}
if _ , err := tx . Exec ( ctx , `UPDATE conversation SET status='STOPPING', lease_expires_at=NULL,
version=version+1, updated_at=$2 WHERE id=$1::uuid` , internalID , now ); err != nil {
return ProviderEventResult {}, err
}
if _ , err := tx . Exec ( ctx , `
INSERT INTO conversation_state_history (
conversation_id, from_status, to_status, reason, source, provider_event_id
) VALUES ($1::uuid, $2, 'STOPPING', 'provider ended task before platform stop completed', 'provider', $3::uuid)` ,
internalID , currentStatus , eventID ); err != nil {
return ProviderEventResult {}, err
}
}
if _ , err := tx . Exec ( ctx , `UPDATE conversation SET status='COMPLETED', ended_at=COALESCE(ended_at,$2),
lease_expires_at=NULL, stop_reason=COALESCE(stop_reason,'provider_ended'),
version=version+1, updated_at=$2 WHERE id=$1::uuid` , internalID , now ); err != nil {
return ProviderEventResult {}, err
}
if _ , err := tx . Exec ( ctx , `UPDATE rtc_task SET status='COMPLETED', lease_expires_at=NULL,
last_checked_at=$2, updated_at=$2 WHERE conversation_id=$1::uuid` , internalID , now ); err != nil {
return ProviderEventResult {}, err
}
if _ , err := tx . Exec ( ctx , `
INSERT INTO conversation_state_history (
conversation_id, from_status, to_status, reason, source, provider_event_id
) VALUES ($1::uuid, 'STOPPING', 'COMPLETED', 'provider confirmed voice chat stop', 'provider', $2::uuid)` ,
internalID , eventID ); err != nil {
return ProviderEventResult {}, err
}
if _ , err := tx . Exec ( ctx , `UPDATE provider_event SET processing_status='processed', processed_at=$2 WHERE id=$1::uuid` , eventID , now ); err != nil {
return ProviderEventResult {}, err
}
deduplicationKey := "content-extraction-v1:" + internalID
if _ , err := p . outbox . Enqueue ( ctx , tx , outbox . NewEvent {
TenantID : & tenantID , AggregateType : "conversation" , AggregateID : internalID ,
EventType : "conversation.content_extraction_requested" , DeduplicationKey : & deduplicationKey ,
Payload : json . RawMessage ( `{"generation_version":1}` ),
}); err != nil {
return ProviderEventResult {}, fmt . Errorf ( "enqueue conversation content extraction: %w" , err )
}
if err := tx . Commit ( ctx ); err != nil {
return ProviderEventResult {}, err
}
result . Status = conversation . StatusCompleted
return result , nil
}
func ( p * ProviderEventProcessor ) processFailedEvent (
ctx context . Context ,
tx pgx . Tx ,
eventID , internalID string ,
currentStatus conversation . Status ,
result ProviderEventResult ,
) ( ProviderEventResult , error ) {
now := p . now (). UTC ()
switch currentStatus {
case conversation . StatusStartingAgent :
if _ , err := tx . Exec ( ctx , `UPDATE conversation SET status='FAILED', ended_at=$2,
lease_expires_at=NULL, failure_code='provider_start_failed', version=version+1, updated_at=$2
WHERE id=$1::uuid` , internalID , now ); err != nil {
return ProviderEventResult {}, err
}
if _ , err := tx . Exec ( ctx , `INSERT INTO conversation_state_history (
conversation_id, from_status, to_status, reason, source, provider_event_id
) VALUES ($1::uuid, 'STARTING_AGENT', 'FAILED', 'provider reported voice chat failure', 'provider', $2::uuid)` , internalID , eventID ); err != nil {
return ProviderEventResult {}, err
}
result . Status = conversation . StatusFailed
case conversation . StatusActive :
if _ , err := tx . Exec ( ctx , `UPDATE conversation SET status='STOPPING', lease_expires_at=NULL,
failure_code='provider_runtime_failed', version=version+1, updated_at=$2 WHERE id=$1::uuid` , internalID , now ); err != nil {
return ProviderEventResult {}, err
}
if _ , err := tx . Exec ( ctx , `INSERT INTO conversation_state_history (
conversation_id, from_status, to_status, reason, source, provider_event_id
) VALUES ($1::uuid, 'ACTIVE', 'STOPPING', 'provider reported runtime failure', 'provider', $2::uuid)` , internalID , eventID ); err != nil {
return ProviderEventResult {}, err
}
result . Status = conversation . StatusStopping
case conversation . StatusStopping :
if _ , err := tx . Exec ( ctx , `UPDATE conversation SET failure_code=COALESCE(failure_code,'provider_stop_failed'), updated_at=$2 WHERE id=$1::uuid` , internalID , now ); err != nil {
return ProviderEventResult {}, err
}
default :
result . Ignored = true
}
if ! result . Ignored {
if _ , err := tx . Exec ( ctx , `UPDATE rtc_task SET status='FAILED', lease_expires_at=NULL,
last_checked_at=$2, updated_at=$2 WHERE conversation_id=$1::uuid` , internalID , now ); err != nil {
return ProviderEventResult {}, err
}
}
processingStatus := "processed"
if result . Ignored {
processingStatus = "ignored"
}
if _ , err := tx . Exec ( ctx , `UPDATE provider_event SET processing_status=$2, processed_at=$3 WHERE id=$1::uuid` , eventID , processingStatus , now ); err != nil {
return ProviderEventResult {}, err
}
if err := tx . Commit ( ctx ); err != nil {
return ProviderEventResult {}, err
}
return result , nil
}