From 78d0d0c15e0de5bb414fe55b3a49f30464581614 Mon Sep 17 00:00:00 2001 From: glass beadian Date: Fri, 16 Oct 2026 21:42:32 +0000 Subject: [PATCH] fix: assemble jobs pin the project's first clip as their source asset The claim path resolves the row's video_asset_id; NULL stranded every assemble job as 'produce asset unavailable'. --- apps/api/src/media-control/edl.service.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/apps/api/src/media-control/edl.service.ts b/apps/api/src/media-control/edl.service.ts index 5e59bdf..c5bd761 100644 --- a/apps/api/src/media-control/edl.service.ts +++ b/apps/api/src/media-control/edl.service.ts @@ -1,4 +1,4 @@ -import { Inject, Injectable, NotFoundException, Optional } from "@nestjs/common"; +import { BadRequestException, Inject, Injectable, NotFoundException, Optional } from "@nestjs/common"; import type { PoolClient } from "pg"; import { appendTenantAudit, type AuditEvent } from "../audit/audit.service.js"; import { withTenant } from "../db/client.js"; @@ -128,7 +128,7 @@ export class EdlService { /** * Queue the assemble lane: the job row carries the validated project so the * worker re-validates exactly what the api persisted (044's claim picks the - * row up by kind 'assemble'). video_asset_id stays NULL — the produced + * row up by kind 'assemble'). video_asset_id pins the project's first * output is a derived video_assets row written when the job completes. */ async render(workspaceId: string, id: string, lane: EdlLane | undefined, actorUserId: string): Promise<{ jobId: string }> { @@ -140,11 +140,17 @@ export class EdlService { if (row === undefined) throw new NotFoundException(`EDL project ${id} not found`); const project = row.project as EdlProject; const input = { edlProjectId: id, project, ...(lane === undefined ? {} : { lane }) }; + // The claim path resolves the row's source asset (the project's FIRST + // clip); the runner resolves the remaining clips through asset(). + const firstClip = project.items.find((item) => item.kind === "clip"); + if (firstClip === undefined || firstClip.kind !== "clip") { + throw new BadRequestException("project has no clip to render"); + } return client .query( `INSERT INTO video_processing_jobs (workspace_id, video_asset_id, kind, state, input_json) - VALUES ($1, NULL, 'assemble', 'queued', $2::jsonb) RETURNING id`, - [workspaceId, JSON.stringify(input)], + VALUES ($1, $3, 'assemble', 'queued', $2::jsonb) RETURNING id`, + [workspaceId, JSON.stringify(input), firstClip.assetId], ) .then((jobResult) => { const jobRow = jobResult.rows[0];