fix(pipeline): keep an urgent export urgent through a validation override
Deploy / deploy (push) Successful in 6m11s

Overriding a failed validation queued the preview at priority 50
regardless. It took its rank from the export's last PREVIEW_ONLY job —
but validation runs before the preview stage, so a VALIDATION_FAILED
export has never had one, and the missing row fell through to a
hardcoded normal priority. An urgent shot lost its urgency at exactly
the moment someone was standing over it waiting.

Every preview now derives its rank from the export's AE_RENDER priority
rather than from a previous preview. That fixes the missing-row case and
also removes a slow drift: a preview's priority is already boosted, so
re-deriving from it moved an export a step up the queue on every retry.

createPreviewJob takes the render priority and the attempt number, so the
first build, a retry, a regenerate and an override all rank identically.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
twotalesanimation
2026-08-14 12:19:19 +02:00
parent c9b0f25bb2
commit 41ee826700
2 changed files with 63 additions and 35 deletions
+49 -24
View File
@@ -193,13 +193,40 @@ export function previewPriority(renderPriority: number, boost: number, urgentThr
return Math.max(0, boosted);
}
/** Creates the PREVIEW_ONLY job that follows a successful EXR render. */
/**
* The priority an export's work runs at: the one its render was queued with.
*
* Every preview for an export derives from this rather than from a previous
* preview job, for two reasons. A preview job may not exist yet — an export
* that failed validation never had one, and reading a missing row used to
* silently drop an urgent export to normal. And deriving from an already-
* boosted preview would boost again on each retry, drifting the export up the
* queue a step at a time.
*/
export async function renderPriorityFor(
tx: Prisma.TransactionClient,
exportId: string
): Promise<number> {
const job = await tx.renderJob.findFirst({
where: { exportId, type: "AE_RENDER" },
orderBy: { attempt: "desc" },
select: { priority: true },
});
return job?.priority ?? 50;
}
/**
* Creates a PREVIEW_ONLY job. `renderPriority` is the export's *render*
* priority; the queue rank is derived from it here so every path — first
* build, retry, regenerate, validation override — ranks identically.
*/
export async function createPreviewJob(
tx: Prisma.TransactionClient,
exportRow: ExportForPreview,
manifest: PreviewManifest,
maxAttempts: number,
priority: number
renderPriority: number,
attempt = 1
) {
const [boost, urgentThreshold] = await Promise.all([
getConfigNumber("render.previewPriorityBoost"),
@@ -210,9 +237,9 @@ export async function createPreviewJob(
data: {
type: "PREVIEW_ONLY",
exportId: exportRow.id,
attempt: 1,
attempt,
maxAttempts,
priority: previewPriority(priority, boost, urgentThreshold),
priority: previewPriority(renderPriority, boost, urgentThreshold),
manifest: manifest as unknown as Prisma.InputJsonValue,
},
});
@@ -421,18 +448,17 @@ export async function retryPreview(exportId: string, actorId?: string) {
const last = await tx.renderJob.findFirst({
where: { exportId, type: "PREVIEW_ONLY" },
orderBy: { attempt: "desc" },
select: { attempt: true },
});
const job = await tx.renderJob.create({
data: {
type: "PREVIEW_ONLY",
exportId,
attempt: (last?.attempt ?? 0) + 1,
maxAttempts,
priority: last?.priority ?? 50,
manifest: manifest as unknown as Prisma.InputJsonValue,
},
});
const job = await createPreviewJob(
tx,
exportRow,
manifest,
maxAttempts,
await renderPriorityFor(tx, exportId),
(last?.attempt ?? 0) + 1
);
const updated = await applyTransition(tx, exportRow, "GENERATING_PREVIEW", {
type: "USER",
@@ -501,18 +527,17 @@ export async function regeneratePreview(exportId: string, input: RegeneratePrevi
const last = await tx.renderJob.findFirst({
where: { exportId, type: "PREVIEW_ONLY" },
orderBy: { attempt: "desc" },
select: { attempt: true },
});
const job = await tx.renderJob.create({
data: {
type: "PREVIEW_ONLY",
exportId,
attempt: (last?.attempt ?? 0) + 1,
maxAttempts,
priority: last?.priority ?? 50,
manifest: manifest as unknown as Prisma.InputJsonValue,
},
});
const job = await createPreviewJob(
tx,
refreshed,
manifest,
maxAttempts,
await renderPriorityFor(tx, exportId),
(last?.attempt ?? 0) + 1
);
const updated = await applyTransition(
tx,
+14 -11
View File
@@ -4,7 +4,7 @@ import { db } from "@/lib/db";
import { PipelineError } from "./errors";
import { applyTransition } from "./transitions";
import { getConfigBoolean, getConfigNumber, getConfigValue, getPreviewConfig } from "./config";
import { buildPreviewManifest, createPreviewJob, inheritPreviewMedia } from "./preview";
import { buildPreviewManifest, createPreviewJob, inheritPreviewMedia, renderPriorityFor } from "./preview";
/**
* Validation engine results (RenderPipeline2 §8). The worker runs the checks —
@@ -301,17 +301,20 @@ export async function overrideValidation(
const last = await tx.renderJob.findFirst({
where: { exportId, type: "PREVIEW_ONLY" },
orderBy: { attempt: "desc" },
select: { attempt: true },
});
const previewJob = await tx.renderJob.create({
data: {
type: "PREVIEW_ONLY",
exportId,
attempt: (last?.attempt ?? 0) + 1,
maxAttempts,
priority: last?.priority ?? 50,
manifest: manifest as unknown as Prisma.InputJsonValue,
},
});
// An export that failed validation has never had a preview job — validation
// runs before the preview stage — so the rank comes from its render, which
// is what keeps an urgent export urgent through an override. Someone is
// usually standing over an urgent shot when they wave a check through.
const previewJob = await createPreviewJob(
tx,
exportRow,
manifest,
maxAttempts,
await renderPriorityFor(tx, exportId),
(last?.attempt ?? 0) + 1
);
const updated = await applyTransition(tx, exportRow, "GENERATING_PREVIEW", {
type: "USER",