/** * Produce job store ports (migrations/009_video.sql + 039_producer.sql). * * The worker owns the same two tables the API's video service owns: it claims * `video_processing_jobs` rows by kind, writes the derived `video_assets` row a * job produces, and drives the state machine to `complete`/`failed`. Ports, not * a concrete client, so tests and the (single-process) e2e path can double them – * the same standing as `ObservedStore`, `AssetStore` and `PreviewObjectStore`. */ import type { Lane, ProduceJobKind, ProduceRequest } from "./command-builder"; export type { Lane, ProduceJobKind, ProduceRequest } from "./command-builder"; /** The source `video_assets` row a claimed job renders from. */ export interface ProduceSourceAsset { id: string; storageKey: string; mimeType: string; sizeBytes: number; width: number | null; height: number | null; durationMs: number | null; frameRate: number | null; /** True when the asset is known to carry an audio track (probed by 009's probe job). */ hasAudio: boolean | null; } export interface ClaimedProduceJob { jobId: string; workspaceId: string; kind: ProduceJobKind; request: ProduceRequest; source: ProduceSourceAsset; claimedAt: string; } export interface ProduceOutput { /** Storage key of the produced mp4. */ storageKey: string; sizeBytes: number; durationMs: number | null; width: number; height: number; frameRate: number; } export interface ProduceJobStore { /** * Claim exactly one queued compose/mix row for this worker's lane, with lease * semantics: the `queued → transcoding` state bump inside * `FOR UPDATE SKIP LOCKED` is the lease, so two workers can never take the * same row and a row stays claimed until this worker finishes it. * * Returns `null` when nothing is claimable — including when a row was claimed * but could not be honoured (an undecodable `input_json`, a missing source * asset): such a row is failed inside the claim, never thrown, so no row can * strand in `transcoding` holding its lease. Store/transport errors still throw * (the poll loop's store-error path). */ claim(lane: Lane): Promise; /** Report progress on a claimed job (never changes state). */ progress(jobId: string, percent: number): Promise; /** Insert the derived asset and complete the job pointing at it. */ complete(jobId: string, output: ProduceOutput, derivedFrom: string): Promise; /** Fail a claimed job; `reason` must already be redacted/bounded. */ fail(jobId: string, reason: string): Promise; /** Resolve a voiceover asset id to its storage key. */ asset(workspaceId: string, assetId: string): Promise<{ storageKey: string; mimeType: string } | null>; } /** * Object storage, i.e. the API's `StorageService` (content-addressed, envelope * encrypted): the worker downloads source bytes and publishes the produced mp4 * through exactly this pair. The adapter lives in `s3-storage.ts`. */ export interface ProduceObjectStorage { getObject(key: string): Promise; putContent(body: Buffer, contentType: "video/mp4"): Promise<{ key: string }>; }