diff --git a/examples/aws-lambda/template.yaml b/examples/aws-lambda/template.yaml
index 34b2a0143..7ae0b7f6f 100644
--- a/examples/aws-lambda/template.yaml
+++ b/examples/aws-lambda/template.yaml
@@ -265,8 +265,8 @@ Resources:
- PlanProtocolUnsupportedError
- VIDEO_SOURCE_UNRENDERABLE
- INVALID_VIDEO_METADATA
- - MARKUP_NOT_MEDIA
- - MarkupNotMediaError
+ - NOT_MEDIA_PAYLOAD
+ - NotMediaPayloadError
- PLAN_ARTIFACT_DIGEST_MISMATCH
- FORMAT_NOT_SUPPORTED_IN_DISTRIBUTED
MaxAttempts: 0
@@ -311,8 +311,8 @@ Resources:
- PLAN_V2_INTEGRITY_UNRECOVERABLE
- VIDEO_SOURCE_UNRENDERABLE
- INVALID_VIDEO_METADATA
- - MARKUP_NOT_MEDIA
- - MarkupNotMediaError
+ - NOT_MEDIA_PAYLOAD
+ - NotMediaPayloadError
- PlanV2IntegrityError
- PLAN_ARTIFACT_DIGEST_MISMATCH
- FORMAT_NOT_SUPPORTED_IN_DISTRIBUTED
diff --git a/packages/aws-lambda/src/cdk/HyperframesRenderStack.snapshot.test.ts b/packages/aws-lambda/src/cdk/HyperframesRenderStack.snapshot.test.ts
index defd4372b..c7f27f503 100644
--- a/packages/aws-lambda/src/cdk/HyperframesRenderStack.snapshot.test.ts
+++ b/packages/aws-lambda/src/cdk/HyperframesRenderStack.snapshot.test.ts
@@ -78,8 +78,8 @@ const EXPECTED_NON_RETRYABLE_ERRORS = new Set([
"PLAN_V2_INTEGRITY_UNRECOVERABLE",
"VIDEO_SOURCE_UNRENDERABLE",
"INVALID_VIDEO_METADATA",
- "MARKUP_NOT_MEDIA",
- "MarkupNotMediaError",
+ "NOT_MEDIA_PAYLOAD",
+ "NotMediaPayloadError",
"PlanV2IntegrityError",
"PLAN_ARTIFACT_DIGEST_MISMATCH",
"FORMAT_NOT_SUPPORTED_IN_DISTRIBUTED",
diff --git a/packages/aws-lambda/src/cdk/HyperframesRenderStack.ts b/packages/aws-lambda/src/cdk/HyperframesRenderStack.ts
index db74a8271..baa7bd6b1 100644
--- a/packages/aws-lambda/src/cdk/HyperframesRenderStack.ts
+++ b/packages/aws-lambda/src/cdk/HyperframesRenderStack.ts
@@ -206,8 +206,8 @@ export class HyperframesRenderStack extends Construct {
"PLAN_V2_INTEGRITY_UNRECOVERABLE",
"VIDEO_SOURCE_UNRENDERABLE",
"INVALID_VIDEO_METADATA",
- "MARKUP_NOT_MEDIA",
- "MarkupNotMediaError",
+ "NOT_MEDIA_PAYLOAD",
+ "NotMediaPayloadError",
"PlanV2IntegrityError",
"PLAN_ARTIFACT_DIGEST_MISMATCH",
"FORMAT_NOT_SUPPORTED_IN_DISTRIBUTED",
diff --git a/packages/aws-lambda/src/handler.ts b/packages/aws-lambda/src/handler.ts
index 92fbc0c8b..bd10c12c2 100644
--- a/packages/aws-lambda/src/handler.ts
+++ b/packages/aws-lambda/src/handler.ts
@@ -152,7 +152,7 @@ function normalizeTerminalErrorName(error: unknown): void {
candidate.code === "PLAN_V2_INTEGRITY_UNRECOVERABLE" ||
candidate.code === "FONT_FETCH_FAILED" ||
candidate.code === "FONT_FETCH_UNAVAILABLE" ||
- candidate.code === "MARKUP_NOT_MEDIA" ||
+ candidate.code === "NOT_MEDIA_PAYLOAD" ||
candidate.code === "VIDEO_SOURCE_UNRENDERABLE" ||
candidate.code === "VIDEO_EXTRACTION_FAILED" ||
candidate.code === "INVALID_VIDEO_METADATA"
diff --git a/packages/engine/src/index.ts b/packages/engine/src/index.ts
index d4b9adb4a..5b5f71f7f 100644
--- a/packages/engine/src/index.ts
+++ b/packages/engine/src/index.ts
@@ -274,12 +274,12 @@ export {
} from "./utils/ffprobe.js";
export {
- MARKUP_NOT_MEDIA,
- MarkupNotMediaError,
- assertNotMarkupPayload,
+ NOT_MEDIA_PAYLOAD,
+ NotMediaPayloadError,
+ assertMediaPayload,
fingerprintElementId,
- isMarkupPayload,
-} from "./utils/markupPayload.js";
+ isNotMediaPayload,
+} from "./utils/notMediaPayload.js";
export { assertPublicHttpsUrl, downloadToTemp, isHttpUrl } from "./utils/urlDownloader.js";
export {
diff --git a/packages/engine/src/services/audioMixer.test.ts b/packages/engine/src/services/audioMixer.test.ts
index cb37baa10..96f5a6606 100644
--- a/packages/engine/src/services/audioMixer.test.ts
+++ b/packages/engine/src/services/audioMixer.test.ts
@@ -120,7 +120,7 @@ describe("processCompositionAudio", () => {
// probe entirely when the element carries an authored duration, and used to
// surface as `prepare/ffmpeg_failed` with owner "system" — an authoring bug
// paged as a platform fault, after every frame had already been captured.
- it("classifies a markup audio source as a user-owned invalid media source", async () => {
+ it("classifies a document audio source as a user-owned invalid media source", async () => {
const baseDir = mkdtempSync(join(tmpdir(), "hf-audio-base-"));
const workDir = mkdtempSync(join(tmpdir(), "hf-audio-work-"));
tempDirs.push(baseDir, workDir);
diff --git a/packages/engine/src/services/audioMixer.ts b/packages/engine/src/services/audioMixer.ts
index 21b56fa71..f27a9eabb 100644
--- a/packages/engine/src/services/audioMixer.ts
+++ b/packages/engine/src/services/audioMixer.ts
@@ -9,7 +9,7 @@ import { closeSync, existsSync, mkdirSync, mkdtempSync, openSync, rmSync, writeF
import { join, dirname } from "path";
import { parseHTML } from "linkedom";
import { extractAudioMetadata } from "../utils/ffprobe.js";
-import { isMarkupPayload } from "../utils/markupPayload.js";
+import { isNotMediaPayload } from "../utils/notMediaPayload.js";
import { downloadToTemp, isHttpUrl } from "../utils/urlDownloader.js";
import { DEFAULT_CONFIG, type EngineConfig } from "../config.js";
import { formatFfmpegError, runFfmpeg, type RunFfmpegResult } from "../utils/runFfmpeg.js";
@@ -734,13 +734,13 @@ export async function processCompositionAudio(
return;
}
- // STUDIO-5433: an audio src that resolved to an HTML/XML document (an
+ // STUDIO-5433: an audio src that resolved to a text document (an
// unresolved nested-composition preview URL, or a 403/404 body served
// with a 200) never reaches the probe below when the element carries an
// authored duration or `loop`. It then fails inside ffmpeg as
// `prepare/ffmpeg_failed` with owner "system" — an authoring bug paged
// as a platform fault, after every frame has already been captured.
- if (await isMarkupPayload(srcPath)) {
+ if (await isNotMediaPayload(srcPath)) {
failures.push({
stage: "source",
reason: "invalid_media",
@@ -748,7 +748,7 @@ export async function processCompositionAudio(
retryable: false,
elementId: element.id,
detail: boundedDetail(
- `Audio element ${element.id} source is a markup document (HTML/XML), not media`,
+ `Audio element ${element.id} source is a text document (HTML/XML/JSON), not media`,
),
});
return;
diff --git a/packages/engine/src/utils/markupPayload.test.ts b/packages/engine/src/utils/notMediaPayload.test.ts
similarity index 70%
rename from packages/engine/src/utils/markupPayload.test.ts
rename to packages/engine/src/utils/notMediaPayload.test.ts
index db90cf3e4..bdfdb844a 100644
--- a/packages/engine/src/utils/markupPayload.test.ts
+++ b/packages/engine/src/utils/notMediaPayload.test.ts
@@ -3,11 +3,11 @@ import { tmpdir } from "node:os";
import { join } from "node:path";
import { describe, expect, it } from "vitest";
import {
- assertNotMarkupPayload,
+ assertMediaPayload,
fingerprintElementId,
- isMarkupPayload,
- MarkupNotMediaError,
-} from "./markupPayload.js";
+ isNotMediaPayload,
+ NotMediaPayloadError,
+} from "./notMediaPayload.js";
function writeFixture(name: string, contents: string | Buffer): string {
const dir = mkdtempSync(join(tmpdir(), "hf-markup-sniff-"));
@@ -16,7 +16,7 @@ function writeFixture(name: string, contents: string | Buffer): string {
return filePath;
}
-describe("isMarkupPayload", () => {
+describe("isNotMediaPayload", () => {
it.each([
["doctype", "\n
"],
["bare html tag, uppercase", "hi"],
@@ -31,11 +31,15 @@ describe("isMarkupPayload", () => {
// signed URL or an ACL change, served as a 200 with an S3 error body.
["s3 error document", 'AccessDenied'],
["comment first", "\n"],
+ // Replicate answers a dead asset this way, and a gateway in front of it can
+ // relay the body with a 200 — the shape a `<` -only check misses.
+ ["json object error body", '{"detail": "requested file not found"}'],
+ ["json array body", '[{"error": "gone"}]'],
])("detects %s", async (_label, contents) => {
- expect(await isMarkupPayload(writeFixture("payload", contents))).toBe(true);
+ expect(await isNotMediaPayload(writeFixture("payload", contents))).toBe(true);
});
- it("detects markup behind a UTF-8 BOM and leading whitespace", async () => {
+ it("detects a document behind a UTF-8 BOM and leading whitespace", async () => {
const filePath = writeFixture(
"bom.html",
Buffer.concat([
@@ -43,15 +47,15 @@ describe("isMarkupPayload", () => {
Buffer.from("\n \t"),
]),
);
- expect(await isMarkupPayload(filePath)).toBe(true);
+ expect(await isNotMediaPayload(filePath)).toBe(true);
});
- it("detects markup behind a leading NUL run", async () => {
+ it("detects a document behind a leading NUL run", async () => {
const filePath = writeFixture(
"nul.html",
Buffer.concat([Buffer.from([0x00, 0x00, 0x00]), Buffer.from("x")]),
);
- expect(await isMarkupPayload(filePath)).toBe(true);
+ expect(await isNotMediaPayload(filePath)).toBe(true);
});
it.each([
@@ -63,15 +67,15 @@ describe("isMarkupPayload", () => {
const body = Buffer.from("", "utf16le");
const bytes = _label === "little-endian" ? body : body.swap16();
const filePath = writeFixture("utf16.html", Buffer.concat([Buffer.from(bom), bytes]));
- expect(await isMarkupPayload(filePath)).toBe(true);
+ expect(await isNotMediaPayload(filePath)).toBe(true);
});
- it("detects markup preceded by more whitespace than a short read would cover", async () => {
+ it("detects nothing when padding overruns the sniff window", async () => {
const filePath = writeFixture("padded.html", `${" ".repeat(600)}`);
// 600 bytes of padding overruns the 512-byte sniff window, so the verdict
// has to be "unknown" (false) rather than a misread — asserted here so the
// window size is a deliberate, visible bound rather than an accident.
- expect(await isMarkupPayload(filePath)).toBe(false);
+ expect(await isNotMediaPayload(filePath)).toBe(false);
});
it.each([
@@ -85,50 +89,50 @@ describe("isMarkupPayload", () => {
["adts aac", [0xff, 0xf1, 0x50, 0x80]],
["mpeg-ps", [0x00, 0x00, 0x01, 0xba]],
])("does not flag a %s container", async (_label, bytes) => {
- expect(await isMarkupPayload(writeFixture("clip.bin", Buffer.from(bytes)))).toBe(false);
+ expect(await isNotMediaPayload(writeFixture("clip.bin", Buffer.from(bytes)))).toBe(false);
});
- it("does not flag a container that merely contains markup further in", async () => {
+ it("does not flag a container that merely contains a document byte further in", async () => {
const filePath = writeFixture(
"not-html.bin",
Buffer.concat([Buffer.from([0x00, 0x00, 0x01, 0xba]), Buffer.from(" {
- expect(await isMarkupPayload(writeFixture("empty.bin", ""))).toBe(false);
+ expect(await isNotMediaPayload(writeFixture("empty.bin", ""))).toBe(false);
});
- it("reports not-markup instead of throwing when the path is a directory", async () => {
+ it("reports not-a-document instead of throwing when the path is a directory", async () => {
// `existsSync` passes for a directory, so callers reach the sniff with one.
// The read fails EISDIR; classifying rather than propagating keeps the real
// probe's own error as the one the caller sees.
const dir = mkdtempSync(join(tmpdir(), "hf-markup-sniff-dir-"));
mkdirSync(join(dir, "assets"));
- expect(await isMarkupPayload(join(dir, "assets"))).toBe(false);
+ expect(await isNotMediaPayload(join(dir, "assets"))).toBe(false);
});
- it("reports not-markup instead of throwing when the file is missing", async () => {
+ it("reports not-a-document instead of throwing when the file is missing", async () => {
const dir = mkdtempSync(join(tmpdir(), "hf-markup-sniff-gone-"));
- expect(await isMarkupPayload(join(dir, "evicted.mp4"))).toBe(false);
+ expect(await isNotMediaPayload(join(dir, "evicted.mp4"))).toBe(false);
});
});
-describe("assertNotMarkupPayload", () => {
- it("throws MarkupNotMediaError carrying routing metadata and a hashed element key", async () => {
+describe("assertMediaPayload", () => {
+ it("throws NotMediaPayloadError carrying routing metadata and a hashed element key", async () => {
const filePath = writeFixture("nested.html", "");
let caught: unknown;
try {
- await assertNotMarkupPayload(filePath, "aroll-scene-3");
+ await assertMediaPayload(filePath, "aroll-scene-3");
} catch (error) {
caught = error;
}
- expect(caught).toBeInstanceOf(MarkupNotMediaError);
- const error = caught as MarkupNotMediaError;
- expect(error.code).toBe("MARKUP_NOT_MEDIA");
+ expect(caught).toBeInstanceOf(NotMediaPayloadError);
+ const error = caught as NotMediaPayloadError;
+ expect(error.code).toBe("NOT_MEDIA_PAYLOAD");
expect(error.owner).toBe("user");
expect(error.retryable).toBe(false);
expect(error.elementFingerprints).toEqual([fingerprintElementId("aroll-scene-3")]);
@@ -145,26 +149,26 @@ describe("assertNotMarkupPayload", () => {
'',
);
- const error = await assertNotMarkupPayload(
+ const error = await assertMediaPayload(
filePath,
"https://cdn.example.com/tenants/acme-corp/projects/secret-q4/streamed-preview.html",
- ).catch((caught: unknown) => caught as MarkupNotMediaError);
+ ).catch((caught: unknown) => caught as NotMediaPayloadError);
expect(error.message).not.toContain("acme-corp");
expect(error.message).not.toContain("secret-q4");
expect(error.message).not.toContain("cdn.example.com");
expect(error.message).not.toContain("tok_9fA3xQ7pLz");
expect(error.message).toContain("unresolved");
- expect(error.message).toContain("403/404");
+ expect(error.message).toContain("success status");
});
it("resolves for a real container", async () => {
const filePath = writeFixture("clip.mp4", Buffer.from([0x00, 0x00, 0x00, 0x18, 0x66, 0x74]));
- await expect(assertNotMarkupPayload(filePath, "v1")).resolves.toBeUndefined();
+ await expect(assertMediaPayload(filePath, "v1")).resolves.toBeUndefined();
});
it("caps the fingerprint list so the message stays bounded", async () => {
- const error = new MarkupNotMediaError(
+ const error = new NotMediaPayloadError(
Array.from({ length: 12 }, (_unused, index) => fingerprintElementId(`el-${index}`)),
);
expect(error.message).toContain("+4");
diff --git a/packages/engine/src/utils/markupPayload.ts b/packages/engine/src/utils/notMediaPayload.ts
similarity index 56%
rename from packages/engine/src/utils/markupPayload.ts
rename to packages/engine/src/utils/notMediaPayload.ts
index 5cbac187e..446e37ad0 100644
--- a/packages/engine/src/utils/markupPayload.ts
+++ b/packages/engine/src/utils/notMediaPayload.ts
@@ -1,14 +1,14 @@
import { createHash } from "node:crypto";
import { open as openFile } from "node:fs/promises";
-export const MARKUP_NOT_MEDIA = "MARKUP_NOT_MEDIA" as const;
+export const NOT_MEDIA_PAYLOAD = "NOT_MEDIA_PAYLOAD" as const;
/** Cap the joined fingerprint list so the message stays bounded. */
const MAX_LISTED_FINGERPRINTS = 8;
/**
- * Thrown when a file behind a `