Domain Model
:::info Source
Sourced from services/media-service/DOMAIN_MODEL.md in the documentation repo.
:::
1. Aggregates
MediaAsset (root)
type MediaAssetId = Branded<string, 'MediaAssetId'>;
interface MediaAsset {
id: MediaAssetId;
tenantId: TenantId;
ownerUserId: UserId;
kind: 'image'|'audio'|'video'|'document'|'subtitle'|'ai_image'|'ai_audio';
source: 'upload'|'ai_generated'|'imported';
storage: { bucket: string; key: string; sizeBytes: number; sha256: SHA256 };
mime: string;
status: 'uploading'|'scanning'|'transcoding'|'ready'|'failed'|'quarantined';
originalFilename?: string;
width?: number; height?: number; durationSeconds?: number;
aiProvenance?: AIProvenance;
createdAt: ISODate;
}
AssetVariant
interface AssetVariant {
id: ULID;
assetId: MediaAssetId;
profile: string; // "hls-720p", "hls-1080p", "mp3-128k", "webp-1280", etc.
storage: { bucket: string; key: string; sizeBytes: number };
mime: string;
width?: number; height?: number;
bitrate?: number;
createdAt: ISODate;
}
CaptionTrack / SubtitleTrack
interface CaptionTrack {
id: ULID;
assetId: MediaAssetId;
language: Locale;
kind: 'captions' | 'subtitles' | 'descriptions';
storage: { bucket: string; key: string; format: 'vtt' | 'srt' };
source: 'ai' | 'human' | 'imported';
aiProvenance?: AIProvenance;
approved: boolean;
reviewedBy?: UserId;
}
Transcript
interface Transcript {
id: ULID;
assetId: MediaAssetId;
language: Locale;
segments: TranscriptSegment[];
source: 'ai' | 'human';
aiProvenance?: AIProvenance;
}
interface TranscriptSegment { startMs: number; endMs: number; text: string; speakerLabel?: string; }
TranscodeJob
interface TranscodeJob {
id: ULID;
assetId: MediaAssetId;
profile: string;
status: 'queued'|'running'|'completed'|'failed';
attempts: number;
startedAt?: ISODate;
completedAt?: ISODate;
errorMessage?: string;
}
AIMediaArtifact
interface AIMediaArtifact {
id: ULID;
tenantId: TenantId;
kind: 'ai_image' | 'ai_audio';
promptRef: string;
outputAssetId: MediaAssetId;
costMicroUSD: number;
aiProvenance: AIProvenance;
}
2. State Machine (MediaAsset)
uploading → scanning → transcoding → ready
↘ ↘
failed quarantined (AV hit)
3. Invariants
- Asset keyed by SHA-256; duplicates deduplicated per tenant (optional).
readyrequires ≥ 1 variant for video/audio.- AI-generated assets must carry
aiProvenance. - Caption track language valid BCP 47.
- Tenant isolation on storage prefix.
4. Domain Events
media.asset.uploaded.v1,.scanned.v1,.transcoded.v1,.ready.v1,.failed.v1,.quarantined.v1,.deleted.v1media.caption.generated.v1,.reviewed.v1media.transcript.generated.v1media.ai_image.generated.v1,media.ai_audio.generated.v1
5. Diagram
User upload ─▶ signed URL ─▶ S3 ─▶ (event) ─▶ scanner
│
▼
clean? ─ no ─▶ quarantined
│ yes
▼
transcoder (video/audio)
│
▼
ready ─▶ variants + captions + transcript (async)