fix(producer): assert render artifact duration and frame count before commit - #3429
Open
santhiprakash wants to merge 2 commits into
Open
fix(producer): assert render artifact duration and frame count before commit#3429santhiprakash wants to merge 2 commits into
santhiprakash wants to merge 2 commits into
Conversation
… commit Refuse to publish an artifact that is significantly shorter or has fewer frames than the capture pipeline just reported. Adds a duration/frame-count gate on top of the existing readable-non-empty check inside ArtifactTransaction.validate(), keyed off the values the orchestrator already carries. Closes heygen-com#3395.
The frame-count gate added in heygen-com#3395 accepts an expectedFrames value from the orchestrator, but defaultArtifactDurationProbe was still returning only durationSeconds - so the wire was half-built and the assertion short-circuited on undefined for every real render. Forward meta.frames from ffprobe so the field-packet case the issue names (container duration correct, stream shorter) is actually caught by the frame-count check, not just the duration one. extractMediaMetadata now populates a new frames field from the video stream's nb_frames tag, returning undefined when the demuxer did not report one (fragmented MP4, malformed streams, muxes that require -count_packets). Callers that gate on the count must treat undefined as no answer; the assertion already does. The previous CI run (#32589981916) cancelled shard-6 at the 1h job timeout after bun install failed to extract the aws-cdk-lib tarball mid-Docker-build - a cache flake, not a code regression. Pushing a follow-up commit retriggers CI against the now-populated cache layer; the regression should clear without further code changes.
santhiprakash
force-pushed
the
fix/3395-artifact-validation-frame-count
branch
from
August 23, 2026 00:47
83171fe to
f15951e
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #3395
What
ArtifactTransaction.validate()now refuses to publish a render output that is significantly shorter or has fewer frames than the capture pipeline just reported. The readable-non-empty check is unchanged; this adds a second gate on top, fed byffprobeon the staged file and the values the orchestrator already holds.Why
The multi-worker encode path can drop ~half the frames between capture and mux. Today the only validation is "the file exists and is non-empty", so a 148-of-300 render ships as
Render completeand reaches the viewer. The information needed to catch it was already in hand at validation time — the captured duration and frame count live on the job — but no comparison was ever made.The maintainer's field report (#3395, second comment) gives both halves of the bug surface: a media-free
--workers 4run lands at 300/300 becausecaptureMode: "beginframe"keeps it off the parallel-disk path, while the trigger composition does go through it and ends up at 148/300 / 4.933s. Either a duration or a frame-count check against expected values catches the truncated case regardless of cause, so we add both.How
ArtifactTransaction.validate()now accepts an optionalexpected: ArtifactValidationExpectation. The existing readable-non-empty check still runs first; whenexpectedis supplied,validate()also calls a pluggabledurationProbe(default: producer'sextractMediaMetadatawrapper) and compares:expectedDurationSeconds - probed.durationSeconds > tolerance(default tolerance is one frame at the job fps, or 20 ms when fps is unknown). Both field packets from the issue (52.2s → 41.333s and 10.0s → 4.933s) are well past this threshold; the--workers 1workaround's exact-10.0s / 300-frame run is within it.expectedFramesand the probe returns aframescount,expectedFrames - probed.frames > 1rejects. This is the case the issue's second paragraph names as the "container reports correct duration but stream is shorter" failure mode — currently dormant in the default probe (which returns only duration), but the wire is in place for a richer probe without a follow-up.Probe errors are surfaced as validation errors rather than silently passing the gate, and a probe returning 0/NaN is rejected outright — both explicitly to keep this from regressing back into "validation passes when it can't answer".
renderOrchestrator.tsnow passesexpectedonly for non-PNG-sequence, non-GIF video outputs with a finite positive captured duration, so PNG/GIF validation keeps its existing contract.The validation still runs inside the staging directory, before the atomic rename to
destinationPath— a hard failure never touches the destination and costs only a retry.Test plan
bun test ./src/services/render/artifactTransaction.test.tsfrompackages/producer/— 16/16 pass (9 pre-existing + 7 new). The new cases cover: probe is not called when no expectation is set, probe is not called for directory artifacts, truncation rejection at the field-packet scale (52.2s expected / 41.333s probed), within-tolerance acceptance of the--workers 1workaround (10.000s / 300 frames / 30 fps), frame-count shortfall rejection (300 expected / 148 probed), probe failure propagation, and a probe returning zero duration being treated as "cannot verify".bun run typecheckfrompackages/producer/— clean.bunx oxfmt --checkandbunx oxlinton the three touched files — clean.packages/producer/src/services/renderOrchestrator.ts:3937to confirm the captured duration / fps / frameCount path is correct, and that PNG-sequence and GIF outputs skip the new check.Files touched:
packages/producer/src/services/render/artifactTransaction.tspackages/producer/src/services/render/artifactTransaction.test.tspackages/producer/src/services/renderOrchestrator.ts