asyncapi: 2.6.0

info:
  title: Cypher Service Async API
  version: 1.0.0
  description: |
    EventZR Cypher Service - Kafka event-driven async API.

    Cypher is the WhatsApp-first agentic AI co-pilot deployed on ECS Fargate.
    It produces and consumes CloudEvents 1.0 messages via Amazon MSK Serverless (Kafka).

    All events use the transactional outbox pattern for reliable delivery.
    Partition key is always `tenant_id` for tenant-scoped ordering.
  contact:
    name: EventZR Team
    email: dev@eventzr.com
  license:
    name: Proprietary

servers:
  staging:
    url: boot-gpm4u49z.c2.kafka-serverless.us-east-1.amazonaws.com:9098
    protocol: kafka
    description: Amazon MSK Serverless (staging)
    security:
      - saslScram: []

defaultContentType: application/json

channels:
  # ============================================================
  # CONSUMED TOPICS (4 topics, 4 consumer files)
  # ============================================================

  eventzr.chat.message.created.v1:
    description: |
      Syncs chat messages from chat-svc to conversation history.
      Consumer: ChatConsumer
      Consumer group: cypher-svc-chat-consumer
      Idempotency: Redis cache (24h TTL) keyed by CloudEvent ID
    subscribe:
      operationId: handleChatMessageCreated
      summary: Receive chat message created events
      message:
        $ref: '#/components/messages/ChatMessageCreated'
      bindings:
        kafka:
          groupId: cypher-svc-chat-consumer

  eventzr.mcp.tool.executed.v1:
    description: |
      Handles tool execution results from MCP service.
      Consumer: McpConsumer
      Consumer group: cypher-svc-mcp-consumer
      Updates task status and publishes task completion/failure events.
      Idempotency: Redis cache (24h TTL) keyed by CloudEvent ID
    subscribe:
      operationId: handleMcpToolExecuted
      summary: Receive MCP tool execution results
      message:
        $ref: '#/components/messages/McpToolExecuted'
      bindings:
        kafka:
          groupId: cypher-svc-mcp-consumer

  eventzr.notify.delivered.v1:
    description: |
      Handles notification delivery status updates from notify-svc.
      Consumer: NotifyConsumer
      Consumer group: cypher-svc-notify-consumer
      Updates nudge delivery status based on notification outcome.
      Idempotency: Redis cache (24h TTL) keyed by CloudEvent ID
    subscribe:
      operationId: handleNotifyDelivered
      summary: Receive notification delivery status updates
      message:
        $ref: '#/components/messages/NotifyDelivered'
      bindings:
        kafka:
          groupId: cypher-svc-notify-consumer

  eventzr.wallet.balance.updated.v1:
    description: |
      Updates local credit cache when tenant wallet balance changes.
      Consumer: WalletConsumer
      Consumer group: cypher-svc-wallet-consumer
      Purpose: Keeps local Redis cache fresh for fast credit checks.
      Avoids hitting wallet-svc on every AI operation.
      Idempotency: Redis cache (24h TTL) keyed by CloudEvent ID
    subscribe:
      operationId: handleWalletBalanceUpdated
      summary: Receive wallet balance updates for credit cache
      message:
        $ref: '#/components/messages/WalletBalanceUpdated'
      bindings:
        kafka:
          groupId: cypher-svc-wallet-consumer

  # ============================================================
  # PRODUCED TOPICS (17 topics, 7 producer files)
  # ============================================================

  # --- Conversation lifecycle (ConversationProducer) ---

  eventzr.cypher.conversation.created.v1:
    description: Published when a new conversation is initiated.
    publish:
      operationId: publishConversationCreated
      summary: New conversation initiated
      message:
        $ref: '#/components/messages/ConversationCreated'
      bindings:
        kafka:
          key:
            type: string
            description: tenant_id (partition key)

  eventzr.cypher.conversation.updated.v1:
    description: Published when conversation state or metrics change.
    publish:
      operationId: publishConversationUpdated
      summary: Conversation state changed
      message:
        $ref: '#/components/messages/ConversationUpdated'
      bindings:
        kafka:
          key:
            type: string
            description: tenant_id (partition key)

  eventzr.cypher.conversation.completed.v1:
    description: Published when conversation reaches terminal state.
    publish:
      operationId: publishConversationCompleted
      summary: Conversation finished
      message:
        $ref: '#/components/messages/ConversationCompleted'
      bindings:
        kafka:
          key:
            type: string
            description: tenant_id (partition key)

  # --- Task lifecycle (TaskProducer) ---

  eventzr.cypher.task.started.v1:
    description: Published when task execution begins.
    publish:
      operationId: publishTaskStarted
      summary: Task execution started
      message:
        $ref: '#/components/messages/TaskStarted'
      bindings:
        kafka:
          key:
            type: string
            description: tenant_id (partition key)

  eventzr.cypher.task.completed.v1:
    description: Published when task completes successfully.
    publish:
      operationId: publishTaskCompleted
      summary: Task successfully completed
      message:
        $ref: '#/components/messages/TaskCompleted'
      bindings:
        kafka:
          key:
            type: string
            description: tenant_id (partition key)

  eventzr.cypher.task.failed.v1:
    description: Published when task execution fails.
    publish:
      operationId: publishTaskFailed
      summary: Task execution failed
      message:
        $ref: '#/components/messages/TaskFailed'
      bindings:
        kafka:
          key:
            type: string
            description: tenant_id (partition key)

  # --- Voice call lifecycle (VoiceProducer) ---

  eventzr.cypher.voice-call.started.v1:
    description: Published when a WhatsApp voice call AI session begins.
    publish:
      operationId: publishVoiceCallStarted
      summary: Voice call started
      message:
        $ref: '#/components/messages/VoiceCallStarted'
      bindings:
        kafka:
          key:
            type: string
            description: tenant_id (partition key)

  eventzr.cypher.voice-call.utterance.v1:
    description: Published for each processed utterance during a voice call (STT + LLM + TTS).
    publish:
      operationId: publishVoiceCallUtterance
      summary: Voice utterance processed
      message:
        $ref: '#/components/messages/VoiceCallUtterance'
      bindings:
        kafka:
          key:
            type: string
            description: tenant_id (partition key)

  eventzr.cypher.voice-call.ended.v1:
    description: Published when a voice call ends (completed, failed, or abandoned).
    publish:
      operationId: publishVoiceCallEnded
      summary: Voice call ended
      message:
        $ref: '#/components/messages/VoiceCallEnded'
      bindings:
        kafka:
          key:
            type: string
            description: tenant_id (partition key)

  eventzr.cypher.voice-call.analytics.v1:
    description: Published with aggregated voice call analytics for dashboards and monitoring.
    publish:
      operationId: publishVoiceCallAnalytics
      summary: Voice call analytics aggregation
      message:
        $ref: '#/components/messages/VoiceCallAnalytics'
      bindings:
        kafka:
          key:
            type: string
            description: tenant_id (partition key)

  # --- Credit events (CreditProducer) ---

  eventzr.cypher.credits.consumed.v1:
    description: Published when AI credits are consumed for any operation.
    publish:
      operationId: publishCreditsConsumed
      summary: AI credits consumed
      message:
        $ref: '#/components/messages/CreditsConsumed'
      bindings:
        kafka:
          key:
            type: string
            description: tenant_id (partition key)

  # --- Session lifecycle (SessionProducer) ---

  eventzr.cypher.session.created.v1:
    description: Published when a new user session is created.
    publish:
      operationId: publishSessionCreated
      summary: Session created
      message:
        $ref: '#/components/messages/SessionCreated'
      bindings:
        kafka:
          key:
            type: string
            description: tenant_id (partition key)

  eventzr.cypher.session.expired.v1:
    description: Published when session expires or is explicitly ended.
    publish:
      operationId: publishSessionExpired
      summary: Session expired
      message:
        $ref: '#/components/messages/SessionExpired'
      bindings:
        kafka:
          key:
            type: string
            description: tenant_id (partition key)

  # --- Nudge events (NudgeProducer) ---

  eventzr.cypher.nudge.sent.v1:
    description: Published when a proactive nudge is sent to user.
    publish:
      operationId: publishNudgeSent
      summary: Nudge sent
      message:
        $ref: '#/components/messages/NudgeSent'
      bindings:
        kafka:
          key:
            type: string
            description: tenant_id (partition key)

  # --- Escalation lifecycle (EscalationProducer) ---

  eventzr.cypher.escalation.created.v1:
    description: Published when a conversation is escalated to human support.
    publish:
      operationId: publishEscalationCreated
      summary: Escalation created
      message:
        $ref: '#/components/messages/EscalationCreated'
      bindings:
        kafka:
          key:
            type: string
            description: tenant_id (partition key)

  eventzr.cypher.escalation.assigned.v1:
    description: Published when an escalation is assigned to a support agent.
    publish:
      operationId: publishEscalationAssigned
      summary: Escalation assigned to agent
      message:
        $ref: '#/components/messages/EscalationAssigned'
      bindings:
        kafka:
          key:
            type: string
            description: tenant_id (partition key)

  eventzr.cypher.escalation.resolved.v1:
    description: Published when an escalation is resolved by agent.
    publish:
      operationId: publishEscalationResolved
      summary: Escalation resolved
      message:
        $ref: '#/components/messages/EscalationResolved'
      bindings:
        kafka:
          key:
            type: string
            description: tenant_id (partition key)

components:
  securitySchemes:
    saslScram:
      type: scramSha256
      description: MSK IAM authentication

  messages:
    # --- Consumed messages ---

    ChatMessageCreated:
      name: ChatMessageCreated
      title: Chat Message Created
      contentType: application/json
      headers:
        type: object
        properties:
          ce_specversion: { type: string, const: '1.0' }
          ce_type: { type: string, const: 'com.eventzr.chat.message.created' }
          ce_source: { type: string, example: '/chat-svc/messages' }
          ce_id: { type: string, format: ulid }
          ce_time: { type: string, format: date-time }
      payload:
        type: object
        required: [messageId, tenantId, userId, threadId, role, content, createdAt]
        properties:
          messageId: { type: string }
          tenantId: { type: string, format: uuid }
          userId: { type: string, format: uuid }
          threadId: { type: string }
          role: { type: string, enum: [user, assistant, system] }
          content: { type: string }
          contentType: { type: string, enum: [text, image, audio, video] }
          createdAt: { type: string, format: date-time }
          metadata: { type: object }

    McpToolExecuted:
      name: McpToolExecuted
      title: MCP Tool Executed
      contentType: application/json
      headers:
        type: object
        properties:
          ce_specversion: { type: string, const: '1.0' }
          ce_type: { type: string, const: 'com.eventzr.mcp.tool.executed' }
          ce_source: { type: string, example: '/mcp-svc/tools' }
      payload:
        type: object
        required:
          [executionId, tenantId, userId, toolName, status, duration, creditsConsumed, executedAt]
        properties:
          executionId: { type: string }
          tenantId: { type: string, format: uuid }
          userId: { type: string, format: uuid }
          toolName: { type: string }
          toolInput: { type: object }
          toolOutput: { type: object, nullable: true }
          status: { type: string, enum: [SUCCESS, FAILURE, TIMEOUT] }
          errorMessage: { type: string }
          duration: { type: number, description: 'Duration in milliseconds' }
          creditsConsumed: { type: number }
          executedAt: { type: string, format: date-time }
          taskId: { type: string }
          conversationId: { type: string }

    NotifyDelivered:
      name: NotifyDelivered
      title: Notification Delivered
      contentType: application/json
      headers:
        type: object
        properties:
          ce_specversion: { type: string, const: '1.0' }
          ce_type: { type: string, const: 'com.eventzr.notify.delivered' }
          ce_source: { type: string, example: '/notify-svc/notifications' }
      payload:
        type: object
        required: [notificationId, tenantId, userId, channel, status, deliveredAt]
        properties:
          notificationId: { type: string }
          tenantId: { type: string, format: uuid }
          userId: { type: string, format: uuid }
          channel: { type: string }
          status: { type: string, enum: [DELIVERED, FAILED, BOUNCED] }
          deliveredAt: { type: string, format: date-time }
          externalId: { type: string }
          errorMessage: { type: string }
          metadata: { type: object }

    WalletBalanceUpdated:
      name: WalletBalanceUpdated
      title: Wallet Balance Updated
      contentType: application/json
      headers:
        type: object
        properties:
          ce_specversion: { type: string, const: '1.0' }
          ce_type: { type: string, const: 'com.eventzr.wallet.balance.updated' }
          ce_source: { type: string, example: '/wallet-svc/wallets' }
      payload:
        type: object
        required:
          [
            walletId,
            tenantId,
            balanceType,
            previousBalance,
            currentBalance,
            change,
            reason,
            updatedAt,
          ]
        properties:
          walletId: { type: string }
          tenantId: { type: string, format: uuid }
          userId: { type: string, format: uuid }
          balanceType: { type: string, enum: [CREDITS, POINTS, CASH] }
          previousBalance: { type: number }
          currentBalance: { type: number }
          change: { type: number }
          reason: { type: string }
          transactionId: { type: string }
          updatedAt: { type: string, format: date-time }
          metadata: { type: object }

    # --- Produced messages ---

    ConversationCreated:
      name: ConversationCreated
      title: Conversation Created
      contentType: application/json
      headers:
        type: object
        properties:
          ce_specversion: { type: string, const: '1.0' }
          ce_type: { type: string, const: 'com.eventzr.cypher.conversation.created.v1' }
          ce_source: { type: string, const: '/cypher-svc/conversations' }
          ce_id: { type: string, format: ulid }
          ce_time: { type: string, format: date-time }
      payload:
        type: object
        required: [conversationId, tenantId, sessionId, channel, userId, startedAt]
        properties:
          conversationId: { type: string }
          tenantId: { type: string, format: uuid }
          sessionId: { type: string }
          channel: { type: string, enum: [WEB, MOBILE, WHATSAPP, SMS, VOICE, API] }
          userId: { type: string, format: uuid }
          vertical: { type: string }
          initialIntent: { type: string }
          locale: { type: string }
          startedAt: { type: string, format: date-time }

    ConversationUpdated:
      name: ConversationUpdated
      title: Conversation Updated
      contentType: application/json
      payload:
        type: object
        required: [conversationId, tenantId, state, messageCount, creditsConsumed, updatedAt]
        properties:
          conversationId: { type: string }
          tenantId: { type: string, format: uuid }
          state: { type: string }
          messageCount: { type: integer }
          creditsConsumed: { type: number }
          updatedAt: { type: string, format: date-time }

    ConversationCompleted:
      name: ConversationCompleted
      title: Conversation Completed
      contentType: application/json
      payload:
        type: object
        required: [conversationId, tenantId, finalState, totalCredits, duration, completedAt]
        properties:
          conversationId: { type: string }
          tenantId: { type: string, format: uuid }
          finalState: { type: string }
          totalCredits: { type: number }
          duration: { type: number, description: 'Duration in milliseconds' }
          completedAt: { type: string, format: date-time }

    TaskStarted:
      name: TaskStarted
      title: Task Started
      contentType: application/json
      payload:
        type: object
        required: [taskId, conversationId, tenantId, type, inputParams, userId, startedAt]
        properties:
          taskId: { type: string }
          conversationId: { type: string }
          tenantId: { type: string, format: uuid }
          type: { type: string }
          inputParams: { type: object }
          userId: { type: string, format: uuid }
          toolName: { type: string }
          priority: { type: string, enum: [LOW, MEDIUM, HIGH, URGENT] }
          estimatedCredits: { type: number }
          startedAt: { type: string, format: date-time }

    TaskCompleted:
      name: TaskCompleted
      title: Task Completed
      contentType: application/json
      payload:
        type: object
        required: [taskId, conversationId, tenantId, status, result, userId, completedAt]
        properties:
          taskId: { type: string }
          conversationId: { type: string }
          tenantId: { type: string, format: uuid }
          status: { type: string }
          result: { type: object }
          error: { type: string }
          userId: { type: string, format: uuid }
          toolName: { type: string }
          toolOutput: { type: object }
          duration: { type: number }
          creditsConsumed: { type: number }
          completedAt: { type: string, format: date-time }

    TaskFailed:
      name: TaskFailed
      title: Task Failed
      contentType: application/json
      payload:
        type: object
        required: [taskId, conversationId, tenantId, error, userId, failedAt]
        properties:
          taskId: { type: string }
          conversationId: { type: string }
          tenantId: { type: string, format: uuid }
          error: { type: string }
          errorCode: { type: string }
          errorMessage: { type: string }
          userId: { type: string, format: uuid }
          toolName: { type: string }
          retryCount: { type: integer }
          maxRetries: { type: integer }
          isRetryable: { type: boolean }
          failedAt: { type: string, format: date-time }

    VoiceCallStarted:
      name: VoiceCallStarted
      title: Voice Call Started
      contentType: application/json
      payload:
        type: object
        required: [tenantId, userId, callId, conversationId, locale, voiceId, startedAt]
        properties:
          tenantId: { type: string, format: uuid }
          userId: { type: string, format: uuid }
          callId: { type: string }
          conversationId: { type: string }
          locale: { type: string, example: 'en-US' }
          voiceId: { type: string, example: 'elevenlabs-adam' }
          startedAt: { type: string, format: date-time }

    VoiceCallUtterance:
      name: VoiceCallUtterance
      title: Voice Call Utterance Processed
      contentType: application/json
      payload:
        type: object
        required:
          [
            tenantId,
            userId,
            callId,
            conversationId,
            utteranceIndex,
            transcript,
            response,
            latencyMs,
            tokensUsed,
            creditsConsumed,
            timestamp,
          ]
        properties:
          tenantId: { type: string, format: uuid }
          userId: { type: string, format: uuid }
          callId: { type: string }
          conversationId: { type: string }
          utteranceIndex: { type: integer }
          transcript: { type: string }
          response: { type: string }
          latencyMs: { type: number }
          tokensUsed: { type: integer }
          creditsConsumed: { type: number }
          timestamp: { type: string, format: date-time }

    VoiceCallEnded:
      name: VoiceCallEnded
      title: Voice Call Ended
      contentType: application/json
      payload:
        type: object
        required:
          [
            tenantId,
            userId,
            callId,
            conversationId,
            startedAt,
            endedAt,
            durationSeconds,
            utteranceCount,
            totalCreditsConsumed,
            averageLatencyMs,
            maxLatencyMs,
            status,
          ]
        properties:
          tenantId: { type: string, format: uuid }
          userId: { type: string, format: uuid }
          callId: { type: string }
          conversationId: { type: string }
          startedAt: { type: string, format: date-time }
          endedAt: { type: string, format: date-time }
          durationSeconds: { type: number }
          utteranceCount: { type: integer }
          totalCreditsConsumed: { type: number }
          averageLatencyMs: { type: number }
          maxLatencyMs: { type: number }
          status: { type: string, enum: [completed, failed, abandoned] }
          failureReason: { type: string }

    VoiceCallAnalytics:
      name: VoiceCallAnalytics
      title: Voice Call Analytics
      contentType: application/json
      payload:
        type: object
        required:
          [
            tenantId,
            timeRange,
            totalCalls,
            totalDurationSeconds,
            totalUtterances,
            totalCreditsConsumed,
            averageLatencyMs,
            successRate,
            timestamp,
          ]
        properties:
          tenantId: { type: string, format: uuid }
          timeRange: { type: string }
          totalCalls: { type: integer }
          totalDurationSeconds: { type: number }
          totalUtterances: { type: integer }
          totalCreditsConsumed: { type: number }
          averageLatencyMs: { type: number }
          successRate: { type: number }
          timestamp: { type: string, format: date-time }

    CreditsConsumed:
      name: CreditsConsumed
      title: Credits Consumed
      contentType: application/json
      payload:
        type: object
        required: [tenantId, conversationId, credits, operation, timestamp]
        properties:
          tenantId: { type: string, format: uuid }
          conversationId: { type: string }
          credits: { type: number }
          operation: { type: string }
          timestamp: { type: string, format: date-time }
          userId: { type: string, format: uuid }
          taskId: { type: string }
          modelUsed: { type: string }
          tokenCount: { type: integer }
          costBreakdown:
            type: object
            properties:
              inputTokens: { type: integer }
              outputTokens: { type: integer }
              totalTokens: { type: integer }
              inputCost: { type: number }
              outputCost: { type: number }
              totalCost: { type: number }
          metadata: { type: object }

    SessionCreated:
      name: SessionCreated
      title: Session Created
      contentType: application/json
      payload:
        type: object
        required: [sessionId, tenantId, channel, channelUserId, expiresAt, createdAt]
        properties:
          sessionId: { type: string }
          tenantId: { type: string, format: uuid }
          channel: { type: string, enum: [WEB, MOBILE, WHATSAPP, SMS, VOICE, API] }
          channelUserId: { type: string }
          userId: { type: string, format: uuid }
          expiresAt: { type: string, format: date-time }
          createdAt: { type: string, format: date-time }
          metadata: { type: object }

    SessionExpired:
      name: SessionExpired
      title: Session Expired
      contentType: application/json
      payload:
        type: object
        required: [sessionId, tenantId, expiredAt, conversationCount]
        properties:
          sessionId: { type: string }
          tenantId: { type: string, format: uuid }
          expiredAt: { type: string, format: date-time }
          conversationCount: { type: integer }
          userId: { type: string, format: uuid }
          activeConversationIds: { type: array, items: { type: string } }
          durationMinutes: { type: number }
          messageCount: { type: integer }
          creditsConsumed: { type: number }

    NudgeSent:
      name: NudgeSent
      title: Nudge Sent
      contentType: application/json
      payload:
        type: object
        required:
          [nudgeId, tenantId, sessionId, userId, type, channel, content, sentAt, deliveryStatus]
        properties:
          nudgeId: { type: string }
          tenantId: { type: string, format: uuid }
          sessionId: { type: string }
          userId: { type: string, format: uuid }
          type: { type: string, enum: [PROACTIVE, REMINDER, FOLLOW_UP, RECOMMENDATION, ALERT] }
          channel: { type: string, enum: [WEB, MOBILE, WHATSAPP, SMS, EMAIL, PUSH] }
          content: { type: string }
          sentAt: { type: string, format: date-time }
          deliveryStatus: { type: string, enum: [PENDING, SENT, DELIVERED, FAILED] }
          externalMessageId: { type: string }
          metadata: { type: object }

    EscalationCreated:
      name: EscalationCreated
      title: Escalation Created
      contentType: application/json
      payload:
        type: object
        required:
          [
            tenantId,
            escalationId,
            conversationId,
            userId,
            reason,
            aiConfidence,
            slaSeconds,
            priority,
            createdAt,
          ]
        properties:
          tenantId: { type: string, format: uuid }
          escalationId: { type: string }
          conversationId: { type: string }
          userId: { type: string, format: uuid }
          reason:
            {
              type: string,
              enum:
                [
                  low_confidence,
                  user_requested,
                  sensitive_topic,
                  payment_issue,
                  repeated_failure,
                  compliance_required,
                ],
            }
          aiConfidence: { type: number }
          slaSeconds: { type: integer }
          priority: { type: integer }
          channel: { type: string }
          locale: { type: string }
          createdAt: { type: string, format: date-time }

    EscalationAssigned:
      name: EscalationAssigned
      title: Escalation Assigned
      contentType: application/json
      payload:
        type: object
        required: [tenantId, escalationId, conversationId, agentId, assignedAt, slaRemaining]
        properties:
          tenantId: { type: string, format: uuid }
          escalationId: { type: string }
          conversationId: { type: string }
          agentId: { type: string }
          assignedAt: { type: string, format: date-time }
          slaRemaining: { type: integer, description: 'Remaining SLA seconds' }

    EscalationResolved:
      name: EscalationResolved
      title: Escalation Resolved
      contentType: application/json
      payload:
        type: object
        required:
          [
            tenantId,
            escalationId,
            conversationId,
            agentId,
            resolvedAt,
            totalDurationSeconds,
            slaCompliant,
          ]
        properties:
          tenantId: { type: string, format: uuid }
          escalationId: { type: string }
          conversationId: { type: string }
          agentId: { type: string }
          resolutionNotes: { type: string }
          resolvedAt: { type: string, format: date-time }
          totalDurationSeconds: { type: number }
          slaCompliant: { type: boolean }
