mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-11 06:30:03 +00:00
fix(engine,producer): classify blank vs psnr from a structural field, not the error message
Deepwork's request-changes on #2411 (twice): deFallbackReason's blank/psnr split still ran /blank/i.test(err.message) even after this PR's stated goal of moving off message-text parsing — a reworded message, a translated string, or a differently-shaped error crossing a module boundary could silently relabel a blank failure as psnr (or vice versa), corrupting the soak's telemetry taxonomy. DrawElementVerificationDetails now carries a required `kind: "blank" | "psnr"` field, set at all three real throw sites in captureStreamingStage.ts. The orchestrator derives deFallbackReason from getDrawElementVerificationDetails's kind instead of regexing the message. Making `kind` a required constructor argument means any future throw site that omits it fails to compile, closing the gap for good rather than just at today's three call sites. New tests in frameCapture.test.ts prove message-independence directly: kind survives a reworded message that says neither "blank" nor "psnr", and stays correctly "psnr" even when the message adversarially contains the substring "blank" — the exact scenario a regex-based classifier would get wrong.
This commit is contained in:
@@ -289,7 +289,7 @@ function createDrainFrameGuard(args: {
|
||||
} catch (err) {
|
||||
throw new DrawElementVerificationError(
|
||||
`blank drawElement frame ${idx}: ${buf.length}B < floor ${Math.round(floor)}B and recapture failed (${err instanceof Error ? err.message : String(err)})`,
|
||||
{ frameIndex: idx },
|
||||
{ kind: "blank", frameIndex: idx },
|
||||
);
|
||||
}
|
||||
if (retryBuf.equals(buf)) {
|
||||
@@ -307,7 +307,7 @@ function createDrainFrameGuard(args: {
|
||||
} else if (retryBuf.length < floor) {
|
||||
throw new DrawElementVerificationError(
|
||||
`blank drawElement frame ${idx}: ${buf.length}B (retry ${retryBuf.length}B) < floor ${Math.round(floor)}B`,
|
||||
{ frameIndex: idx },
|
||||
{ kind: "blank", frameIndex: idx },
|
||||
);
|
||||
} else {
|
||||
buf = retryBuf;
|
||||
@@ -341,7 +341,7 @@ function createDrainFrameGuard(args: {
|
||||
}
|
||||
throw new DrawElementVerificationError(
|
||||
`drawElement self-verify failed at frame ${idx}: ${db.toFixed(1)}dB < ${verifyMinDb}dB vs pre-injection screenshot${dumpDir ? ` (pair: ${dumpDir})` : ""}`,
|
||||
{ frameIndex: idx, failedDb: db, verifyThresholdDb: verifyMinDb },
|
||||
{ kind: "psnr", frameIndex: idx, failedDb: db, verifyThresholdDb: verifyMinDb },
|
||||
);
|
||||
}
|
||||
stats.verifyChecked += 1;
|
||||
|
||||
@@ -2729,15 +2729,17 @@ export async function executeRenderJob(
|
||||
throw err;
|
||||
const isMemoryExhaustion = !isVerifyError && isMemoryExhaustionError(err);
|
||||
deSelfVerifyFallback = isVerifyError;
|
||||
// `kind` is a structural field on the error (DrawElementVerificationDetails),
|
||||
// never derived from message text — a reworded message, a translated
|
||||
// string, or a cross-module/serialized error must never be able to
|
||||
// flip "blank" into "psnr" or vice versa (review finding).
|
||||
const verifyDetails = isVerifyError ? getDrawElementVerificationDetails(err) : undefined;
|
||||
deFallbackReason = isVerifyError
|
||||
? /blank/i.test(err instanceof Error ? err.message : "")
|
||||
? "blank"
|
||||
: "psnr"
|
||||
? (verifyDetails?.kind ?? "psnr")
|
||||
: isMemoryExhaustion
|
||||
? "oom"
|
||||
: "capture_error";
|
||||
if (isVerifyError) {
|
||||
const verifyDetails = getDrawElementVerificationDetails(err);
|
||||
deFallbackFailedDb = roundDb(verifyDetails?.failedDb);
|
||||
deFallbackFrameIndex = verifyDetails?.frameIndex;
|
||||
deFallbackThresholdDb = roundDb(verifyDetails?.verifyThresholdDb);
|
||||
|
||||
Reference in New Issue
Block a user