fix(producer): rethrow disk drawElement verify failure past completeness check

This commit is contained in:
Vance Ingalls
2026-07-23 16:06:22 -07:00
parent 9fc1c2f159
commit c85cfae8fa
3 changed files with 45 additions and 8 deletions
@@ -140,6 +140,28 @@ describe("CapturePlan", () => {
});
});
it("forces screenshot on a disk-plan drawElement verify failure (PRINFRA-352 recovery)", () => {
// Parallel disk workers under the explicit fast-capture opt-in verify their
// own captured samples; a breach must re-render the DISK plan on the
// screenshot baseline (not throw, and not stay on drawElement).
const disk = createCapturePlan({
workerCount: 2,
forceScreenshot: false,
useStreamingEncode: false,
useLayeredComposite: false,
usePageSideCompositing: false,
hasHdrContent: false,
needsAlpha: false,
});
const next = replanAfterFailure(disk, { kind: "draw_element_verification" });
expect(next).toMatchObject({
kind: "sdr_disk",
forceScreenshot: true,
forceParallelStream: false,
workerCount: 2,
});
});
it("rejects a streaming transition from a non-streaming plan", () => {
const disk = createCapturePlan({
workerCount: 2,
@@ -63,6 +63,7 @@ import {
executeParallelCapture,
getCapturePerfSummary,
psnrDb,
resolveDeVerifyMinDb,
recaptureDrawElementFrameForVerify,
completeDeferredDrawElementInit,
initializeSession,
@@ -257,14 +258,14 @@ function createDrainFrameGuard(args: {
// check stops meaning anything); above ~60dB natural DE-vs-screenshot
// encoder differences (~45dB+) would force a screenshot fallback on every
// verified render. Out-of-range or malformed values fall back to 32.
const verifyMinDbRaw = Number(process.env.HF_DE_VERIFY_MIN_DB ?? "32");
const verifyMinDb =
Number.isFinite(verifyMinDbRaw) && verifyMinDbRaw >= 10 && verifyMinDbRaw <= 60
? verifyMinDbRaw
: 32;
if (process.env.HF_DE_VERIFY_MIN_DB !== undefined && verifyMinDb !== verifyMinDbRaw) {
log.warn("[Render] HF_DE_VERIFY_MIN_DB out of range [10,60]; using 32", {
raw: process.env.HF_DE_VERIFY_MIN_DB,
// Single-sourced clamp (psnr.ts) so the disk and streaming verify paths can
// never apply different PSNR floors to the same composition. The warn stays
// here because only this path has a logger in scope.
const verifyMinDb = resolveDeVerifyMinDb();
const rawEnv = process.env.HF_DE_VERIFY_MIN_DB;
if (rawEnv !== undefined && Number(rawEnv) !== verifyMinDb) {
log.warn(`[Render] HF_DE_VERIFY_MIN_DB out of range [10,60]; using ${verifyMinDb}`, {
raw: rawEnv,
});
}
const sizes: number[] = [];
@@ -1018,6 +1018,20 @@ export async function executeDiskCaptureWithAdaptiveRetry(options: {
if (failure.kind === "cancelled") {
throw error;
}
// A drawElement self-verify breach (a parallel disk worker's sampled
// frame diverged from its pre-injection ground truth) is a CORRECTNESS
// failure, not a missing-frame one: the damaged frames are written
// complete to disk, so the presence/size-only findMissingFrameRanges
// below would count them present and wrongly return success — shipping
// the exact compositor damage this verify exists to catch. Rethrow so
// the orchestrator's disk-stage screenshot retry fires (mirrors the
// `cancelled` guard; a worker-halving retry here would only re-run
// drawElement and re-damage). Structural detection walks the aggregated
// CaptureFailure → worker CaptureFailure → DrawElementVerificationError
// cause chain.
if (isDrawElementVerificationError(error)) {
throw error;
}
const remaining = findMissingFrameRanges(
options.totalFrames,
options.framesDir,