diff --git a/RenderPipeline2.md b/RenderPipeline2.md index 5d18ffa..70e87de 100644 --- a/RenderPipeline2.md +++ b/RenderPipeline2.md @@ -1132,6 +1132,10 @@ Cost note: the AE preview pass roughly doubles machine time per export. This mat Finalize (E13) then creates the `Version` row server-side (Section 6.4) in pipeline mode — visible internally, never client-shared, no task/shot status changes. +**Re-exports inherit approval, and carry no artist-facing text.** A shot is routinely re-exported after client approval — most often for an unscaled/un-punched-in delivery pass — and that must not read as new work awaiting review. The new `Version.approvalStatus` is copied from the task's previous latest version rather than left at its `PENDING_REVIEW` default; an export that starts a task's history has nothing to inherit and gets `PENDING_REVIEW` as before. `Version.notes` is left unset rather than stamped with an internal description: `notes` is the artist's message to reviewers and is rendered verbatim in the client portal and the review player, so pipeline provenance belongs on the Export and its `ExportEvent` timeline — where a human would go looking for it — not in a field a client can read. + +**Known gap:** `shareWithClient` (and the per-task share action) promote whichever `Version` is `isLatest` for a task to `isClientVisible: true`, with no awareness that a pipeline-created version is meant to stay internal permanently (this section, "never client-shared"). Re-sharing a shot after a pipeline re-export will currently expose the internal version if it is the task's latest. Closing this needs a way to identify a pipeline-origin version at the share call sites — `Export.versionId` is presently a one-way scalar with no inverse relation on `Version` — and is not yet implemented. + ### 9.3a Where a preview sits in the queue The queue is ordered `priority ASC, createdAt ASC`, and a preview job is only created when its render *finishes* — so at equal priority it sorts behind every render queued alongside it. Ten shots queued together would run as ten renders and then ten previews, with nothing reviewable until the end. diff --git a/lib/render-pipeline/preview.ts b/lib/render-pipeline/preview.ts index ded5eed..471764c 100644 --- a/lib/render-pipeline/preview.ts +++ b/lib/render-pipeline/preview.ts @@ -319,6 +319,7 @@ export async function finalizePreview(jobId: string, machineId: string, body: Fi let versionId: string | null = null; let versionReplaced = false; + let inheritedApproval: string | null = null; if (artifacts.previewMovKey) { const mediaFields = { fileUrl: `/api/files/${artifacts.previewMovKey}`, @@ -350,6 +351,20 @@ export async function finalizePreview(jobId: string, machineId: string, body: Fi // Version numbering stays monotonic for the task (existing review flows // rely on the newest version having the highest number) while matching // the export version whenever it is ahead. + // A re-export does not change the picture — it re-renders EXRs for + // delivery from a shot that has usually already been approved. A fresh + // Version defaults to PENDING_REVIEW, which is what the dashboard, the + // shot page and the client portal all read, so an approved shot would + // silently reappear as awaiting review. The approval carries over from + // the version this one supersedes. + const previousLatest = exportRow.taskId + ? await tx.version.findFirst({ + where: { taskId: exportRow.taskId }, + orderBy: { versionNumber: "desc" }, + select: { approvalStatus: true, versionNumber: true }, + }) + : null; + const taskMax = exportRow.taskId ? await tx.version.aggregate({ where: { taskId: exportRow.taskId }, @@ -365,12 +380,17 @@ export async function finalizePreview(jobId: string, machineId: string, body: Fi shotId: exportRow.shotId, taskId: exportRow.taskId, artistId: exportRow.submittedById, - notes: `Pipeline render ${exportRow.versionString} (internal only)`, + // `notes` is the artist's message to reviewers and is shown in the + // client portal, so the pipeline stays out of it. What this version + // is and where it came from lives on the Export and its timeline, + // which is where someone would go looking anyway. + approvalStatus: previousLatest?.approvalStatus ?? "PENDING_REVIEW", isLatest: true, isClientVisible: false, // never shared to the client portal (§10.0) }, }); versionId = version.id; + inheritedApproval = previousLatest?.approvalStatus ?? null; } if (exportRow.taskId) { @@ -416,7 +436,7 @@ export async function finalizePreview(jobId: string, machineId: string, body: Fi (versionId ? versionReplaced ? " — review version media replaced" - : " — review version created" + : ` — review version created (${inheritedApproval ?? "PENDING_REVIEW"})` : " — no preview media reported") + (taskRenamed ? ` — task renamed to ${taskTitle}` : ""), },