asyncapi: 2.6.0
id: urn:gpuaas:asyncapi:realtime-events
info:
  title: GPUaaS Realtime and Event Contracts
  version: 0.3.0-draft
  description: |
    Async contracts for websocket sessions and internal domain events.
  contact:
    name: GPUaaS Platform Team
    url: https://example.com/gpuaas
    email: platform@gpuaas.local
  license:
    name: Proprietary
tags:
  - name: gpuaas
    description: GPUaaS realtime websocket and internal domain event contracts.

defaultContentType: application/json

servers:
  ws-public:
    url: localhost:8090
    protocol: ws
    description: Public websocket endpoint (development)
  nats-internal:
    url: nats://nats:4222
    protocol: nats
    description: Internal event bus (draft)

channels:
  ws/terminal:
    servers: [ws-public]
    description: |
      Interactive terminal websocket stream.

      Auth flow (required before connecting):
        1. Client calls POST /api/v1/allocations/{allocation_id}/terminal-token
           to obtain a short-lived (≤300s), single-use, allocation-scoped token.
        2. Client opens this WebSocket and presents the token:
             - Browser client: via Sec-WebSocket-Protocol
             - Non-browser client: via Authorization: Bearer <terminal-token>
        3. MVP (Day 1): Terminal Gateway validates token scope and allocation ownership
           using cache/DB checks for correctness.
           Single-use enforcement must use atomic consume semantics (e.g. Redis GETDEL).
           Phase-2 target: signed terminal tokens with local verification to reduce
           per-connection DB dependency.
        4. On token validation failure the gateway closes the connection with 4401.
        5. Gateway sends a JSON `TerminalControl` frame for session lifecycle events:
             - `session_ready` after PTY attach succeeds
             - `session_error` for non-fatal stream errors (includes `retryable`)
             - `session_closed` before normal close
           Close codes: 4401 auth failure, 4403 authorization denied, 1011 internal error.

      Query-token auth (e.g. ?token=) is explicitly prohibited.
    parameters:
      allocation_id:
        description: Allocation UUID for this terminal session.
        schema:
          type: string
          format: uuid
    bindings:
      ws:
        method: GET
        headers:
          type: object
          properties:
            Authorization:
              type: string
              description: >
                Optional for non-browser clients: Bearer <terminal-token> obtained
                from POST /api/v1/allocations/{allocation_id}/terminal-token.
            Sec-WebSocket-Protocol:
              type: string
              description: >
                Required auth transport for browser clients. Carries short-lived
                terminal token value.
    publish:
      operationId: clientTerminalInput
      summary: Client -> gateway terminal input or resize messages
      message:
        oneOf:
          - $ref: '#/components/messages/TerminalResize'
          - $ref: '#/components/messages/TerminalHeartbeat'
          - $ref: '#/components/messages/TerminalInputBinary'
    subscribe:
      operationId: serverTerminalOutput
      summary: Gateway -> client terminal output stream and control events
      message:
        oneOf:
          - $ref: '#/components/messages/TerminalControl'
          - $ref: '#/components/messages/TerminalOutputText'
          - $ref: '#/components/messages/TerminalOutputBinary'

  ws/notifications:
    servers: [ws-public]
    description: |
      Planned dedicated user notification channel.
      Interim state: prototype may multiplex notification messages on terminal channel.
      Target state: move all notification traffic to this dedicated channel.
    bindings:
      ws:
        method: GET
        headers:
          type: object
          properties:
            Authorization:
              type: string
              description: Optional for non-browser clients.
            Sec-WebSocket-Protocol:
              type: string
              description: Required auth transport for browser clients.
    subscribe:
      operationId: serverUserNotification
      message:
        $ref: '#/components/messages/UserNotification'

  platform.billing.low_balance_warning:
    servers: [nats-internal]
    description: Emitted when user enters low-balance state.
    x-nats-config:
      ack_policy: explicit
      ack_wait: 30s
      max_deliver: 10
      dlq_subject: dlq.platform.billing.low_balance_warning
    subscribe:
      operationId: onLowBalanceWarning
      message:
        $ref: '#/components/messages/LowBalanceWarningEvent'

  platform.billing.balance_depleted:
    servers: [nats-internal]
    description: Emitted when user balance depletes and force release is required.
    x-nats-config:
      ack_policy: explicit
      ack_wait: 30s
      max_deliver: 10
      dlq_subject: dlq.platform.billing.balance_depleted
    subscribe:
      operationId: onBalanceDepleted
      message:
        $ref: '#/components/messages/BalanceDepletedEvent'

  platform.billing.auto_release_pending:
    servers: [nats-internal]
    description: Emitted when projected depletion indicates forced release is approaching.
    x-nats-config:
      ack_policy: explicit
      ack_wait: 30s
      max_deliver: 10
      dlq_subject: dlq.platform.billing.auto_release_pending
    subscribe:
      operationId: onAutoReleasePending
      message:
        $ref: '#/components/messages/AutoReleasePendingEvent'

  platform.billing.budget_threshold_crossed:
    servers: [nats-internal]
    description: Emitted when a notification-only tenant or project budget threshold is crossed.
    x-nats-config:
      ack_policy: explicit
      ack_wait: 30s
      max_deliver: 10
      dlq_subject: dlq.platform.billing.budget_threshold_crossed
    subscribe:
      operationId: onBudgetThresholdCrossed
      message:
        $ref: '#/components/messages/BudgetThresholdCrossedEvent'

  platform.billing.usage.metered:
    servers: [nats-internal]
    description: Product-neutral usage interval emitted by GPUaaS allocation, App Platform runtime, or future metered products for billing ingestion.
    x-nats-config:
      ack_policy: explicit
      ack_wait: 30s
      max_deliver: 10
      dlq_subject: dlq.platform.billing.usage.metered
    publish:
      operationId: publishUsageMetered
      message:
        $ref: '#/components/messages/UsageMeteredEvent'

  platform.payments.balance_credited:
    servers: [nats-internal]
    description: Emitted after successful idempotent payment credit posting.
    x-nats-config:
      ack_policy: explicit
      ack_wait: 30s
      max_deliver: 10
      dlq_subject: dlq.platform.payments.balance_credited
    publish:
      operationId: publishBalanceCredited
      message:
        $ref: '#/components/messages/BalanceCreditedEvent'

  platform.payments.reconcile_failed:
    servers: [nats-internal]
    description: Emitted when provider/session reconciliation fails and requires support follow-up.
    x-nats-config:
      ack_policy: explicit
      ack_wait: 30s
      max_deliver: 10
      dlq_subject: dlq.platform.payments.reconcile_failed
    publish:
      operationId: publishReconcileFailed
      message:
        $ref: '#/components/messages/ReconcileFailedEvent'

  gpuaas.provisioning.requested:
    servers: [nats-internal]
    x-nats-config:
      ack_policy: explicit
      ack_wait: 45s
      max_deliver: 20
      dlq_subject: dlq.gpuaas.provisioning.requested
    publish:
      operationId: publishProvisionRequested
      message:
        $ref: '#/components/messages/ProvisionRequestedEvent'

  gpuaas.provisioning.active:
    servers: [nats-internal]
    x-nats-config:
      ack_policy: explicit
      ack_wait: 30s
      max_deliver: 10
      dlq_subject: dlq.gpuaas.provisioning.active
    publish:
      operationId: publishAllocationActive
      message:
        $ref: '#/components/messages/AllocationActiveEvent'

  gpuaas.provisioning.failed:
    servers: [nats-internal]
    x-nats-config:
      ack_policy: explicit
      ack_wait: 30s
      max_deliver: 10
      dlq_subject: dlq.gpuaas.provisioning.failed
    publish:
      operationId: publishProvisionFailed
      message:
        $ref: '#/components/messages/ProvisionFailedEvent'

  gpuaas.provisioning.releasing.requested:
    servers: [nats-internal]
    x-nats-config:
      ack_policy: explicit
      ack_wait: 45s
      max_deliver: 20
      dlq_subject: dlq.gpuaas.provisioning.releasing.requested
    publish:
      operationId: publishReleasingRequested
      message:
        $ref: '#/components/messages/ReleasingRequestedEvent'

  gpuaas.provisioning.releasing.completed:
    servers: [nats-internal]
    x-nats-config:
      ack_policy: explicit
      ack_wait: 30s
      max_deliver: 10
      dlq_subject: dlq.gpuaas.provisioning.releasing.completed
    publish:
      operationId: publishReleasingCompleted
      message:
        $ref: '#/components/messages/ReleasingCompletedEvent'

  gpuaas.provisioning.force_release_requested:
    servers: [nats-internal]
    description: Requests forced release for specific active allocations.
    x-nats-config:
      ack_policy: explicit
      ack_wait: 45s
      max_deliver: 20
      dlq_subject: dlq.gpuaas.provisioning.force_release_requested
    publish:
      operationId: publishForceReleaseRequested
      message:
        $ref: '#/components/messages/ForceReleaseRequestedEvent'

  gpuaas.provisioning.release_failed:
    servers: [nats-internal]
    description: |
      Published by the provisioning worker when all release retries are exhausted
      and the allocation cannot be cleaned up automatically. Consumers must stop
      billing accrual and alert the user/admin. The node remains assigned until
      an admin manually retries via POST /api/v1/admin/allocations/{id}/force-release.
    x-nats-config:
      ack_policy: explicit
      ack_wait: 30s
      max_deliver: 10
      dlq_subject: dlq.gpuaas.provisioning.release_failed
    publish:
      operationId: publishReleaseFailed
      message:
        $ref: '#/components/messages/ReleaseFailedEvent'

  storage.attachment.requested:
    servers: [nats-internal]
    description: Emitted when a storage attach workflow is accepted.
    x-nats-config:
      ack_policy: explicit
      ack_wait: 30s
      max_deliver: 10
      dlq_subject: dlq.storage.attachment.requested
    publish:
      operationId: publishStorageAttachmentRequested
      message:
        $ref: '#/components/messages/StorageAttachmentRequestedEvent'

  storage.attachment.detach_requested:
    servers: [nats-internal]
    description: Emitted when a storage detach workflow is accepted.
    x-nats-config:
      ack_policy: explicit
      ack_wait: 30s
      max_deliver: 10
      dlq_subject: dlq.storage.attachment.detach_requested
    publish:
      operationId: publishStorageAttachmentDetachRequested
      message:
        $ref: '#/components/messages/StorageAttachmentDetachRequestedEvent'

  storage.attachment.mounted:
    servers: [nats-internal]
    description: Emitted when node-agent mount verification succeeds.
    x-nats-config:
      ack_policy: explicit
      ack_wait: 30s
      max_deliver: 10
      dlq_subject: dlq.storage.attachment.mounted
    publish:
      operationId: publishStorageAttachmentMounted
      message:
        $ref: '#/components/messages/StorageAttachmentMountedEvent'

  storage.attachment.failed:
    servers: [nats-internal]
    description: Emitted when a storage attach workflow fails and compensation is complete or recorded.
    x-nats-config:
      ack_policy: explicit
      ack_wait: 30s
      max_deliver: 10
      dlq_subject: dlq.storage.attachment.failed
    publish:
      operationId: publishStorageAttachmentFailed
      message:
        $ref: '#/components/messages/StorageAttachmentFailedEvent'

  storage.attachment.detached:
    servers: [nats-internal]
    description: Emitted when a storage attachment is detached. Persistent storage is not deleted.
    x-nats-config:
      ack_policy: explicit
      ack_wait: 30s
      max_deliver: 10
      dlq_subject: dlq.storage.attachment.detached
    publish:
      operationId: publishStorageAttachmentDetached
      message:
        $ref: '#/components/messages/StorageAttachmentDetachedEvent'

  storage.attachment.detach_failed:
    servers: [nats-internal]
    description: Emitted when detach retries are exhausted and operator retry is required.
    x-nats-config:
      ack_policy: explicit
      ack_wait: 30s
      max_deliver: 10
      dlq_subject: dlq.storage.attachment.detach_failed
    publish:
      operationId: publishStorageAttachmentDetachFailed
      message:
        $ref: '#/components/messages/StorageAttachmentDetachFailedEvent'

  appplatform.catalog.entitlement.updated:
    servers: [nats-internal]
    description: Emitted when a project app entitlement is created or updated.
    x-nats-config:
      ack_policy: explicit
      ack_wait: 30s
      max_deliver: 10
      dlq_subject: dlq.appplatform.catalog.entitlement.updated
    publish:
      operationId: publishAppEntitlementUpdated
      message:
        $ref: '#/components/messages/AppEntitlementUpdatedEvent'

  appplatform.runtime.instance.requested:
    servers: [nats-internal]
    description: Emitted when an app instance deployment request is accepted.
    x-nats-config:
      ack_policy: explicit
      ack_wait: 45s
      max_deliver: 20
      dlq_subject: dlq.appplatform.runtime.instance.requested
    publish:
      operationId: publishAppInstanceRequested
      message:
        $ref: '#/components/messages/AppInstanceRequestedEvent'

  appplatform.runtime.instance.running:
    servers: [nats-internal]
    description: Emitted when an app instance reaches running state.
    x-nats-config:
      ack_policy: explicit
      ack_wait: 30s
      max_deliver: 10
      dlq_subject: dlq.appplatform.runtime.instance.running
    publish:
      operationId: publishAppInstanceRunning
      message:
        $ref: '#/components/messages/AppInstanceRunningEvent'

  appplatform.runtime.instance.failed:
    servers: [nats-internal]
    description: Emitted when an app instance deployment or runtime transitions to failed.
    x-nats-config:
      ack_policy: explicit
      ack_wait: 30s
      max_deliver: 10
      dlq_subject: dlq.appplatform.runtime.instance.failed
    publish:
      operationId: publishAppInstanceFailed
      message:
        $ref: '#/components/messages/AppInstanceFailedEvent'

  appplatform.runtime.instance.deleting:
    servers: [nats-internal]
    description: Emitted when app instance deletion is requested and accepted.
    x-nats-config:
      ack_policy: explicit
      ack_wait: 30s
      max_deliver: 10
      dlq_subject: dlq.appplatform.runtime.instance.deleting
    publish:
      operationId: publishAppInstanceDeleting
      message:
        $ref: '#/components/messages/AppInstanceDeletingEvent'

  appplatform.runtime.instance.upgrade_requested:
    servers: [nats-internal]
    description: Emitted when an app instance upgrade is requested and accepted.
    x-nats-config:
      ack_policy: explicit
      ack_wait: 30s
      max_deliver: 10
      dlq_subject: dlq.appplatform.runtime.instance.upgrade_requested
    publish:
      operationId: publishAppInstanceUpgradeRequested
      message:
        $ref: '#/components/messages/AppInstanceUpgradeRequestedEvent'

  appplatform.runtime.instance.rollback_requested:
    servers: [nats-internal]
    description: Emitted when an app instance rollback is requested and accepted.
    x-nats-config:
      ack_policy: explicit
      ack_wait: 30s
      max_deliver: 10
      dlq_subject: dlq.appplatform.runtime.instance.rollback_requested
    publish:
      operationId: publishAppInstanceRollbackRequested
      message:
        $ref: '#/components/messages/AppInstanceRollbackRequestedEvent'

  appplatform.runtime.instance.decommission_requested:
    servers: [nats-internal]
    description: Emitted when an app instance decommission is requested and accepted.
    x-nats-config:
      ack_policy: explicit
      ack_wait: 30s
      max_deliver: 10
      dlq_subject: dlq.appplatform.runtime.instance.decommission_requested
    publish:
      operationId: publishAppInstanceDecommissionRequested
      message:
        $ref: '#/components/messages/AppInstanceDecommissionRequestedEvent'

  appplatform.runtime.instance.stop_requested:
    servers: [nats-internal]
    description: Emitted when a non-destructive app instance stop is requested and accepted.
    x-nats-config:
      ack_policy: explicit
      ack_wait: 30s
      max_deliver: 10
      dlq_subject: dlq.appplatform.runtime.instance.stop_requested
    publish:
      operationId: publishAppInstanceStopRequested
      message:
        $ref: '#/components/messages/AppInstanceStopRequestedEvent'

  appplatform.runtime.instance.start_requested:
    servers: [nats-internal]
    description: Emitted when a stopped app instance start is requested and accepted.
    x-nats-config:
      ack_policy: explicit
      ack_wait: 30s
      max_deliver: 10
      dlq_subject: dlq.appplatform.runtime.instance.start_requested
    publish:
      operationId: publishAppInstanceStartRequested
      message:
        $ref: '#/components/messages/AppInstanceStartRequestedEvent'

  appplatform.runtime.instance.restart_requested:
    servers: [nats-internal]
    description: Emitted when a non-destructive app instance restart is requested and accepted.
    x-nats-config:
      ack_policy: explicit
      ack_wait: 30s
      max_deliver: 10
      dlq_subject: dlq.appplatform.runtime.instance.restart_requested
    publish:
      operationId: publishAppInstanceRestartRequested
      message:
        $ref: '#/components/messages/AppInstanceRestartRequestedEvent'

  appplatform.runtime.instance.deleted:
    servers: [nats-internal]
    description: Emitted when app instance deletion is completed.
    x-nats-config:
      ack_policy: explicit
      ack_wait: 30s
      max_deliver: 10
      dlq_subject: dlq.appplatform.runtime.instance.deleted
    publish:
      operationId: publishAppInstanceDeleted
      message:
        $ref: '#/components/messages/AppInstanceDeletedEvent'

  appplatform.runtime.shared_runtime.requested:
    servers: [nats-internal]
    description: Emitted when a tenant-owned shared runtime deployment request is accepted.
    x-nats-config:
      ack_policy: explicit
      ack_wait: 45s
      max_deliver: 20
      dlq_subject: dlq.appplatform.runtime.shared_runtime.requested
    publish:
      operationId: publishSharedAppRuntimeRequested
      message:
        $ref: '#/components/messages/SharedAppRuntimeRequestedEvent'

  appplatform.runtime.shared_runtime.running:
    servers: [nats-internal]
    description: Emitted when a tenant-owned shared runtime reaches running state.
    x-nats-config:
      ack_policy: explicit
      ack_wait: 30s
      max_deliver: 10
      dlq_subject: dlq.appplatform.runtime.shared_runtime.running
    publish:
      operationId: publishSharedAppRuntimeRunning
      message:
        $ref: '#/components/messages/SharedAppRuntimeRunningEvent'

  appplatform.runtime.shared_runtime.failed:
    servers: [nats-internal]
    description: Emitted when a tenant-owned shared runtime transitions to failed.
    x-nats-config:
      ack_policy: explicit
      ack_wait: 30s
      max_deliver: 10
      dlq_subject: dlq.appplatform.runtime.shared_runtime.failed
    publish:
      operationId: publishSharedAppRuntimeFailed
      message:
        $ref: '#/components/messages/SharedAppRuntimeFailedEvent'

  appplatform.runtime.shared_runtime.deleting:
    servers: [nats-internal]
    description: Emitted when tenant-owned shared runtime deletion is requested and accepted.
    x-nats-config:
      ack_policy: explicit
      ack_wait: 30s
      max_deliver: 10
      dlq_subject: dlq.appplatform.runtime.shared_runtime.deleting
    publish:
      operationId: publishSharedAppRuntimeDeleting
      message:
        $ref: '#/components/messages/SharedAppRuntimeDeletingEvent'

  appplatform.runtime.shared_runtime.deleted:
    servers: [nats-internal]
    description: Emitted when tenant-owned shared runtime deletion is completed.
    x-nats-config:
      ack_policy: explicit
      ack_wait: 30s
      max_deliver: 10
      dlq_subject: dlq.appplatform.runtime.shared_runtime.deleted
    publish:
      operationId: publishSharedAppRuntimeDeleted
      message:
        $ref: '#/components/messages/SharedAppRuntimeDeletedEvent'

  appplatform.artifact.registered:
    servers: [nats-internal]
    description: Emitted when an OCI artifact digest is registered for a project app.
    x-nats-config:
      ack_policy: explicit
      ack_wait: 30s
      max_deliver: 10
      dlq_subject: dlq.appplatform.artifact.registered
    publish:
      operationId: publishAppArtifactRegistered
      message:
        $ref: '#/components/messages/AppArtifactRegisteredEvent'

  appplatform.artifact.promoted:
    servers: [nats-internal]
    description: Emitted when a registered OCI artifact is promoted to a named channel.
    x-nats-config:
      ack_policy: explicit
      ack_wait: 30s
      max_deliver: 10
      dlq_subject: dlq.appplatform.artifact.promoted
    publish:
      operationId: publishAppArtifactPromoted
      message:
        $ref: '#/components/messages/AppArtifactPromotedEvent'

  platform.artifact.verified:
    servers: [nats-internal]
    description: Emitted when a registered app artifact trust state becomes verified.
    x-nats-config:
      ack_policy: explicit
      ack_wait: 30s
      max_deliver: 10
      dlq_subject: dlq.platform.artifact.verified
    publish:
      operationId: publishAppArtifactVerified
      message:
        $ref: '#/components/messages/AppArtifactVerifiedEvent'

  platform.artifact.revoked:
    servers: [nats-internal]
    description: Emitted when trust is revoked for a registered app artifact.
    x-nats-config:
      ack_policy: explicit
      ack_wait: 30s
      max_deliver: 10
      dlq_subject: dlq.platform.artifact.revoked
    publish:
      operationId: publishAppArtifactRevoked
      message:
        $ref: '#/components/messages/AppArtifactRevokedEvent'

  appplatform.artifact.deprecated:
    servers: [nats-internal]
    description: Emitted when a registered OCI artifact is deprecated.
    x-nats-config:
      ack_policy: explicit
      ack_wait: 30s
      max_deliver: 10
      dlq_subject: dlq.appplatform.artifact.deprecated
    publish:
      operationId: publishAppArtifactDeprecated
      message:
        $ref: '#/components/messages/AppArtifactDeprecatedEvent'

  appplatform.artifact.retired:
    servers: [nats-internal]
    description: Emitted when a registered OCI artifact is retired.
    x-nats-config:
      ack_policy: explicit
      ack_wait: 30s
      max_deliver: 10
      dlq_subject: dlq.appplatform.artifact.retired
    publish:
      operationId: publishAppArtifactRetired
      message:
        $ref: '#/components/messages/AppArtifactRetiredEvent'

components:
  schemas:
    EventMetadata:
      type: object
      additionalProperties: false
      properties:
        event_id:
          type: string
          format: uuid
        event_type:
          type: string
        occurred_at:
          type: string
          format: date-time
        version:
          type: string
        correlation_id:
          type: string
      required: [event_id, event_type, occurred_at, version, correlation_id]

    UserNotificationPayload:
      type: object
      additionalProperties: false
      properties:
        id:
          type: string
          format: uuid
        occurred_at:
          type: string
          format: date-time
        severity:
          type: string
          enum: [info, warning, error]
        type:
          type: string
          enum: [low_balance_warning, balance_depleted, info]
        title:
          type: string
        message:
          type: string
        action_url:
          type: string
          format: uri
          nullable: true
      required: [id, occurred_at, severity, type, title, message]

    NodeCleanupTaskType:
      type: string
      description: >
        Node-agent cleanup task contracts used by MAAS soft-reset, storage-cleanup,
        and worker cleanup orchestration. Execution is implemented by the node-agent;
        this schema defines the typed task contract.
      enum:
        - node.gpu_scrub
        - node.storage_cleanup
        - node.validate_clean

    NodeGPUScrubTaskParams:
      type: object
      additionalProperties: false
      properties:
        allocation_id:
          type: string
          format: uuid
        mode:
          type: string
          enum: [reset, verify_only]
        gpu_pci_addresses:
          type: array
          items:
            type: string
            pattern: '^[0-9a-fA-F]{4}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}\.[0-7]$'
        container_runtime:
          type: string
          enum: [docker, containerd, none]
        reason:
          type: string
      required: [mode, reason]

    NodeStorageCleanupTaskParams:
      type: object
      additionalProperties: false
      properties:
        allocation_id:
          type: string
          format: uuid
        paths:
          type: array
          minItems: 1
          items:
            type: string
        delete_empty_dirs:
          type: boolean
        wipe_block_devices:
          type: array
          items:
            type: string
        reason:
          type: string
      required: [paths, reason]

    NodeValidateCleanTaskParams:
      type: object
      additionalProperties: false
      properties:
        allocation_id:
          type: string
          format: uuid
        checks:
          type: array
          minItems: 1
          items:
            type: string
            enum: [gpu_processes, runtime_containers, mounts, paths, block_devices]
        paths:
          type: array
          items:
            type: string
        block_devices:
          type: array
          items:
            type: string
        gpu_pci_addresses:
          type: array
          items:
            type: string
            pattern: '^[0-9a-fA-F]{4}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}\.[0-7]$'
        reason:
          type: string
      required: [checks, reason]

    NodeCleanupTaskResult:
      type: object
      additionalProperties: false
      properties:
        task_type:
          $ref: '#/components/schemas/NodeCleanupTaskType'
        allocation_id:
          type: string
          format: uuid
        clean:
          type: boolean
        scrubbed:
          type: boolean
        cleaned:
          type: boolean
        findings:
          type: array
          items:
            type: object
            additionalProperties: true
        proof:
          type: object
          additionalProperties: true
      required: [task_type]

    TerminalControlPayload:
      type: object
      additionalProperties: false
      properties:
        type:
          type: string
          enum: [session_ready, session_error, session_closed]
        code:
          type: string
          description: Stable machine-readable terminal control code.
          enum:
            - normal_close        # clean close, no error
            - allocation_released # allocation release triggered session close
            - session_timeout     # terminal.session_max_ttl_seconds exceeded
            - node_stream_dropped # node-agent relay stream dropped (retryable)
            - open_failed         # terminal.open task failed or rejected by node
            - auth_failed         # terminal token invalid or unauthorized
            - admin_terminate     # admin-initiated session close
        session_id:
          type: string
          format: uuid
          description: Session identifier for correlation across client reconnects.
        message:
          type: string
        error_code:
          type: string
          description: Optional node-agent terminal startup failure code for UI/operator correlation.
          nullable: true
        error_message:
          type: string
          description: Optional human-safe node-agent terminal startup failure message.
          nullable: true
        runtime_username:
          type: string
          description: Optional allocation runtime username involved in terminal startup failure.
          nullable: true
        retryable:
          type: boolean
      required: [type, code, session_id, message, retryable]

    TerminalOpenTaskParams:
      type: object
      additionalProperties: false
      description: |
        Parameters for the `terminal.open` signed node task.
        Dispatched by the terminal gateway (via api.internal relay) to the node-agent
        to authorize and open a PTY session for the named OS user.
        This is a discrete signed task; I/O then flows on a separate stream channel.
      properties:
        session_id:
          type: string
          format: uuid
          description: Stable session identifier — enforced by node seen-set against replay.
        user_id:
          type: string
          format: uuid
        allocation_id:
          type: string
          format: uuid
        username:
          type: string
          description: Stable per-user OS username from platform_iam_user_posix_identities. Must exist on the node.
          pattern: '^[a-z_][a-z0-9_-]{0,31}$'
        uid:
          type: integer
          minimum: 10000
          maximum: 199999
          description: Stable per-user POSIX UID.
        gid:
          type: integer
          minimum: 10000
          maximum: 199999
          description: Stable per-user POSIX primary GID.
        supplemental_gids:
          type: array
          description: Optional supplemental groups for cluster interoperability.
          items:
            type: integer
            minimum: 10000
            maximum: 199999
        session_ttl_seconds:
          type: integer
          description: Maximum session lifetime in seconds. Enforced by node-agent. Sourced from policy key terminal.session_max_ttl_seconds.
          minimum: 300
          maximum: 86400
        cols:
          type: integer
          description: Initial PTY width in columns.
          minimum: 1
          maximum: 512
        rows:
          type: integer
          description: Initial PTY height in rows.
          minimum: 1
          maximum: 512
      required: [session_id, user_id, allocation_id, username, uid, gid, session_ttl_seconds, cols, rows]

    TerminalStreamFrame:
      type: object
      additionalProperties: false
      description: |
        Wire frame for terminal I/O relay between gateway and node-agent via api.internal.
        Not exposed externally on the public WS channel; governs the internal
        HTTP/2 streaming relay used by the TerminalSessionBroker implementation (Option A),
        transported on `POST /internal/v1/nodes/{node_id}/terminal/stream`.
        Gateway sends upstream frames (stdin, resize, close-request).
        Node-agent sends downstream frames (stdout/stderr, close-confirm).
      properties:
        session_id:
          type: string
          format: uuid
        type:
          type: string
          enum: [data, resize, close]
          description: |
            data:   stdin/stdout/stderr bytes (base64-encoded in `payload`).
            resize: PTY resize event (`cols` and `rows` set).
            close:  graceful close request (upstream) or close confirmation (downstream).
        direction:
          type: string
          enum: [upstream, downstream]
          description: |
            upstream:   gateway -> node-agent (stdin, resize, close-request).
            downstream: node-agent -> gateway (stdout/stderr, close-confirm).
        payload:
          type: string
          format: byte
          description: Base64-encoded raw bytes (present for type=data frames only).
          nullable: true
        close_reason:
          type: string
          description: Close reason code (present on type=close frames).
          enum:
            - normal_close
            - allocation_released
            - session_timeout
            - node_stream_dropped
            - open_failed
            - admin_terminate
          nullable: true
        error_code:
          type: string
          description: Stable node-agent terminal startup failure code for close frames, when available.
          nullable: true
        error_message:
          type: string
          description: Human-safe node-agent terminal startup failure message for close frames, when available.
          nullable: true
        runtime_username:
          type: string
          description: Allocation runtime username that node-agent attempted to open for terminal startup failures.
          nullable: true
        cols:
          type: integer
          description: New column count (present on type=resize frames).
          minimum: 1
          maximum: 512
          nullable: true
        rows:
          type: integer
          description: New row count (present on type=resize frames).
          minimum: 1
          maximum: 512
          nullable: true
      required: [session_id, type, direction]

    ProvisionRequestedPayload:
      type: object
      additionalProperties: false
      properties:
        allocation_id:
          type: string
          format: uuid
        user_id:
          type: string
          format: uuid
        node_id:
          type: string
          format: uuid
          nullable: true
        scheduler_type:
          type: string
          enum: [bare_metal, slurm, k8s, ray]
        org_id:
          type: string
          format: uuid
          nullable: true
        project_id:
          type: string
          format: uuid
          nullable: true
      required: [allocation_id, user_id, scheduler_type]

    AllocationActivePayload:
      type: object
      additionalProperties: false
      properties:
        allocation_id:
          type: string
          format: uuid
        user_id:
          type: string
          format: uuid
        node_id:
          type: string
          format: uuid
        active_at:
          type: string
          format: date-time
        project_id:
          type: string
          format: uuid
          nullable: true
      required: [allocation_id, user_id, node_id, active_at]

    ProvisionFailedPayload:
      type: object
      additionalProperties: false
      properties:
        allocation_id:
          type: string
          format: uuid
        user_id:
          type: string
          format: uuid
        project_id:
          type: string
          format: uuid
          nullable: true
        reason:
          type: string
      required: [allocation_id, user_id, reason]

    ForceReleaseRequestedPayload:
      type: object
      additionalProperties: false
      properties:
        user_id:
          type: string
          format: uuid
        allocation_ids:
          type: array
          minItems: 1
          items:
            type: string
            format: uuid
        project_id:
          type: string
          format: uuid
          nullable: true
        reason:
          type: string
      required: [user_id, allocation_ids, reason]

    ReleaseFailedPayload:
      type: object
      additionalProperties: false
      properties:
        allocation_id:
          type: string
          format: uuid
        user_id:
          type: string
          format: uuid
        project_id:
          type: string
          format: uuid
          nullable: true
        reason:
          type: string
      required: [allocation_id, user_id, reason]

    StorageAttachmentPayload:
      type: object
      additionalProperties: false
      properties:
        attachment_id:
          type: string
          format: uuid
        bucket_id:
          type: string
        project_id:
          type: string
          format: uuid
        allocation_id:
          type: string
          format: uuid
          nullable: true
        workload_instance_id:
          type: string
          format: uuid
          nullable: true
        node_id:
          type: string
          format: uuid
          nullable: true
        provider_backend:
          type: string
          enum: [local_dev, weka, vast, ddn, nvme_pool, s3_compatible, unknown]
        mount_path:
          type: string
        access_mode:
          type: string
          enum: [read_only, read_write]
        write_policy:
          type: string
          enum: [single_writer, multi_writer]
        state:
          type: string
          enum: [requested, prechecking, grant_applying, grant_applied, mounting, mounted, failed, detaching, detached, detach_failed]
        reason:
          type: string
          nullable: true
      required: [attachment_id, bucket_id, project_id, provider_backend, mount_path, access_mode, write_policy, state]

    ReleasingRequestedPayload:
      type: object
      additionalProperties: false
      properties:
        allocation_id:
          type: string
          format: uuid
        user_id:
          type: string
          format: uuid
        project_id:
          type: string
          format: uuid
          nullable: true
        reason:
          type: string
      required: [allocation_id, user_id, reason]

    ReleasingCompletedPayload:
      type: object
      additionalProperties: false
      properties:
        allocation_id:
          type: string
          format: uuid
        user_id:
          type: string
          format: uuid
        released_at:
          type: string
          format: date-time
        project_id:
          type: string
          format: uuid
          nullable: true
      required: [allocation_id, user_id, released_at]

    BalanceCreditedPayload:
      type: object
      additionalProperties: false
      properties:
        user_id:
          type: string
          format: uuid
        amount_minor:
          type: integer
          minimum: 1
        currency:
          type: string
          pattern: '^[A-Z]{3}$'
        source:
          type: string
          enum: [stripe_webhook]
      required: [user_id, amount_minor, currency, source]

    UsageMeteredPayload:
      type: object
      additionalProperties: false
      description: |
        Product-neutral metered usage interval. Producers must emit a stable
        `usage_event_id` and `idempotency_key`; billing consumers use those
        values to avoid duplicate rated usage and ledger writes.
      properties:
        usage_event_id:
          type: string
          format: uuid
        usage_source:
          type: string
          enum: [allocation, app_runtime]
        usage_unit:
          type: string
          enum: [gpu_hour, cpu_vm_hour, app_runtime_hour]
        org_id:
          type: string
          format: uuid
          nullable: true
        department_id:
          type: string
          format: uuid
          nullable: true
        project_id:
          type: string
          format: uuid
          nullable: true
        billing_account_id:
          type: string
          format: uuid
          nullable: true
        actor_type:
          type: string
          enum: [user, service_account, api_key, workload, system]
          nullable: true
        actor_id:
          type: string
          nullable: true
        requested_by_user_id:
          type: string
          format: uuid
          nullable: true
        requested_by_service_account_id:
          type: string
          format: uuid
          nullable: true
        credential_id:
          type: string
          format: uuid
          nullable: true
        allocation_id:
          type: string
          format: uuid
          nullable: true
        app_instance_id:
          type: string
          format: uuid
          nullable: true
        product_id:
          type: string
          nullable: true
        resource_type:
          type: string
          nullable: true
        resource_id:
          type: string
          nullable: true
        region_code:
          type: string
          nullable: true
        sku:
          type: string
          nullable: true
        quantity_millis:
          type: integer
          minimum: 0
        unit_count:
          type: integer
          minimum: 0
        bill_from:
          type: string
          format: date-time
        bill_to:
          type: string
          format: date-time
        currency:
          type: string
          pattern: '^[A-Z]{3}$'
          nullable: true
        pricing_source:
          type: string
          nullable: true
        pricing_plan_id:
          type: string
          format: uuid
          nullable: true
        pricing_plan_version:
          type: string
          nullable: true
        rate_card_id:
          type: string
          format: uuid
          nullable: true
        pricing_snapshot:
          type: object
          additionalProperties: true
        operating_mode:
          type: string
          enum: [tenant_dedicated, platform_managed]
          nullable: true
        control_plane_scope:
          type: string
          enum: [project, tenant, platform]
          nullable: true
        runtime_backend:
          type: string
          enum: [k8s, rke2, slurm, ray, bare_metal]
          nullable: true
        control_plane_component:
          type: boolean
        idempotency_key:
          type: string
        request_id:
          type: string
          nullable: true
        source_correlation_id:
          type: string
          nullable: true
        reconciliation_status:
          type: string
          enum: [accepted, rated, posted, reconciled, disputed]
          nullable: true
        metadata:
          type: object
          additionalProperties: true
      required: [usage_event_id, usage_source, usage_unit, quantity_millis, unit_count, bill_from, bill_to, idempotency_key]

    ReconcileFailedPayload:
      type: object
      additionalProperties: false
      properties:
        user_id:
          type: string
          format: uuid
        payment_session_id:
          type: string
          format: uuid
        stripe_session_id:
          type: string
        reason:
          type: string
      required: [user_id, payment_session_id, stripe_session_id, reason]

    AppInstanceRequestedPayload:
      type: object
      additionalProperties: false
      properties:
        app_instance_id:
          type: string
          format: uuid
        app_artifact_id:
          type: string
          format: uuid
          nullable: true
        app_slug:
          type: string
          pattern: '^[a-z0-9][a-z0-9-]{1,62}$'
        app_version:
          type: string
        operating_mode:
          type: string
          enum: [tenant_dedicated, platform_managed]
        control_plane_scope:
          type: string
          enum: [project, tenant, platform]
        runtime_backend:
          type: string
          enum: [k8s, slurm, ray, bare_metal]
        tenant_boundary_mode:
          type: string
          enum: [tenant_isolated, shared_service]
        org_id:
          type: string
          format: uuid
        project_id:
          type: string
          format: uuid
        requested_by_user_id:
          type: string
          format: uuid
          nullable: true
        requested_by_service_account_id:
          type: string
          format: uuid
          nullable: true
        operator_service_account_id:
          type: string
          format: uuid
          nullable: true
      required: [app_instance_id, app_slug, app_version, operating_mode, control_plane_scope, runtime_backend, tenant_boundary_mode, org_id, project_id]

    AppEntitlementUpdatedPayload:
      type: object
      additionalProperties: false
      properties:
        org_id:
          type: string
          format: uuid
        project_id:
          type: string
          format: uuid
        app_slug:
          type: string
          pattern: '^[a-z0-9][a-z0-9-]{1,62}$'
        enabled:
          type: boolean
        policy_overrides:
          type: object
          additionalProperties: true
        updated_by_user_id:
          type: string
          format: uuid
          nullable: true
      required: [org_id, project_id, app_slug, enabled, policy_overrides]

    AppInstanceRunningPayload:
      type: object
      additionalProperties: false
      properties:
        app_instance_id:
          type: string
          format: uuid
        app_slug:
          type: string
          pattern: '^[a-z0-9][a-z0-9-]{1,62}$'
        operating_mode:
          type: string
          enum: [tenant_dedicated, platform_managed]
        control_plane_scope:
          type: string
          enum: [project, tenant, platform]
        runtime_backend:
          type: string
          enum: [k8s, slurm, ray, bare_metal]
        org_id:
          type: string
          format: uuid
        project_id:
          type: string
          format: uuid
        endpoint:
          type: string
          nullable: true
      required: [app_instance_id, app_slug, operating_mode, control_plane_scope, runtime_backend, org_id, project_id]

    AppInstanceFailedPayload:
      type: object
      additionalProperties: false
      properties:
        app_instance_id:
          type: string
          format: uuid
        app_slug:
          type: string
          pattern: '^[a-z0-9][a-z0-9-]{1,62}$'
        operating_mode:
          type: string
          enum: [tenant_dedicated, platform_managed]
        control_plane_scope:
          type: string
          enum: [project, tenant, platform]
        runtime_backend:
          type: string
          enum: [k8s, slurm, ray, bare_metal]
        org_id:
          type: string
          format: uuid
        project_id:
          type: string
          format: uuid
        reason:
          type: string
      required: [app_instance_id, app_slug, operating_mode, control_plane_scope, runtime_backend, org_id, project_id, reason]

    AppInstanceDeletingPayload:
      type: object
      additionalProperties: false
      properties:
        app_instance_id:
          type: string
          format: uuid
        app_slug:
          type: string
          pattern: '^[a-z0-9][a-z0-9-]{1,62}$'
        operating_mode:
          type: string
          enum: [tenant_dedicated, platform_managed]
        control_plane_scope:
          type: string
          enum: [project, tenant, platform]
        runtime_backend:
          type: string
          enum: [k8s, slurm, ray, bare_metal]
        org_id:
          type: string
          format: uuid
        project_id:
          type: string
          format: uuid
        requested_by_user_id:
          type: string
          format: uuid
          nullable: true
        requested_by_service_account_id:
          type: string
          format: uuid
          nullable: true
      required: [app_instance_id, app_slug, operating_mode, control_plane_scope, runtime_backend, org_id, project_id]

    AppInstanceUpgradeRequestedPayload:
      type: object
      additionalProperties: false
      properties:
        app_instance_id:
          type: string
          format: uuid
        app_slug:
          type: string
          pattern: '^[a-z0-9][a-z0-9-]{1,62}$'
        app_version:
          type: string
        previous_app_version:
          type: string
        operating_mode:
          type: string
          enum: [tenant_dedicated, platform_managed]
        control_plane_scope:
          type: string
          enum: [project, tenant, platform]
        runtime_backend:
          type: string
          enum: [k8s, slurm, ray, bare_metal]
        org_id:
          type: string
          format: uuid
        project_id:
          type: string
          format: uuid
        requested_by_user_id:
          type: string
          format: uuid
          nullable: true
        requested_by_service_account_id:
          type: string
          format: uuid
          nullable: true
        operator_service_account_id:
          type: string
          format: uuid
          nullable: true
      required: [app_instance_id, app_slug, app_version, previous_app_version, operating_mode, control_plane_scope, runtime_backend, org_id, project_id]

    AppInstanceRollbackRequestedPayload:
      type: object
      additionalProperties: false
      properties:
        app_instance_id:
          type: string
          format: uuid
        app_slug:
          type: string
          pattern: '^[a-z0-9][a-z0-9-]{1,62}$'
        app_version:
          type: string
        previous_app_version:
          type: string
        operating_mode:
          type: string
          enum: [tenant_dedicated, platform_managed]
        control_plane_scope:
          type: string
          enum: [project, tenant, platform]
        runtime_backend:
          type: string
          enum: [k8s, slurm, ray, bare_metal]
        org_id:
          type: string
          format: uuid
        project_id:
          type: string
          format: uuid
        requested_by_user_id:
          type: string
          format: uuid
          nullable: true
        requested_by_service_account_id:
          type: string
          format: uuid
          nullable: true
        operator_service_account_id:
          type: string
          format: uuid
          nullable: true
      required: [app_instance_id, app_slug, app_version, previous_app_version, operating_mode, control_plane_scope, runtime_backend, org_id, project_id]

    AppInstanceDecommissionRequestedPayload:
      type: object
      additionalProperties: false
      properties:
        app_instance_id:
          type: string
          format: uuid
        app_slug:
          type: string
          pattern: '^[a-z0-9][a-z0-9-]{1,62}$'
        app_version:
          type: string
        operating_mode:
          type: string
          enum: [tenant_dedicated, platform_managed]
        control_plane_scope:
          type: string
          enum: [project, tenant, platform]
        runtime_backend:
          type: string
          enum: [k8s, slurm, ray, bare_metal]
        org_id:
          type: string
          format: uuid
        project_id:
          type: string
          format: uuid
        requested_by_user_id:
          type: string
          format: uuid
          nullable: true
        requested_by_service_account_id:
          type: string
          format: uuid
          nullable: true
        operator_service_account_id:
          type: string
          format: uuid
          nullable: true
      required: [app_instance_id, app_slug, app_version, operating_mode, control_plane_scope, runtime_backend, org_id, project_id]

    AppInstanceLifecycleRequestedPayload:
      type: object
      additionalProperties: false
      properties:
        app_instance_id:
          type: string
          format: uuid
        app_slug:
          type: string
          pattern: '^[a-z0-9][a-z0-9-]{1,62}$'
        app_version:
          type: string
        org_id:
          type: string
          format: uuid
        project_id:
          type: string
          format: uuid
        requested_by_user_id:
          type: string
          format: uuid
          nullable: true
        requested_by_service_account_id:
          type: string
          format: uuid
          nullable: true
        operator_service_account_id:
          type: string
          format: uuid
          nullable: true
        operation:
          type: string
          enum: [stop, start, restart]
      required: [app_instance_id, app_slug, app_version, org_id, project_id, operation]

    AppInstanceDeletedPayload:
      type: object
      additionalProperties: false
      properties:
        app_instance_id:
          type: string
          format: uuid
        app_slug:
          type: string
          pattern: '^[a-z0-9][a-z0-9-]{1,62}$'
        operating_mode:
          type: string
          enum: [tenant_dedicated, platform_managed]
        control_plane_scope:
          type: string
          enum: [project, tenant, platform]
        runtime_backend:
          type: string
          enum: [k8s, slurm, ray, bare_metal]
        org_id:
          type: string
          format: uuid
        project_id:
          type: string
          format: uuid
        deleted_at:
          type: string
          format: date-time
      required: [app_instance_id, app_slug, operating_mode, control_plane_scope, runtime_backend, org_id, project_id, deleted_at]

    SharedAppRuntimeRequestedPayload:
      type: object
      additionalProperties: false
      properties:
        shared_runtime_id:
          type: string
          format: uuid
        app_slug:
          type: string
          pattern: '^[a-z0-9][a-z0-9-]{1,62}$'
        app_version:
          type: string
        operating_mode:
          type: string
          enum: [tenant_dedicated]
        control_plane_scope:
          type: string
          enum: [tenant]
        runtime_backend:
          type: string
          enum: [k8s, slurm, ray, bare_metal]
        org_id:
          type: string
          format: uuid
        requested_by_user_id:
          type: string
          format: uuid
          nullable: true
        operator_identity_ref:
          type: string
          nullable: true
      required: [shared_runtime_id, app_slug, app_version, operating_mode, control_plane_scope, runtime_backend, org_id]

    SharedAppRuntimeRunningPayload:
      type: object
      additionalProperties: false
      properties:
        shared_runtime_id:
          type: string
          format: uuid
        app_slug:
          type: string
          pattern: '^[a-z0-9][a-z0-9-]{1,62}$'
        operating_mode:
          type: string
          enum: [tenant_dedicated]
        control_plane_scope:
          type: string
          enum: [tenant]
        runtime_backend:
          type: string
          enum: [k8s, slurm, ray, bare_metal]
        org_id:
          type: string
          format: uuid
        endpoint:
          type: string
          nullable: true
      required: [shared_runtime_id, app_slug, operating_mode, control_plane_scope, runtime_backend, org_id]

    SharedAppRuntimeFailedPayload:
      type: object
      additionalProperties: false
      properties:
        shared_runtime_id:
          type: string
          format: uuid
        app_slug:
          type: string
          pattern: '^[a-z0-9][a-z0-9-]{1,62}$'
        org_id:
          type: string
          format: uuid
        reason:
          type: string
      required: [shared_runtime_id, app_slug, org_id, reason]

    SharedAppRuntimeDeletingPayload:
      type: object
      additionalProperties: false
      properties:
        shared_runtime_id:
          type: string
          format: uuid
        app_slug:
          type: string
          pattern: '^[a-z0-9][a-z0-9-]{1,62}$'
        runtime_backend:
          type: string
          enum: [k8s, slurm, ray, bare_metal]
        org_id:
          type: string
          format: uuid
        requested_by_user_id:
          type: string
          format: uuid
          nullable: true
      required: [shared_runtime_id, app_slug, runtime_backend, org_id]

    SharedAppRuntimeDeletedPayload:
      type: object
      additionalProperties: false
      properties:
        shared_runtime_id:
          type: string
          format: uuid
        app_slug:
          type: string
          pattern: '^[a-z0-9][a-z0-9-]{1,62}$'
        org_id:
          type: string
          format: uuid
        deleted_at:
          type: string
          format: date-time
      required: [shared_runtime_id, app_slug, org_id, deleted_at]

    AppArtifactLifecyclePayload:
      type: object
      additionalProperties: false
      properties:
        artifact_id:
          type: string
          format: uuid
        artifact_kind:
          type: string
          enum: [oci, blob]
        source_type:
          type: string
          enum: [oci_registry, s3, gcs, azure_blob, https, huggingface, artifact_store]
        app_slug:
          type: string
          pattern: '^[a-z0-9][a-z0-9-]{1,62}$'
        app_version:
          type: string
        artifact_name:
          type: string
        repository:
          type: string
        source_uri:
          type: string
          nullable: true
        digest:
          type: string
          pattern: '^sha256:[a-f0-9]{64}$'
        lifecycle_state:
          type: string
          enum: [published, promoted, deprecated, retired]
        trust_state:
          type: string
          enum: [unverified, verified, failed_verification, revoked]
        promoted_channel:
          type: string
          nullable: true
        target_environment:
          type: string
          nullable: true
        org_id:
          type: string
          format: uuid
          nullable: true
        project_id:
          type: string
          format: uuid
        actor_user_id:
          type: string
          format: uuid
          nullable: true
        actor_service_account_id:
          type: string
          format: uuid
          nullable: true
      required: [artifact_id, artifact_kind, source_type, app_slug, app_version, artifact_name, repository, digest, lifecycle_state, trust_state, project_id]

  messages:
    TerminalControl:
      name: TerminalControl
      messageId: terminal.control
      contentType: application/json
      payload:
        $ref: '#/components/schemas/TerminalControlPayload'

    TerminalResize:
      name: TerminalResize
      messageId: terminal.resize
      contentType: application/json
      payload:
        type: object
        additionalProperties: false
        properties:
          type:
            type: string
            const: resize
          cols:
            type: integer
            minimum: 1
          rows:
            type: integer
            minimum: 1
        required: [type, cols, rows]

    TerminalHeartbeat:
      name: TerminalHeartbeat
      messageId: terminal.heartbeat
      contentType: application/json
      payload:
        type: object
        additionalProperties: false
        description: Client keepalive frame. Gateway consumes this frame locally and must not forward it to the PTY or node-agent.
        properties:
          type:
            type: string
            const: ping
        required: [type]

    TerminalInputBinary:
      name: TerminalInputBinary
      messageId: terminal.input.binary
      contentType: application/octet-stream
      payload:
        type: string
        format: binary

    TerminalOutputText:
      name: TerminalOutputText
      messageId: terminal.output.text
      contentType: text/plain
      payload:
        type: string

    TerminalOutputBinary:
      name: TerminalOutputBinary
      messageId: terminal.output.binary
      contentType: application/octet-stream
      payload:
        type: string
        format: binary

    UserNotification:
      name: UserNotification
      messageId: notifications.user
      payload:
        $ref: '#/components/schemas/UserNotificationPayload'

    LowBalanceWarningEvent:
      name: LowBalanceWarningEvent
      messageId: platform.billing.low_balance_warning
      payload:
        allOf:
          - $ref: '#/components/schemas/EventMetadata'
          - type: object
            additionalProperties: false
            properties:
              event_type:
                const: platform.billing.low_balance_warning
              payload:
                type: object
                additionalProperties: false
                properties:
                  user_id:
                    type: string
                    format: uuid
                  balance_minor:
                    type: integer
                  threshold_minor:
                    type: integer
                  currency:
                    type: string
                    pattern: '^[A-Z]{3}$'
                  projected_depletion_at:
                    type: string
                    format: date-time
                    nullable: true
                  projected_hours_remaining:
                    type: number
                    minimum: 0
                    nullable: true
                required: [user_id, balance_minor, threshold_minor, currency]
            required: [event_type, payload]

    BalanceDepletedEvent:
      name: BalanceDepletedEvent
      messageId: platform.billing.balance_depleted
      payload:
        allOf:
          - $ref: '#/components/schemas/EventMetadata'
          - type: object
            additionalProperties: false
            properties:
              event_type:
                const: platform.billing.balance_depleted
              payload:
                type: object
                additionalProperties: false
                properties:
                  user_id:
                    type: string
                    format: uuid
                  balance_minor:
                    type: integer
                  currency:
                    type: string
                    pattern: '^[A-Z]{3}$'
                  active_allocation_ids:
                    type: array
                    items:
                      type: string
                      format: uuid
                  active_app_instance_ids:
                    type: array
                    items:
                      type: string
                      format: uuid
                  selected_action:
                    type: string
                    enum: [restrict, force_release]
                  projected_depletion_at:
                    type: string
                    format: date-time
                    nullable: true
                  projected_hours_remaining:
                    type: number
                    minimum: 0
                    nullable: true
                required: [user_id, balance_minor, currency]
            required: [event_type, payload]

    AutoReleasePendingEvent:
      name: AutoReleasePendingEvent
      messageId: platform.billing.auto_release_pending
      payload:
        allOf:
          - $ref: '#/components/schemas/EventMetadata'
          - type: object
            additionalProperties: false
            properties:
              event_type:
                const: platform.billing.auto_release_pending
              payload:
                type: object
                additionalProperties: false
                properties:
                  user_id:
                    type: string
                    format: uuid
                  currency:
                    type: string
                    pattern: '^[A-Z]{3}$'
                  projected_depletion_at:
                    type: string
                    format: date-time
                  projected_hours_remaining:
                    type: number
                    minimum: 0
                  active_allocation_ids:
                    type: array
                    minItems: 1
                    items:
                      type: string
                      format: uuid
                required: [user_id, currency, projected_depletion_at, projected_hours_remaining, active_allocation_ids]
            required: [event_type, payload]

    BudgetThresholdCrossedEvent:
      name: BudgetThresholdCrossedEvent
      messageId: platform.billing.budget_threshold_crossed
      payload:
        allOf:
          - $ref: '#/components/schemas/EventMetadata'
          - type: object
            additionalProperties: false
            properties:
              event_type:
                const: platform.billing.budget_threshold_crossed
              payload:
                type: object
                additionalProperties: false
                properties:
                  org_id:
                    type: string
                    format: uuid
                  project_id:
                    type: string
                    format: uuid
                    nullable: true
                  budget_policy_id:
                    type: string
                    format: uuid
                  threshold_key:
                    type: string
                  scope_type:
                    type: string
                    enum: [tenant, project]
                  spent_minor:
                    type: integer
                    minimum: 0
                  budget_minor:
                    type: integer
                    minimum: 1
                  currency:
                    type: string
                    pattern: '^[A-Z]{3}$'
                  percent_used:
                    type: integer
                    minimum: 0
                  period_start:
                    type: string
                    format: date-time
                  period_end:
                    type: string
                    format: date-time
                  recipient_user_ids:
                    type: array
                    items:
                      type: string
                      format: uuid
                required: [org_id, budget_policy_id, threshold_key, scope_type, spent_minor, budget_minor, currency, percent_used, period_start, period_end]
            required: [event_type, payload]

    UsageMeteredEvent:
      name: UsageMeteredEvent
      messageId: platform.billing.usage.metered
      payload:
        allOf:
          - $ref: '#/components/schemas/EventMetadata'
          - type: object
            additionalProperties: false
            properties:
              event_type:
                const: platform.billing.usage.metered
              payload:
                $ref: '#/components/schemas/UsageMeteredPayload'
            required: [event_type, payload]

    BalanceCreditedEvent:
      name: BalanceCreditedEvent
      messageId: platform.payments.balance_credited
      payload:
        allOf:
          - $ref: '#/components/schemas/EventMetadata'
          - type: object
            additionalProperties: false
            properties:
              event_type:
                const: platform.payments.balance_credited
              payload:
                $ref: '#/components/schemas/BalanceCreditedPayload'
            required: [event_type, payload]

    ReconcileFailedEvent:
      name: ReconcileFailedEvent
      messageId: platform.payments.reconcile_failed
      payload:
        allOf:
          - $ref: '#/components/schemas/EventMetadata'
          - type: object
            additionalProperties: false
            properties:
              event_type:
                const: platform.payments.reconcile_failed
              payload:
                $ref: '#/components/schemas/ReconcileFailedPayload'
            required: [event_type, payload]

    ProvisionRequestedEvent:
      name: ProvisionRequestedEvent
      messageId: gpuaas.provisioning.requested
      payload:
        allOf:
          - $ref: '#/components/schemas/EventMetadata'
          - type: object
            additionalProperties: false
            properties:
              event_type:
                const: gpuaas.provisioning.requested
              payload:
                $ref: '#/components/schemas/ProvisionRequestedPayload'
            required: [event_type, payload]

    AllocationActiveEvent:
      name: AllocationActiveEvent
      messageId: gpuaas.provisioning.active
      payload:
        allOf:
          - $ref: '#/components/schemas/EventMetadata'
          - type: object
            additionalProperties: false
            properties:
              event_type:
                const: gpuaas.provisioning.active
              payload:
                $ref: '#/components/schemas/AllocationActivePayload'
            required: [event_type, payload]

    ProvisionFailedEvent:
      name: ProvisionFailedEvent
      messageId: gpuaas.provisioning.failed
      payload:
        allOf:
          - $ref: '#/components/schemas/EventMetadata'
          - type: object
            additionalProperties: false
            properties:
              event_type:
                const: gpuaas.provisioning.failed
              payload:
                $ref: '#/components/schemas/ProvisionFailedPayload'
            required: [event_type, payload]

    ReleasingRequestedEvent:
      name: ReleasingRequestedEvent
      messageId: gpuaas.provisioning.releasing.requested
      payload:
        allOf:
          - $ref: '#/components/schemas/EventMetadata'
          - type: object
            additionalProperties: false
            properties:
              event_type:
                const: gpuaas.provisioning.releasing.requested
              payload:
                $ref: '#/components/schemas/ReleasingRequestedPayload'
            required: [event_type, payload]

    ReleasingCompletedEvent:
      name: ReleasingCompletedEvent
      messageId: gpuaas.provisioning.releasing.completed
      payload:
        allOf:
          - $ref: '#/components/schemas/EventMetadata'
          - type: object
            additionalProperties: false
            properties:
              event_type:
                const: gpuaas.provisioning.releasing.completed
              payload:
                $ref: '#/components/schemas/ReleasingCompletedPayload'
            required: [event_type, payload]

    ForceReleaseRequestedEvent:
      name: ForceReleaseRequestedEvent
      messageId: gpuaas.provisioning.force_release_requested
      payload:
        allOf:
          - $ref: '#/components/schemas/EventMetadata'
          - type: object
            additionalProperties: false
            properties:
              event_type:
                const: gpuaas.provisioning.force_release_requested
              payload:
                $ref: '#/components/schemas/ForceReleaseRequestedPayload'
            required: [event_type, payload]

    ReleaseFailedEvent:
      name: ReleaseFailedEvent
      messageId: gpuaas.provisioning.release_failed
      payload:
        allOf:
          - $ref: '#/components/schemas/EventMetadata'
          - type: object
            additionalProperties: false
            properties:
              event_type:
                const: gpuaas.provisioning.release_failed
              payload:
                $ref: '#/components/schemas/ReleaseFailedPayload'
            required: [event_type, payload]

    StorageAttachmentRequestedEvent:
      name: StorageAttachmentRequestedEvent
      messageId: storage.attachment.requested
      payload:
        allOf:
          - $ref: '#/components/schemas/EventMetadata'
          - type: object
            additionalProperties: false
            properties:
              event_type:
                const: storage.attachment.requested
              payload:
                $ref: '#/components/schemas/StorageAttachmentPayload'
            required: [event_type, payload]

    StorageAttachmentMountedEvent:
      name: StorageAttachmentMountedEvent
      messageId: storage.attachment.mounted
      payload:
        allOf:
          - $ref: '#/components/schemas/EventMetadata'
          - type: object
            additionalProperties: false
            properties:
              event_type:
                const: storage.attachment.mounted
              payload:
                $ref: '#/components/schemas/StorageAttachmentPayload'
            required: [event_type, payload]

    StorageAttachmentDetachRequestedEvent:
      name: StorageAttachmentDetachRequestedEvent
      messageId: storage.attachment.detach_requested
      payload:
        allOf:
          - $ref: '#/components/schemas/EventMetadata'
          - type: object
            additionalProperties: false
            properties:
              event_type:
                const: storage.attachment.detach_requested
              payload:
                $ref: '#/components/schemas/StorageAttachmentPayload'
            required: [event_type, payload]

    StorageAttachmentFailedEvent:
      name: StorageAttachmentFailedEvent
      messageId: storage.attachment.failed
      payload:
        allOf:
          - $ref: '#/components/schemas/EventMetadata'
          - type: object
            additionalProperties: false
            properties:
              event_type:
                const: storage.attachment.failed
              payload:
                $ref: '#/components/schemas/StorageAttachmentPayload'
            required: [event_type, payload]

    StorageAttachmentDetachedEvent:
      name: StorageAttachmentDetachedEvent
      messageId: storage.attachment.detached
      payload:
        allOf:
          - $ref: '#/components/schemas/EventMetadata'
          - type: object
            additionalProperties: false
            properties:
              event_type:
                const: storage.attachment.detached
              payload:
                $ref: '#/components/schemas/StorageAttachmentPayload'
            required: [event_type, payload]

    StorageAttachmentDetachFailedEvent:
      name: StorageAttachmentDetachFailedEvent
      messageId: storage.attachment.detach_failed
      payload:
        allOf:
          - $ref: '#/components/schemas/EventMetadata'
          - type: object
            additionalProperties: false
            properties:
              event_type:
                const: storage.attachment.detach_failed
              payload:
                $ref: '#/components/schemas/StorageAttachmentPayload'
            required: [event_type, payload]

    AppInstanceRequestedEvent:
      name: AppInstanceRequestedEvent
      messageId: appplatform.runtime.instance.requested
      payload:
        allOf:
          - $ref: '#/components/schemas/EventMetadata'
          - type: object
            additionalProperties: false
            properties:
              event_type:
                const: appplatform.runtime.instance.requested
              payload:
                $ref: '#/components/schemas/AppInstanceRequestedPayload'
            required: [event_type, payload]

    AppEntitlementUpdatedEvent:
      name: AppEntitlementUpdatedEvent
      messageId: appplatform.catalog.entitlement.updated
      payload:
        allOf:
          - $ref: '#/components/schemas/EventMetadata'
          - type: object
            additionalProperties: false
            properties:
              event_type:
                const: appplatform.catalog.entitlement.updated
              payload:
                $ref: '#/components/schemas/AppEntitlementUpdatedPayload'
            required: [event_type, payload]

    AppInstanceRunningEvent:
      name: AppInstanceRunningEvent
      messageId: appplatform.runtime.instance.running
      payload:
        allOf:
          - $ref: '#/components/schemas/EventMetadata'
          - type: object
            additionalProperties: false
            properties:
              event_type:
                const: appplatform.runtime.instance.running
              payload:
                $ref: '#/components/schemas/AppInstanceRunningPayload'
            required: [event_type, payload]

    AppInstanceFailedEvent:
      name: AppInstanceFailedEvent
      messageId: appplatform.runtime.instance.failed
      payload:
        allOf:
          - $ref: '#/components/schemas/EventMetadata'
          - type: object
            additionalProperties: false
            properties:
              event_type:
                const: appplatform.runtime.instance.failed
              payload:
                $ref: '#/components/schemas/AppInstanceFailedPayload'
            required: [event_type, payload]

    AppInstanceDeletingEvent:
      name: AppInstanceDeletingEvent
      messageId: appplatform.runtime.instance.deleting
      payload:
        allOf:
          - $ref: '#/components/schemas/EventMetadata'
          - type: object
            additionalProperties: false
            properties:
              event_type:
                const: appplatform.runtime.instance.deleting
              payload:
                $ref: '#/components/schemas/AppInstanceDeletingPayload'
            required: [event_type, payload]

    AppInstanceDeletedEvent:
      name: AppInstanceDeletedEvent
      messageId: appplatform.runtime.instance.deleted
      payload:
        allOf:
          - $ref: '#/components/schemas/EventMetadata'
          - type: object
            additionalProperties: false
            properties:
              event_type:
                const: appplatform.runtime.instance.deleted
              payload:
                $ref: '#/components/schemas/AppInstanceDeletedPayload'
            required: [event_type, payload]

    SharedAppRuntimeRequestedEvent:
      name: SharedAppRuntimeRequestedEvent
      messageId: appplatform.runtime.shared_runtime.requested
      payload:
        allOf:
          - $ref: '#/components/schemas/EventMetadata'
          - type: object
            additionalProperties: false
            properties:
              event_type:
                const: appplatform.runtime.shared_runtime.requested
              payload:
                $ref: '#/components/schemas/SharedAppRuntimeRequestedPayload'
            required: [event_type, payload]

    SharedAppRuntimeRunningEvent:
      name: SharedAppRuntimeRunningEvent
      messageId: appplatform.runtime.shared_runtime.running
      payload:
        allOf:
          - $ref: '#/components/schemas/EventMetadata'
          - type: object
            additionalProperties: false
            properties:
              event_type:
                const: appplatform.runtime.shared_runtime.running
              payload:
                $ref: '#/components/schemas/SharedAppRuntimeRunningPayload'
            required: [event_type, payload]

    SharedAppRuntimeFailedEvent:
      name: SharedAppRuntimeFailedEvent
      messageId: appplatform.runtime.shared_runtime.failed
      payload:
        allOf:
          - $ref: '#/components/schemas/EventMetadata'
          - type: object
            additionalProperties: false
            properties:
              event_type:
                const: appplatform.runtime.shared_runtime.failed
              payload:
                $ref: '#/components/schemas/SharedAppRuntimeFailedPayload'
            required: [event_type, payload]

    SharedAppRuntimeDeletingEvent:
      name: SharedAppRuntimeDeletingEvent
      messageId: appplatform.runtime.shared_runtime.deleting
      payload:
        allOf:
          - $ref: '#/components/schemas/EventMetadata'
          - type: object
            additionalProperties: false
            properties:
              event_type:
                const: appplatform.runtime.shared_runtime.deleting
              payload:
                $ref: '#/components/schemas/SharedAppRuntimeDeletingPayload'
            required: [event_type, payload]

    SharedAppRuntimeDeletedEvent:
      name: SharedAppRuntimeDeletedEvent
      messageId: appplatform.runtime.shared_runtime.deleted
      payload:
        allOf:
          - $ref: '#/components/schemas/EventMetadata'
          - type: object
            additionalProperties: false
            properties:
              event_type:
                const: appplatform.runtime.shared_runtime.deleted
              payload:
                $ref: '#/components/schemas/SharedAppRuntimeDeletedPayload'
            required: [event_type, payload]

    AppInstanceUpgradeRequestedEvent:
      name: AppInstanceUpgradeRequestedEvent
      messageId: appplatform.runtime.instance.upgrade_requested
      payload:
        allOf:
          - $ref: '#/components/schemas/EventMetadata'
          - type: object
            additionalProperties: false
            properties:
              event_type:
                const: appplatform.runtime.instance.upgrade_requested
              payload:
                $ref: '#/components/schemas/AppInstanceUpgradeRequestedPayload'
            required: [event_type, payload]

    AppInstanceRollbackRequestedEvent:
      name: AppInstanceRollbackRequestedEvent
      messageId: appplatform.runtime.instance.rollback_requested
      payload:
        allOf:
          - $ref: '#/components/schemas/EventMetadata'
          - type: object
            additionalProperties: false
            properties:
              event_type:
                const: appplatform.runtime.instance.rollback_requested
              payload:
                $ref: '#/components/schemas/AppInstanceRollbackRequestedPayload'
            required: [event_type, payload]

    AppInstanceDecommissionRequestedEvent:
      name: AppInstanceDecommissionRequestedEvent
      messageId: appplatform.runtime.instance.decommission_requested
      payload:
        allOf:
          - $ref: '#/components/schemas/EventMetadata'
          - type: object
            additionalProperties: false
            properties:
              event_type:
                const: appplatform.runtime.instance.decommission_requested
              payload:
                $ref: '#/components/schemas/AppInstanceDecommissionRequestedPayload'
            required: [event_type, payload]

    AppInstanceStopRequestedEvent:
      name: AppInstanceStopRequestedEvent
      messageId: appplatform.runtime.instance.stop_requested
      payload:
        allOf:
          - $ref: '#/components/schemas/EventMetadata'
          - type: object
            additionalProperties: false
            properties:
              event_type:
                const: appplatform.runtime.instance.stop_requested
              payload:
                $ref: '#/components/schemas/AppInstanceLifecycleRequestedPayload'
            required: [event_type, payload]

    AppInstanceStartRequestedEvent:
      name: AppInstanceStartRequestedEvent
      messageId: appplatform.runtime.instance.start_requested
      payload:
        allOf:
          - $ref: '#/components/schemas/EventMetadata'
          - type: object
            additionalProperties: false
            properties:
              event_type:
                const: appplatform.runtime.instance.start_requested
              payload:
                $ref: '#/components/schemas/AppInstanceLifecycleRequestedPayload'
            required: [event_type, payload]

    AppInstanceRestartRequestedEvent:
      name: AppInstanceRestartRequestedEvent
      messageId: appplatform.runtime.instance.restart_requested
      payload:
        allOf:
          - $ref: '#/components/schemas/EventMetadata'
          - type: object
            additionalProperties: false
            properties:
              event_type:
                const: appplatform.runtime.instance.restart_requested
              payload:
                $ref: '#/components/schemas/AppInstanceLifecycleRequestedPayload'
            required: [event_type, payload]

    AppArtifactRegisteredEvent:
      name: AppArtifactRegisteredEvent
      messageId: appplatform.artifact.registered
      payload:
        allOf:
          - $ref: '#/components/schemas/EventMetadata'
          - type: object
            additionalProperties: false
            properties:
              event_type:
                const: appplatform.artifact.registered
              payload:
                $ref: '#/components/schemas/AppArtifactLifecyclePayload'
            required: [event_type, payload]

    AppArtifactPromotedEvent:
      name: AppArtifactPromotedEvent
      messageId: appplatform.artifact.promoted
      payload:
        allOf:
          - $ref: '#/components/schemas/EventMetadata'
          - type: object
            additionalProperties: false
            properties:
              event_type:
                const: appplatform.artifact.promoted
              payload:
                $ref: '#/components/schemas/AppArtifactLifecyclePayload'
            required: [event_type, payload]

    AppArtifactVerifiedEvent:
      name: AppArtifactVerifiedEvent
      messageId: platform.artifact.verified
      payload:
        allOf:
          - $ref: '#/components/schemas/EventMetadata'
          - type: object
            additionalProperties: false
            properties:
              event_type:
                const: platform.artifact.verified
              payload:
                $ref: '#/components/schemas/AppArtifactLifecyclePayload'
            required: [event_type, payload]

    AppArtifactRevokedEvent:
      name: AppArtifactRevokedEvent
      messageId: platform.artifact.revoked
      payload:
        allOf:
          - $ref: '#/components/schemas/EventMetadata'
          - type: object
            additionalProperties: false
            properties:
              event_type:
                const: platform.artifact.revoked
              payload:
                $ref: '#/components/schemas/AppArtifactLifecyclePayload'
            required: [event_type, payload]

    AppArtifactDeprecatedEvent:
      name: AppArtifactDeprecatedEvent
      messageId: appplatform.artifact.deprecated
      payload:
        allOf:
          - $ref: '#/components/schemas/EventMetadata'
          - type: object
            additionalProperties: false
            properties:
              event_type:
                const: appplatform.artifact.deprecated
              payload:
                $ref: '#/components/schemas/AppArtifactLifecyclePayload'
            required: [event_type, payload]

    AppArtifactRetiredEvent:
      name: AppArtifactRetiredEvent
      messageId: appplatform.artifact.retired
      payload:
        allOf:
          - $ref: '#/components/schemas/EventMetadata'
          - type: object
            additionalProperties: false
            properties:
              event_type:
                const: appplatform.artifact.retired
              payload:
                $ref: '#/components/schemas/AppArtifactLifecyclePayload'
            required: [event_type, payload]
