asyncapi: '2.6.0'

info:
  title: EventZR Jobs Service - Kafka Event API
  version: '1.0.0'
  description: |
    AsyncAPI specification for jobs-svc Kafka event channels.

    The jobs service publishes events for job lifecycle, scheduling, DLQ,
    worker, and quota operations. It also consumes events from auth, tenant,
    wallet, and integration-hub services for quota management and job lifecycle.

    - Event format: CloudEvents 1.0 specification
    - Partitioning: By tenant_id for multi-tenant isolation
    - Topic naming: eventzr.jobs.<entity>.<action>.v<N>
    - Outbox pattern: Events stored in KafkaOutbox table before publishing
    - DLQ: Failed events moved to KafkaDlq table after max retries
  contact:
    name: EventZR Platform Team
    email: platform@eventzr.com
    url: https://eventzr.com
  license:
    name: Proprietary
    url: https://eventzr.com/legal/license

servers:
  kafka-staging:
    url: boot-gpm4u49z.c2.kafka-serverless.us-east-1.amazonaws.com:9098
    protocol: kafka-secure
    protocolVersion: '3.6.0'
    description: Amazon MSK Serverless (staging)
    security:
      - saslAwsMskIam: []
    bindings:
      kafka:
        schemaRegistryUrl: ''
        schemaRegistryVendor: ''

defaultContentType: application/json

tags:
  - name: job-lifecycle
    description: Job creation, execution, completion
  - name: job-failure
    description: Job failures, retries, timeouts
  - name: dead-letter-queue
    description: DLQ operations
  - name: quota
    description: Quota enforcement events

# -- Published Channels --------------------------------------------------------

channels:
  # -- Job Lifecycle -----------------------------------------------------------

  eventzr.jobs.enqueued.v1:
    description: |
      Published when a new job is enqueued for processing.
      Consumed by: analytics-svc, notify-svc, Neptune KG sync
    bindings:
      kafka:
        partitions: 12
        replicas: 3
        topicConfiguration:
          retention.ms: 604800000
          cleanup.policy:
            - delete
    publish:
      operationId: publishJobEnqueued
      summary: Job enqueued
      tags:
        - name: job-lifecycle
      bindings:
        kafka:
          groupId:
            type: string
            enum:
              - jobs-svc-kg-sync
              - analytics-svc-consumer
              - notify-svc-consumer
          clientId:
            type: string
            enum:
              - jobs-svc
          bindingVersion: '0.5.0'
      message:
        $ref: '#/components/messages/JobEnqueued'

  eventzr.jobs.started.v1:
    description: |
      Published when a worker starts executing a job.
      Consumed by: analytics-svc, Neptune KG sync
    bindings:
      kafka:
        partitions: 12
        replicas: 3
        topicConfiguration:
          retention.ms: 604800000
          cleanup.policy:
            - delete
    publish:
      operationId: publishJobStarted
      summary: Job started
      tags:
        - name: job-lifecycle
      bindings:
        kafka:
          groupId:
            type: string
            enum:
              - jobs-svc-kg-sync
              - analytics-svc-consumer
          clientId:
            type: string
            enum:
              - jobs-svc
          bindingVersion: '0.5.0'
      message:
        $ref: '#/components/messages/JobStarted'

  eventzr.jobs.completed.v1:
    description: |
      Published when a job completes successfully.
      Consumed by: analytics-svc, notify-svc, Neptune KG sync
    bindings:
      kafka:
        partitions: 12
        replicas: 3
        topicConfiguration:
          retention.ms: 604800000
          cleanup.policy:
            - delete
    publish:
      operationId: publishJobCompleted
      summary: Job completed
      tags:
        - name: job-lifecycle
      bindings:
        kafka:
          groupId:
            type: string
            enum:
              - jobs-svc-kg-sync
              - analytics-svc-consumer
              - notify-svc-consumer
          clientId:
            type: string
            enum:
              - jobs-svc
          bindingVersion: '0.5.0'
      message:
        $ref: '#/components/messages/JobCompleted'

  eventzr.jobs.updated.v1:
    description: |
      Published when a job is updated (priority change, metadata update, etc.).
      Consumed by: analytics-svc
    bindings:
      kafka:
        partitions: 12
        replicas: 3
        topicConfiguration:
          retention.ms: 604800000
          cleanup.policy:
            - delete
    publish:
      operationId: publishJobUpdated
      summary: Job updated
      tags:
        - name: job-lifecycle
      bindings:
        kafka:
          groupId:
            type: string
            enum:
              - analytics-svc-consumer
          clientId:
            type: string
            enum:
              - jobs-svc
          bindingVersion: '0.5.0'
      message:
        $ref: '#/components/messages/JobUpdated'

  eventzr.jobs.cancelled.v1:
    description: |
      Published when a job is cancelled by a user or system action.
      Consumed by: analytics-svc, notify-svc
    bindings:
      kafka:
        partitions: 12
        replicas: 3
        topicConfiguration:
          retention.ms: 604800000
          cleanup.policy:
            - delete
    publish:
      operationId: publishJobCancelled
      summary: Job cancelled
      tags:
        - name: job-lifecycle
      bindings:
        kafka:
          groupId:
            type: string
            enum:
              - analytics-svc-consumer
              - notify-svc-consumer
          clientId:
            type: string
            enum:
              - jobs-svc
          bindingVersion: '0.5.0'
      message:
        $ref: '#/components/messages/JobCancelled'

  # -- Job Failure & Retry -----------------------------------------------------

  eventzr.jobs.failed.v1:
    description: |
      Published when a job execution fails. Includes whether a retry will be attempted.
      Consumed by: analytics-svc, notify-svc
    bindings:
      kafka:
        partitions: 12
        replicas: 3
        topicConfiguration:
          retention.ms: 604800000
          cleanup.policy:
            - delete
    publish:
      operationId: publishJobFailed
      summary: Job failed
      tags:
        - name: job-failure
      bindings:
        kafka:
          groupId:
            type: string
            enum:
              - analytics-svc-consumer
              - notify-svc-consumer
          clientId:
            type: string
            enum:
              - jobs-svc
          bindingVersion: '0.5.0'
      message:
        $ref: '#/components/messages/JobFailed'

  eventzr.jobs.retrying.v1:
    description: |
      Published when a failed job is scheduled for retry with exponential backoff.
      Consumed by: analytics-svc
    bindings:
      kafka:
        partitions: 12
        replicas: 3
        topicConfiguration:
          retention.ms: 604800000
          cleanup.policy:
            - delete
    publish:
      operationId: publishJobRetrying
      summary: Job retrying
      tags:
        - name: job-failure
      bindings:
        kafka:
          groupId:
            type: string
            enum:
              - analytics-svc-consumer
          clientId:
            type: string
            enum:
              - jobs-svc
          bindingVersion: '0.5.0'
      message:
        $ref: '#/components/messages/JobRetrying'

  eventzr.jobs.timeout.v1:
    description: |
      Published when a job exceeds its configured timeout duration.
      Consumed by: analytics-svc, notify-svc
    bindings:
      kafka:
        partitions: 12
        replicas: 3
        topicConfiguration:
          retention.ms: 604800000
          cleanup.policy:
            - delete
    publish:
      operationId: publishJobTimeout
      summary: Job timed out
      tags:
        - name: job-failure
      bindings:
        kafka:
          groupId:
            type: string
            enum:
              - analytics-svc-consumer
              - notify-svc-consumer
          clientId:
            type: string
            enum:
              - jobs-svc
          bindingVersion: '0.5.0'
      message:
        $ref: '#/components/messages/JobTimeout'

  # -- Dead Letter Queue -------------------------------------------------------

  eventzr.jobs.deadlettered.v1:
    description: |
      Published when a job is moved to the dead-letter queue after exhausting
      all retry attempts.
      Consumed by: analytics-svc, notify-svc
    bindings:
      kafka:
        partitions: 12
        replicas: 3
        topicConfiguration:
          retention.ms: 604800000
          cleanup.policy:
            - delete
    publish:
      operationId: publishJobDeadlettered
      summary: Job dead-lettered
      tags:
        - name: dead-letter-queue
      bindings:
        kafka:
          groupId:
            type: string
            enum:
              - analytics-svc-consumer
              - notify-svc-consumer
          clientId:
            type: string
            enum:
              - jobs-svc
          bindingVersion: '0.5.0'
      message:
        $ref: '#/components/messages/JobDeadlettered'

  eventzr.jobs.replayed.v1:
    description: |
      Published when a dead-lettered job is replayed (re-enqueued) for
      another processing attempt.
      Consumed by: analytics-svc
    bindings:
      kafka:
        partitions: 12
        replicas: 3
        topicConfiguration:
          retention.ms: 604800000
          cleanup.policy:
            - delete
    publish:
      operationId: publishJobReplayed
      summary: Job replayed from DLQ
      tags:
        - name: dead-letter-queue
      bindings:
        kafka:
          groupId:
            type: string
            enum:
              - analytics-svc-consumer
          clientId:
            type: string
            enum:
              - jobs-svc
          bindingVersion: '0.5.0'
      message:
        $ref: '#/components/messages/JobReplayed'

  # -- Quota -------------------------------------------------------------------

  eventzr.jobs.quota-exceeded.v1:
    description: |
      Published when a tenant exceeds their job quota for the current billing period.
      Consumed by: notify-svc, analytics-svc
    bindings:
      kafka:
        partitions: 12
        replicas: 3
        topicConfiguration:
          retention.ms: 604800000
          cleanup.policy:
            - delete
    publish:
      operationId: publishQuotaExceeded
      summary: Tenant quota exceeded
      tags:
        - name: quota
      bindings:
        kafka:
          groupId:
            type: string
            enum:
              - notify-svc-consumer
              - analytics-svc-consumer
          clientId:
            type: string
            enum:
              - jobs-svc
          bindingVersion: '0.5.0'
      message:
        $ref: '#/components/messages/QuotaExceeded'

  # -- Custom Events ------------------------------------------------------------

  'eventzr.jobs.custom.{type}.v1':
    description: |
      Published when a custom job event occurs. The {type} parameter in the
      topic name is replaced with the custom event type identifier.
      Consumed by: analytics-svc, notify-svc (depending on type)
    parameters:
      type:
        description: Custom event type identifier (e.g., "webhook-callback", "batch-complete")
        schema:
          type: string
    bindings:
      kafka:
        partitions: 12
        replicas: 3
        topicConfiguration:
          retention.ms: 604800000
          cleanup.policy:
            - delete
    publish:
      operationId: publishJobCustomEvent
      summary: Custom job event
      tags:
        - name: job-lifecycle
      bindings:
        kafka:
          groupId:
            type: string
            enum:
              - analytics-svc-consumer
              - notify-svc-consumer
          clientId:
            type: string
            enum:
              - jobs-svc
          bindingVersion: '0.5.0'
      message:
        $ref: '#/components/messages/JobCustomEvent'

# -- Components ----------------------------------------------------------------

components:
  securitySchemes:
    saslAwsMskIam:
      type: scramSha512
      description: AWS MSK IAM authentication via SASL/SCRAM

  messageTraits:
    CloudEventEnvelope:
      headers:
        type: object
        required:
          - ce_specversion
          - ce_type
          - ce_source
          - ce_id
          - ce_time
          - ce_tenantid
        properties:
          ce_specversion:
            type: string
            const: '1.0'
          ce_type:
            type: string
            description: 'CloudEvents type (e.g., eventzr.jobs.enqueued.v1)'
          ce_source:
            type: string
            const: 'https://eventzr.com/jobs-svc'
          ce_id:
            type: string
            format: uuid
          ce_time:
            type: string
            format: date-time
          ce_subject:
            type: string
            description: 'e.g., job/<jobId>'
          ce_datacontenttype:
            type: string
            const: application/json
          ce_tenantid:
            type: string
            format: uuid
            description: Tenant ID (also Kafka partition key)
          ce_actor:
            type: string
            description: User or system ID that triggered the event
      bindings:
        kafka:
          key:
            type: string
            format: uuid
            description: Kafka message key is always tenant_id
          bindingVersion: '0.5.0'

  schemas:
    CloudEventBase:
      type: object
      required: [specversion, type, source, subject, id, time, datacontenttype, tenantid, data]
      properties:
        specversion:
          type: string
          const: '1.0'
        type:
          type: string
          description: 'Topic-style: eventzr.jobs.<entity>.<action>.v<N>'
        source:
          type: string
          const: 'https://eventzr.com/jobs-svc'
        subject:
          type: string
          description: 'e.g., job/<jobId>'
        id:
          type: string
          format: uuid
        time:
          type: string
          format: date-time
        datacontenttype:
          type: string
          const: application/json
        tenantid:
          type: string
          format: uuid
        actor:
          type: string
        data:
          type: object

    JobBase:
      type: object
      required: [job_id, tenant_id]
      properties:
        job_id:
          type: string
          format: uuid
        tenant_id:
          type: string
          format: uuid

    JobEnqueuedData:
      type: object
      required: [job_id, tenant_id, type, priority, payload]
      properties:
        job_id:
          type: string
          format: uuid
        tenant_id:
          type: string
          format: uuid
        type:
          type: string
          description: Job type identifier (e.g., "email-send", "report-generate")
        priority:
          type: integer
          minimum: 1
          maximum: 9
          description: Job priority (1 = lowest, 9 = highest)
        payload:
          type: object
          description: Arbitrary job payload data
        run_at:
          type: string
          format: date-time
          nullable: true
          description: Scheduled execution time (null for immediate)
        cron_expression:
          type: string
          nullable: true
          description: Cron expression for recurring jobs (e.g., "0 */6 * * *")
        tags:
          type: array
          items:
            type: string
          nullable: true
          description: Tags for job categorization and filtering
        metadata:
          type: object
          nullable: true
          description: Additional metadata (correlation IDs, tracing context)

    JobStartedData:
      type: object
      required: [job_id, tenant_id, worker_id, attempt, started_at]
      properties:
        job_id:
          type: string
          format: uuid
        tenant_id:
          type: string
          format: uuid
        worker_id:
          type: string
          description: Identifier of the worker processing the job
        attempt:
          type: integer
          minimum: 1
          description: Current attempt number
        started_at:
          type: string
          format: date-time

    JobCompletedData:
      type: object
      required: [job_id, tenant_id, result, duration_ms, attempt]
      properties:
        job_id:
          type: string
          format: uuid
        tenant_id:
          type: string
          format: uuid
        result:
          type: object
          nullable: true
          description: Job execution result payload
        duration_ms:
          type: integer
          minimum: 0
          description: Total execution duration in milliseconds
        attempt:
          type: integer
          minimum: 1
          description: Attempt number that succeeded

    JobFailedData:
      type: object
      required: [job_id, tenant_id, error, attempt, max_attempts, will_retry]
      properties:
        job_id:
          type: string
          format: uuid
        tenant_id:
          type: string
          format: uuid
        error:
          type: string
          description: Error message or serialised error object
        attempt:
          type: integer
          minimum: 1
          description: Attempt number that failed
        max_attempts:
          type: integer
          minimum: 1
          description: Maximum configured retry attempts
        will_retry:
          type: boolean
          description: Whether the job will be retried

    JobCancelledData:
      type: object
      required: [job_id, tenant_id, cancelled_by]
      properties:
        job_id:
          type: string
          format: uuid
        tenant_id:
          type: string
          format: uuid
        cancelled_by:
          type: string
          description: User ID or system identifier that cancelled the job
        reason:
          type: string
          nullable: true
          description: Optional cancellation reason

    JobRetryingData:
      type: object
      required: [job_id, tenant_id, attempt, next_attempt, retry_at, backoff_ms]
      properties:
        job_id:
          type: string
          format: uuid
        tenant_id:
          type: string
          format: uuid
        attempt:
          type: integer
          minimum: 1
          description: Attempt number that just failed
        next_attempt:
          type: integer
          minimum: 2
          description: Upcoming attempt number
        retry_at:
          type: string
          format: date-time
          description: Scheduled retry time
        backoff_ms:
          type: integer
          minimum: 0
          description: Backoff delay in milliseconds before retry

    JobTimeoutData:
      type: object
      required: [job_id, tenant_id, timeout_ms, elapsed_ms, attempt]
      properties:
        job_id:
          type: string
          format: uuid
        tenant_id:
          type: string
          format: uuid
        timeout_ms:
          type: integer
          minimum: 0
          description: Configured timeout in milliseconds
        elapsed_ms:
          type: integer
          minimum: 0
          description: Actual elapsed time before timeout was triggered
        attempt:
          type: integer
          minimum: 1
          description: Attempt number that timed out

    JobDeadletteredData:
      type: object
      required: [job_id, tenant_id, reason, attempts_made, last_error]
      properties:
        job_id:
          type: string
          format: uuid
        tenant_id:
          type: string
          format: uuid
        reason:
          type: string
          description: Reason the job was moved to DLQ (e.g., "max_retries_exceeded")
        attempts_made:
          type: integer
          minimum: 1
          description: Total number of attempts before dead-lettering
        last_error:
          type: string
          description: Error message from the final failed attempt

    JobReplayedData:
      type: object
      required: [job_id, tenant_id, replayed_by, original_failure]
      properties:
        job_id:
          type: string
          format: uuid
        tenant_id:
          type: string
          format: uuid
        replayed_by:
          type: string
          description: User ID or system identifier that triggered the replay
        original_failure:
          type: string
          description: Original failure reason from the dead-lettered job

    JobUpdatedData:
      type: object
      required: [job_id, tenant_id, changes]
      properties:
        job_id:
          type: string
          format: uuid
        tenant_id:
          type: string
          format: uuid
        changes:
          type: object
          description: Key-value map of changed fields and their new values

    QuotaExceededData:
      type: object
      required: [tenant_id, quota_type, current_value, limit, tier]
      properties:
        tenant_id:
          type: string
          format: uuid
        quota_type:
          type: string
          description: Type of quota exceeded (e.g., "concurrent_jobs", "daily_jobs")
        current_value:
          type: integer
          minimum: 0
          description: Current usage value at time of violation
        limit:
          type: integer
          minimum: 0
          description: Configured quota limit for the tenant tier
        tier:
          type: string
          enum: [BASE, STUDENT, STARTUP, PRO, PROMAX, ENTERPRISE, CUSTOM]
          description: Tenant subscription tier

    JobCustomEventData:
      type: object
      required: [job_id, tenant_id, custom_type, payload]
      properties:
        job_id:
          type: string
          format: uuid
        tenant_id:
          type: string
          format: uuid
        custom_type:
          type: string
          description: Custom event type identifier (matches topic parameter)
        payload:
          type: object
          description: Arbitrary event payload specific to the custom type
        metadata:
          type: object
          nullable: true
          description: Additional metadata (correlation IDs, tracing context)

  messages:
    # -- Job Lifecycle Messages ------------------------------------------------

    JobEnqueued:
      name: JobEnqueued
      title: Job Enqueued
      summary: A new job was enqueued for processing
      contentType: application/json
      traits:
        - $ref: '#/components/messageTraits/CloudEventEnvelope'
      payload:
        allOf:
          - $ref: '#/components/schemas/CloudEventBase'
          - type: object
            properties:
              type:
                const: eventzr.jobs.enqueued.v1
              data:
                $ref: '#/components/schemas/JobEnqueuedData'

    JobStarted:
      name: JobStarted
      title: Job Started
      summary: A worker started executing a job
      contentType: application/json
      traits:
        - $ref: '#/components/messageTraits/CloudEventEnvelope'
      payload:
        allOf:
          - $ref: '#/components/schemas/CloudEventBase'
          - type: object
            properties:
              type:
                const: eventzr.jobs.started.v1
              data:
                $ref: '#/components/schemas/JobStartedData'

    JobCompleted:
      name: JobCompleted
      title: Job Completed
      summary: A job completed successfully
      contentType: application/json
      traits:
        - $ref: '#/components/messageTraits/CloudEventEnvelope'
      payload:
        allOf:
          - $ref: '#/components/schemas/CloudEventBase'
          - type: object
            properties:
              type:
                const: eventzr.jobs.completed.v1
              data:
                $ref: '#/components/schemas/JobCompletedData'

    JobUpdated:
      name: JobUpdated
      title: Job Updated
      summary: A job was updated
      contentType: application/json
      traits:
        - $ref: '#/components/messageTraits/CloudEventEnvelope'
      payload:
        allOf:
          - $ref: '#/components/schemas/CloudEventBase'
          - type: object
            properties:
              type:
                const: eventzr.jobs.updated.v1
              data:
                $ref: '#/components/schemas/JobUpdatedData'

    JobCancelled:
      name: JobCancelled
      title: Job Cancelled
      summary: A job was cancelled
      contentType: application/json
      traits:
        - $ref: '#/components/messageTraits/CloudEventEnvelope'
      payload:
        allOf:
          - $ref: '#/components/schemas/CloudEventBase'
          - type: object
            properties:
              type:
                const: eventzr.jobs.cancelled.v1
              data:
                $ref: '#/components/schemas/JobCancelledData'

    # -- Job Failure Messages --------------------------------------------------

    JobFailed:
      name: JobFailed
      title: Job Failed
      summary: A job execution failed
      contentType: application/json
      traits:
        - $ref: '#/components/messageTraits/CloudEventEnvelope'
      payload:
        allOf:
          - $ref: '#/components/schemas/CloudEventBase'
          - type: object
            properties:
              type:
                const: eventzr.jobs.failed.v1
              data:
                $ref: '#/components/schemas/JobFailedData'

    JobRetrying:
      name: JobRetrying
      title: Job Retrying
      summary: A failed job is scheduled for retry
      contentType: application/json
      traits:
        - $ref: '#/components/messageTraits/CloudEventEnvelope'
      payload:
        allOf:
          - $ref: '#/components/schemas/CloudEventBase'
          - type: object
            properties:
              type:
                const: eventzr.jobs.retrying.v1
              data:
                $ref: '#/components/schemas/JobRetryingData'

    JobTimeout:
      name: JobTimeout
      title: Job Timed Out
      summary: A job exceeded its configured timeout
      contentType: application/json
      traits:
        - $ref: '#/components/messageTraits/CloudEventEnvelope'
      payload:
        allOf:
          - $ref: '#/components/schemas/CloudEventBase'
          - type: object
            properties:
              type:
                const: eventzr.jobs.timeout.v1
              data:
                $ref: '#/components/schemas/JobTimeoutData'

    # -- Dead Letter Queue Messages --------------------------------------------

    JobDeadlettered:
      name: JobDeadlettered
      title: Job Dead-Lettered
      summary: A job was moved to the dead-letter queue
      contentType: application/json
      traits:
        - $ref: '#/components/messageTraits/CloudEventEnvelope'
      payload:
        allOf:
          - $ref: '#/components/schemas/CloudEventBase'
          - type: object
            properties:
              type:
                const: eventzr.jobs.deadlettered.v1
              data:
                $ref: '#/components/schemas/JobDeadletteredData'

    JobReplayed:
      name: JobReplayed
      title: Job Replayed
      summary: A dead-lettered job was replayed
      contentType: application/json
      traits:
        - $ref: '#/components/messageTraits/CloudEventEnvelope'
      payload:
        allOf:
          - $ref: '#/components/schemas/CloudEventBase'
          - type: object
            properties:
              type:
                const: eventzr.jobs.replayed.v1
              data:
                $ref: '#/components/schemas/JobReplayedData'

    # -- Quota Messages --------------------------------------------------------

    QuotaExceeded:
      name: QuotaExceeded
      title: Quota Exceeded
      summary: A tenant exceeded their job quota
      contentType: application/json
      traits:
        - $ref: '#/components/messageTraits/CloudEventEnvelope'
      payload:
        allOf:
          - $ref: '#/components/schemas/CloudEventBase'
          - type: object
            properties:
              type:
                const: eventzr.jobs.quota-exceeded.v1
              data:
                $ref: '#/components/schemas/QuotaExceededData'

    # -- Custom Event Messages --------------------------------------------------

    JobCustomEvent:
      name: JobCustomEvent
      title: Job Custom Event
      summary: A custom job event was published
      contentType: application/json
      traits:
        - $ref: '#/components/messageTraits/CloudEventEnvelope'
      payload:
        allOf:
          - $ref: '#/components/schemas/CloudEventBase'
          - type: object
            properties:
              type:
                const: eventzr.jobs.custom.v1
              data:
                $ref: '#/components/schemas/JobCustomEventData'
