Skip to content

feat(producer): refuse distributed-unsupported formats (webm + HDR mp4)#815

Merged
jrusso1020 merged 1 commit into
mainfrom
05-13-feat_producer_refuse_distributed-unsupported_formats_early
May 14, 2026
Merged

feat(producer): refuse distributed-unsupported formats (webm + HDR mp4)#815
jrusso1020 merged 1 commit into
mainfrom
05-13-feat_producer_refuse_distributed-unsupported_formats_early

Conversation

@jrusso1020
Copy link
Copy Markdown
Collaborator

@jrusso1020 jrusso1020 commented May 13, 2026

What

Phase 3 of the distributed rendering plan: §11 PR 3.5 format banlist. Extends plan() to refuse two v1-unsupported formats up front with a typed non-retryable FormatNotSupportedInDistributedError (code === "FORMAT_NOT_SUPPORTED_IN_DISTRIBUTED").

Why

Both webm and HDR mp4 are documented as deferred to v1.5 (§7.2 + §12), but until this PR the only signal at the runtime layer is the in-process pipeline silently producing wrong output (chunk concat-copy doesn't round-trip VP9; HDR signaling gets stripped at the chunk boundary). Failing fast at plan() time keeps adopters from spending fan-out compute on a render that can't succeed and gives them a typed error code their workflow adapter can route on.

How

  • New exports in services/distributed/plan.ts:
    • FORMAT_NOT_SUPPORTED_IN_DISTRIBUTED — non-retryable error code matching §11's wording.
    • FormatNotSupportedInDistributedError — typed error class with code, format, and reason fields. Message names the rejected format and tells adopters to fall back to the in-process renderer (executeRenderJob) which has full format support.
    • rejectUnsupportedDistributedFormat(config) — pure helper exported separately so adapters can run the same gate at their input layer (Step Functions input validation, Temporal workflow start) before the activity even runs.
  • plan() calls rejectUnsupportedDistributedFormat(config) as the first line of the function — BEFORE mkdirSync(planDir) so a banned input never produces a partial planDir.
  • Replaced the previous ad-hoc if (hdrMode === "force-hdr") throw new Error(...) with the typed error class.

What did NOT change

executeRenderJob, the in-process orchestrator, the hyperframes render CLI, producer HTTP routes — all unchanged. The in-process renderer continues to accept webm + HDR (its existing functionality).

Test plan

  • Unit tests added — packages/producer/src/services/distributed/planFormatBanlist.test.ts. 5 cases:
    • rejectUnsupportedDistributedFormat accepts the v1-supported formats (mp4, mov, png-sequence) with both auto and force-sdr hdrMode.
    • Rejects webm — error has code === FORMAT_NOT_SUPPORTED_IN_DISTRIBUTED, format === "webm", message mentions in-process renderer.
    • Rejects HDR mp4 (hdrMode === "force-hdr") — error has format === "mp4-hdr", message mentions HDR.
    • End-to-end via plan(): webm throws with no planDir leaking to disk.
    • End-to-end via plan(): HDR mp4 throws with no planDir leaking to disk.
  • bun test packages/producer/src/services/distributed/ — 30 pass.
  • bun run --filter @hyperframes/producer typecheck — clean.
  • bunx oxlint + bunx oxfmt --check — clean on changed files.
  • Producer Docker regression harness — pending CI. executeRenderJob is unchanged; PSNR baselines should hold.

This is PR 5 of a 6-PR Phase 3 stack:

🤖 Generated with Claude Code

@jrusso1020 jrusso1020 changed the title feat(producer): refuse distributed-unsupported formats early feat(producer): refuse distributed-unsupported formats (webm + HDR mp4) May 13, 2026
@jrusso1020 jrusso1020 force-pushed the 05-13-feat_producer_enforce_plandir_size_cap_with_plan_too_large branch from 27a3d70 to 5f0239a Compare May 13, 2026 23:02
@jrusso1020 jrusso1020 force-pushed the 05-13-feat_producer_refuse_distributed-unsupported_formats_early branch from 07b1a71 to 60e67b7 Compare May 13, 2026 23:02
miguel-heygen
miguel-heygen previously approved these changes May 13, 2026
Copy link
Copy Markdown
Collaborator

@miguel-heygen miguel-heygen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: refuse distributed-unsupported formats (webm + HDR mp4)

Good upgrade from ad-hoc throws to a typed error class. Validation runs before mkdirSync(planDir) so banned configs never leak partial directories — tests verify existsSync(planDir) is false after the throw. Exporting rejectUnsupportedDistributedFormat separately lets adapters gate at their own input layer.

Minor note

rejectUnsupportedDistributedFormat checks hdrMode === "force-hdr" but reports format: "mp4-hdr" regardless of the actual format. A mov + force-hdr would be rejected with the wrong format in the error. Low severity — mov + force-hdr is likely an edge case — but worth matching the actual config format in the error.

LGTM.

Copy link
Copy Markdown
Collaborator

@vanceingalls vanceingalls left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Format banlist for distributed mode — refuses webm + HDR mp4 with a typed non-retryable error before any planDir work happens. Replaces the ("force-hdr" as unknown) cast from #808 with a proper error class. Tight, targeted, well-tested.

Strengths

  • plan.ts:46-83FormatNotSupportedInDistributedError carries format AND reason fields. Adapter retry policies can route on code, observability can group by format, error messages get the user-friendly reason. Three orthogonal axes from one error class.
  • plan.ts:63-84 — exporting rejectUnsupportedDistributedFormat as a pure helper means adapters can pre-validate at their input layer (Step Functions input validation, Temporal workflow start) before the activity even runs. The error class is the same one plan() would have thrown — so adapter-side and activity-side rejections look identical to the workflow.
  • plan.ts:447-450 — the validator runs as the very first line of plan(), BEFORE mkdirSync(planDir). End-to-end tests at planFormatBanlist.test.ts:222-267 assert existsSync(planDir) === false post-throw — pins the no-leaked-partial-planDir contract.
  • Replaces the ("force-hdr" as unknown) cast from #808 with the typed class. Cleanup that also widens the error surface.

Findings

important — the banlist enforces at plan-time but the chunk worker doesn't re-validate (plan.ts:447, renderChunk.ts:622-700)

rejectUnsupportedDistributedFormat runs once at plan time. The chunk worker reads plan.dimensions.format and uses it to branch (renderChunk.ts:684, assemble.ts:483) but never re-checks against the banlist. So a planDir hand-crafted (or generated by an out-of-date producer) with plan.dimensions.format === "webm" would skip the validator and run all the way to chunk capture, where it'd produce garbage instead of erroring with the typed code.

This is Rule 2 territory: the contract ("distributed mode rejects webm + HDR") is satisfied at one site (plan()), but every consumer of plan.dimensions.format is implicitly trusting the contract holds. Cheap defense: call rejectUnsupportedDistributedFormat({ format: plan.dimensions.format }) at the top of renderChunk and assemble too. Three lines, same error class, no behavior change on valid inputs.

important — hdrMode is checked but force-hdr could still leak via producerConfig (plan.ts:77-83)

The validator checks config.hdrMode, but a caller passing producerConfig: { ... hdrMode: "force-hdr" } (if such a field exists in EngineConfig) gets it merged into cfg at plan.ts:616-620. The validator doesn't see the merged value. Trace through whether EngineConfig carries hdrMode — if it does, the same defense as above: validate against the resolved/merged config, not just the caller's direct config.hdrMode.

Trivial to check; if EngineConfig doesn't carry hdrMode this is a non-issue. Worth a quick grep to confirm.

nit — the error message could be more discoverable about which format is supported (plan.ts:33-37)

super(
  `[plan] format ${JSON.stringify(format)} is not supported in distributed mode: ${reason}. ` +
    `Render with the in-process renderer (\`executeRenderJob\`) — it has full format ` +
    `support — or pick a distributed-supported format: mp4 SDR, mov ProRes 4444, or ` +
    `png-sequence.`,
);

Good as is. The supported list at the end is exactly what an adopter hitting this error wants. Flagging only because the analogous message in PlanTooLargeError (#814) names the size cap AND the v1.5 roadmap link — same shape would let this error message point at the v1.5 issue where webm/HDR support is planned, so adopters can subscribe instead of working around in their own code.

nit — test could assert the reason field (planFormatBanlist.test.ts:170-219)

The tests assert code, format, and the message contains certain substrings — but never assert reason is set on the error instance. The reason field carries the "why was this banned" diagnostic that adapters might surface to users; pin it with a test so a future refactor doesn't accidentally drop it.

Verdict: APPROVE
Reasoning: Clean, focused, replaces a smelly cast with a proper typed error class. The Rule-2 finding (chunk worker / assemble don't re-validate) is a real gap but the architecture currently funnels all entries through plan(), so it's an in-spec gap worth fixing in a follow-up — not a blocker.

— Vai

@jrusso1020 jrusso1020 force-pushed the 05-13-feat_producer_enforce_plandir_size_cap_with_plan_too_large branch from 5f0239a to 819a06c Compare May 14, 2026 00:14
@jrusso1020 jrusso1020 force-pushed the 05-13-feat_producer_refuse_distributed-unsupported_formats_early branch from 60e67b7 to 72407de Compare May 14, 2026 00:14
vanceingalls
vanceingalls previously approved these changes May 14, 2026
Copy link
Copy Markdown
Collaborator

@vanceingalls vanceingalls left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review

PR rebased onto the new bases. rejectUnsupportedDistributedFormat is called at the top of plan() (plan.ts:100), exported via #816, and tested for both webm and force-hdr rejection paths. The (config.format as string) cast at the runtime branch handles the JS-caller-passes-webm-anyway case — belt-and-suspenders against the TS type narrowing.

Net-new findings

None gating. Single call site is acceptable here because (a) the gate is exported so adapters can re-call it at their own input layer (per the module doc), and (b) format/hdrMode types are narrowed at the public surface so TS callers can't get past compilation.

Verdict: APPROVE
Reasoning: Rebase only; content matches prior approval. Single-site enforcement is fine given the type narrowing + adapter-side gate availability. CI required checks green.

— Vai

@jrusso1020 jrusso1020 force-pushed the 05-13-feat_producer_enforce_plandir_size_cap_with_plan_too_large branch from 819a06c to dbbd329 Compare May 14, 2026 00:48
@jrusso1020 jrusso1020 force-pushed the 05-13-feat_producer_refuse_distributed-unsupported_formats_early branch 2 times, most recently from 81975cc to 5c789ef Compare May 14, 2026 01:13
@jrusso1020 jrusso1020 force-pushed the 05-13-feat_producer_enforce_plandir_size_cap_with_plan_too_large branch 2 times, most recently from 6aca0b4 to 8590f07 Compare May 14, 2026 02:10
@jrusso1020 jrusso1020 force-pushed the 05-13-feat_producer_refuse_distributed-unsupported_formats_early branch from 5c789ef to 72c6cb5 Compare May 14, 2026 02:11
@jrusso1020 jrusso1020 force-pushed the 05-13-feat_producer_enforce_plandir_size_cap_with_plan_too_large branch 2 times, most recently from e813fcb to fbbd41a Compare May 14, 2026 02:39
@jrusso1020 jrusso1020 force-pushed the 05-13-feat_producer_refuse_distributed-unsupported_formats_early branch from 72c6cb5 to a1ab804 Compare May 14, 2026 02:39
@jrusso1020 jrusso1020 changed the base branch from 05-13-feat_producer_enforce_plandir_size_cap_with_plan_too_large to graphite-base/815 May 14, 2026 03:08
@jrusso1020 jrusso1020 force-pushed the 05-13-feat_producer_refuse_distributed-unsupported_formats_early branch from a1ab804 to 552027b Compare May 14, 2026 03:08
@graphite-app graphite-app Bot changed the base branch from graphite-base/815 to main May 14, 2026 03:09
@graphite-app graphite-app Bot dismissed stale reviews from vanceingalls and miguel-heygen May 14, 2026 03:09

The base branch was changed.

@jrusso1020 jrusso1020 force-pushed the 05-13-feat_producer_refuse_distributed-unsupported_formats_early branch from 552027b to e506848 Compare May 14, 2026 03:09
Copy link
Copy Markdown
Collaborator

@miguel-heygen miguel-heygen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-stamping after rebase. LGTM.

@jrusso1020 jrusso1020 merged commit b47a3e7 into main May 14, 2026
39 checks passed
Copy link
Copy Markdown
Collaborator Author

Merge activity

@jrusso1020 jrusso1020 deleted the 05-13-feat_producer_refuse_distributed-unsupported_formats_early branch May 14, 2026 03:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants