# ===================================================================== # Render Events — published by Render Orchestrator # Routing: flatrender.events (topic) — key: render.*.v1 # ===================================================================== $schema: "http://json-schema.org/draft-07/schema#" title: Render Event Schemas type: object definitions: EventEnvelope: type: object required: [event_id, event_type, event_time, tenant_id, data] properties: event_id: { type: string, format: uuid } event_type: { type: string } event_time: { type: string, format: date-time } tenant_id: { type: string, format: uuid } user_id: { type: string, format: uuid } trace_id: { type: string, format: uuid } correlation_id: { type: string, format: uuid } producer: { type: string, const: render-orchestrator } data: { type: object } events: # ------------------------------------------------------------------- # render.job.queued.v1 # Emitted when a job is accepted into the queue. # Consumed by: notification (none yet), analytics # ------------------------------------------------------------------- render.job.queued.v1: routing_key: render.job.queued.v1 description: New render job has been accepted into a priority queue. payload: type: object required: [render_job_id, saved_project_id, priority_queue, price_type, region] properties: render_job_id: { type: string, format: uuid } saved_project_id: { type: string, format: uuid } original_project_id: { type: string, format: uuid } priority_queue: type: string enum: [snapshot, vip, paid, preview, mockup, voiceover] priority_score: { type: integer, minimum: 0, maximum: 100 } price_type: type: string enum: [Free, Preview, Cash, Plan, Snapshot, Reseller] paid_price_minor: { type: integer, format: int64 } region: { type: string } duration_sec: { type: number } resolution: { type: string } is_60_fps: { type: boolean } has_music: { type: boolean } has_sfx: { type: boolean } has_voiceover: { type: boolean } # ------------------------------------------------------------------- # render.job.started.v1 # Emitted once nodes are assigned and rendering begins. # ------------------------------------------------------------------- render.job.started.v1: routing_key: render.job.started.v1 description: Render job has begun processing on at least one node. payload: type: object required: [render_job_id, started_at] properties: render_job_id: { type: string, format: uuid } started_at: { type: string, format: date-time } node_ids: type: array items: { type: string, format: uuid } region: { type: string } total_frames: { type: integer } # ------------------------------------------------------------------- # render.job.progress.v1 (high-frequency, optional pub/sub) # NOT broadcast on the main events exchange — only on per-job # ephemeral fanout for WebSocket fan-out. Schema documented here. # ------------------------------------------------------------------- render.job.progress.v1: routing_key: render.job.progress.{job_id} description: Progress tick for live UI update. exchange: render.progress # separate dedicated exchange (auto-delete fanout) payload: type: object required: [render_job_id, step, progress] properties: render_job_id: { type: string, format: uuid } step: type: string enum: [Queued, Preparing, TemplateCache, JsxGen, Music, Rendering, Validating, Repairing, Optimisation, Video, Mixing, Final, Uploading, Done, Failed, Cancelled] progress: { type: integer, minimum: 0, maximum: 100 } current_frame: { type: integer, nullable: true } total_frames: { type: integer, nullable: true } eta_seconds: { type: integer, nullable: true } preview_b64: type: string nullable: true description: Last rendered frame thumbnail (small, ~5-15 KB) message: { type: string, nullable: true } # ------------------------------------------------------------------- # render.job.completed.v1 # Consumed by: notification, studio (mark project), file (cleanup), # tenant (usage metering), webhook dispatcher # ------------------------------------------------------------------- render.job.completed.v1: routing_key: render.job.completed.v1 description: Render job successfully produced an export. payload: type: object required: [render_job_id, export_id, output_url, duration_sec, size_bytes] properties: render_job_id: { type: string, format: uuid } saved_project_id: { type: string, format: uuid } original_project_id: { type: string, format: uuid } export_id: { type: string, format: uuid } output_url: { type: string } thumbnail_url: { type: string, nullable: true } duration_sec: { type: number } size_bytes: { type: integer, format: int64 } resolution: { type: string } width: { type: integer } height: { type: integer } render_compute_seconds: { type: integer, description: "Sum of node-seconds spent" } node_ids_used: type: array items: { type: string, format: uuid } price_type: { type: string } paid_price_minor: { type: integer, format: int64 } # ------------------------------------------------------------------- # render.job.failed.v1 # Consumed by: notification, identity (refund), tenant (metering) # ------------------------------------------------------------------- render.job.failed.v1: routing_key: render.job.failed.v1 description: Render job failed permanently (after retries exhausted). payload: type: object required: [render_job_id, failed_at_step, error_message] properties: render_job_id: { type: string, format: uuid } saved_project_id: { type: string, format: uuid } failed_at_step: type: string enum: [Queued, Preparing, TemplateCache, JsxGen, Music, Rendering, Validating, Repairing, Optimisation, Video, Mixing, Final, Uploading, Done, Failed, Cancelled] error_message: { type: string } error_code: { type: string, nullable: true } retry_count: { type: integer } refund_required: { type: boolean, default: true } price_type: { type: string } paid_price_minor: { type: integer, format: int64 } node_ids_attempted: type: array items: { type: string, format: uuid } # ------------------------------------------------------------------- # render.job.cancelled.v1 # User cancelled via UI before completion. # ------------------------------------------------------------------- render.job.cancelled.v1: routing_key: render.job.cancelled.v1 description: Render job was cancelled by user. payload: type: object required: [render_job_id, cancelled_by_user_id] properties: render_job_id: { type: string, format: uuid } cancelled_by_user_id: { type: string, format: uuid } cancelled_at_step: { type: string } progress_when_cancelled: { type: integer } refund_required: { type: boolean, default: true } # ------------------------------------------------------------------- # render.snapshot.requested.v1 # User asked for a single-frame snapshot of a scene. # ------------------------------------------------------------------- render.snapshot.requested.v1: routing_key: render.snapshot.requested.v1 description: Scene snapshot was queued. payload: type: object required: [snapshot_id, saved_project_id, scene_key, frame_number] properties: snapshot_id: { type: string, format: uuid } saved_project_id: { type: string, format: uuid } scene_key: { type: string } frame_number: { type: integer } cached: type: boolean description: True if served from cache (no render needed) # ------------------------------------------------------------------- # render.snapshot.ready.v1 # Single-frame snapshot finished. # ------------------------------------------------------------------- render.snapshot.ready.v1: routing_key: render.snapshot.ready.v1 description: Snapshot image is ready. payload: type: object required: [snapshot_id, image_url] properties: snapshot_id: { type: string, format: uuid } saved_project_id: { type: string, format: uuid } scene_key: { type: string } frame_number: { type: integer } image_url: { type: string } thumbnail_url: { type: string, nullable: true } width: { type: integer } height: { type: integer } size_bytes: { type: integer } duration_ms: { type: integer } expires_at: { type: string, format: date-time }