mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-11 23:00: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:
@@ -229,12 +229,14 @@ describe("navigation diagnostics", () => {
|
||||
describe("DrawElementVerificationError details", () => {
|
||||
it("carries frameIndex/failedDb/verifyThresholdDb when provided", () => {
|
||||
const err = new DrawElementVerificationError("drawElement self-verify failed at frame 649", {
|
||||
kind: "psnr",
|
||||
frameIndex: 649,
|
||||
failedDb: 28.4,
|
||||
verifyThresholdDb: 32,
|
||||
});
|
||||
expect(isDrawElementVerificationError(err)).toBe(true);
|
||||
expect(getDrawElementVerificationDetails(err)).toEqual({
|
||||
kind: "psnr",
|
||||
frameIndex: 649,
|
||||
failedDb: 28.4,
|
||||
verifyThresholdDb: 32,
|
||||
@@ -242,8 +244,11 @@ describe("DrawElementVerificationError details", () => {
|
||||
});
|
||||
|
||||
it("omits fields that weren't supplied (blank-frame throws have no dB)", () => {
|
||||
const err = new DrawElementVerificationError("blank drawElement frame 12", { frameIndex: 12 });
|
||||
expect(getDrawElementVerificationDetails(err)).toEqual({ frameIndex: 12 });
|
||||
const err = new DrawElementVerificationError("blank drawElement frame 12", {
|
||||
kind: "blank",
|
||||
frameIndex: 12,
|
||||
});
|
||||
expect(getDrawElementVerificationDetails(err)).toEqual({ kind: "blank", frameIndex: 12 });
|
||||
});
|
||||
|
||||
it("returns undefined for a non-verification error", () => {
|
||||
@@ -252,11 +257,49 @@ describe("DrawElementVerificationError details", () => {
|
||||
|
||||
it("finds details through a wrapping cause chain (producer's CaptureStageError)", () => {
|
||||
const inner = new DrawElementVerificationError("psnr breach", {
|
||||
kind: "psnr",
|
||||
frameIndex: 5,
|
||||
failedDb: 12.1,
|
||||
});
|
||||
const wrapper = new Error("capture stage failed", { cause: inner });
|
||||
expect(isDrawElementVerificationError(wrapper)).toBe(true);
|
||||
expect(getDrawElementVerificationDetails(wrapper)).toEqual({ frameIndex: 5, failedDb: 12.1 });
|
||||
expect(getDrawElementVerificationDetails(wrapper)).toEqual({
|
||||
kind: "psnr",
|
||||
frameIndex: 5,
|
||||
failedDb: 12.1,
|
||||
});
|
||||
});
|
||||
|
||||
it("carries kind='blank'/'psnr' as a structural field, not derived from message text", () => {
|
||||
const blankErr = new DrawElementVerificationError("blank drawElement frame 12", {
|
||||
kind: "blank",
|
||||
frameIndex: 12,
|
||||
});
|
||||
const psnrErr = new DrawElementVerificationError(
|
||||
"drawElement self-verify failed at frame 649",
|
||||
{ kind: "psnr", frameIndex: 649, failedDb: 28.4, verifyThresholdDb: 32 },
|
||||
);
|
||||
expect(getDrawElementVerificationDetails(blankErr)?.kind).toBe("blank");
|
||||
expect(getDrawElementVerificationDetails(psnrErr)?.kind).toBe("psnr");
|
||||
});
|
||||
|
||||
it("kind survives even when the message text disagrees with (or omits) the word psnr/blank", () => {
|
||||
// A reworded message that says neither "blank" nor "psnr" — a regex over
|
||||
// message text would have no signal here; the structural kind must still
|
||||
// report correctly.
|
||||
const reworded = new DrawElementVerificationError("frame 12 failed verification", {
|
||||
kind: "blank",
|
||||
frameIndex: 12,
|
||||
});
|
||||
expect(getDrawElementVerificationDetails(reworded)?.kind).toBe("blank");
|
||||
|
||||
// Adversarial: a psnr failure whose message happens to mention "blank"
|
||||
// (e.g. quoting a neighboring log line) — the structural kind must not
|
||||
// flip just because the substring "blank" appears in the text.
|
||||
const adversarial = new DrawElementVerificationError(
|
||||
"drawElement self-verify failed at frame 5 (previous frame was blank-guard-accepted)",
|
||||
{ kind: "psnr", frameIndex: 5, failedDb: 12.1, verifyThresholdDb: 32 },
|
||||
);
|
||||
expect(getDrawElementVerificationDetails(adversarial)?.kind).toBe("psnr");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user