Co-Authored-By: Claude <noreply@anthropic.com>
Co-authored-by: Joel Gallardo <joel.gallardo@heygen.com>
Co-authored-by: heygengenesis[bot] <262951085+heygengenesis[bot]@users.noreply.github.com>
* fix(render): align container Chrome with the CLI
* chore: prepare conflict resolution against current main
* fix(render): retain Chrome visuals and current audio baselines
* fix(gcp-cloud-run): the BeginFrame contract image resolves the engine's core import
The engine config imports the distributed fps set from @hyperframes/core, and the contract stage of the GCP image copies only parsers and engine sources, so the import failed at build time. The set now lives in a small core subpath (@hyperframes/core/distributed-fps) that the engine imports like its other light core imports, and the contract stage copies core like parsers. The core root export is unchanged.
* style(core): format package.json
* fix(build): ship NodeNext-resolvable declarations from every package
tsc copies relative specifiers verbatim, so packages whose sources use
bundler-style extensionless imports (core, inspector) published .d.ts files
that a module/moduleResolution nodenext consumer cannot resolve. Core already
rewrote its emitted JavaScript for this, but not its declarations.
Move that rewrite to scripts/rewrite-relative-import-extensions.mjs, make it
cover .d.ts too (adding .js or /index.js, and failing the build when a
declaration import has no declaration), and run it after every tsc emit:
core, engine, sdk, producer, aws-lambda, gcp-cloud-run and inspector.
verify:packed-manifests now typechecks the clean packed consumer with
skipLibCheck false and type-only imports of every typed subpath, so an
unresolvable import inside a published declaration fails the gate and is
attributed to its package.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* chore(ci): allow the moved core rewrite-esm-extensions script deletion
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* ci: rebuild the edit-accuracy bench when the import-extension rewriter changes
The package builds now run scripts/rewrite-relative-import-extensions.mjs, so main's
edit_accuracy paths filter has to include it (check-edit-accuracy-filter test).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* chore(docker): pin FFmpeg 8.0.1 in the Docker images and host CI
Install one pinned static BtbN build (n8.0.1-66, month-end release) in
Dockerfile.test, the CLI render image, the Cloud Run image and the k8s
example, and bump the host CI install-ffmpeg-linux action from 7.1.3 to
the same build. Regression baselines and the shipped images now use the
same FFmpeg version instead of Debian's apt ffmpeg 5.1.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* test(producer): regenerate six regression baselines on FFmpeg 8.0.1
The Docker test image now renders with FFmpeg 8.0.1 instead of Debian's
5.1. Six fixtures fail against the 5.1 baselines; everything else passes
unchanged, including vfr-screen-recording.
- style-2-prod, style-10-prod: FFmpeg 8 honors the MP3 gapless header,
so the probed SFX duration is 1.0s instead of 1.044898s. Regenerate
output.mp4 and compiled.html.
- vignelli-stacking: one checkpoint (8.06s) now shows source frame 201
of the 25fps a-roll, which is the right frame for that time; the old
baseline showed frame 202.
- missing-host-comp-id, variables-prod, speed-ramp: AAC encoder output
differs enough to drop the RMS-envelope correlation on flat signals
below 0.9. Sample correlation against the source stays at 0.9999.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(cli): read the ffmpeg version from n-prefixed release builds
Release-branch ffmpeg builds, including the BtbN build now pinned in CI,
print "ffmpeg version n8.0.1-66-...". extractMajorVersion skipped the
"n8" token and returned 0 (and 1 for n7.1.3), so the CLI telemetry
smoke check failed. parseToolVersion fell back to the whole banner line.
Accept an optional "n" before the version number in both.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(cli): keep the preflight comment share flat
Fold the n-prefix note into the existing doc line instead of adding new
comment lines, which the comment ratchet rejects.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(docker): pick the FFmpeg build per arch and fail on a bad binary
Dockerfile.test, the k8s example and the Cloud Run image always extracted the
amd64 archive on multiarch bases, and the piped version check passed even when
the binary could not run. All four images now pick linux64 or linuxarm64 by
TARGETARCH, fail on any other arch, and grep both ffmpeg and ffprobe for the
pinned version.
style-2-prod and style-10-prod compiled.html go back to the previous golden
with only the SFX data-duration/data-end changed, instead of a full
recompile that also picked up unrelated compiler output.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(cli): snapshot, check, layout and thumbnails honour software WebGPU like render
* test(cli): stub the local WebGPU helper in the studio server tests
* fix(engine): webgpu compositions are recognised after the bundled runtime script
* fix(engine): a hung GPU probe falls back to software in 15 s instead of 120 s
* fix(engine): the GPU probe keeps the configured launch timeout and bounds only its page
* build(parsers): declare the html-attribute-spans subpath in its descriptor
* build(gcp-cloud-run): the BeginFrame contract stage copies parsers, which the engine now imports
* test(cli): the screenshot launch pin follows the shared local WebGPU helper
* fix(engine): the GPU probe's wait for Chrome's first page falls inside its 15 s limit
* feat(core): the renderer's font pipeline is importable from @hyperframes/core/fonts/embed
* style(core): format the font pipeline and state the bundled-subset invariant
* test(producer): repin the test lane map after the font tests moved to core
* fix(core): images build fonts once, CLI bundles one pipeline, font tests pass on Windows
* test(core): font retry tests get a budget no slow machine can exhaust
* test(core): the Retry-After budget test fails when the budget is ignored
* fix(gcp-cloud-run): run the Cloud Run image on Bun 1.4.2 so remote downloads cannot hang
* fix(engine): make the download deadline abort the body pipeline
Same shape as fix/regression-docker-build-timeout: bun install can print a tarball error and hang instead of exiting (run 35468046055).
This Dockerfile ships a real prod deploy path, so a reviewer should look twice.
* fix(capture): never silently render a webgpu composition blank
A composition declaring data-requires-webgpu only got the Chrome WebGPU
flag when hardware GPU auto-detection succeeded. On a software-GPU host
it launched without the flag and rendered blank with no error.
compositionRequiresWebGpu (static declaration check) and
assertWebGpuAdapterAvailable (live per-page adapter probe) now live once
in packages/engine's browserManager, the shared choke point every launch
site already goes through. All six launch sites (frame capture, the four
CLI diagnostic commands, the studio thumbnail server, and the render
pipeline's capture-options builder) route through those two functions;
the old CLI-only static-heuristic guard is deleted, not kept alongside
the new one. A software host that cannot obtain any WebGPU adapter now
fails loudly naming the requirement instead of producing a blank frame.
* fix(capture): thread requiresWebGpu through the distributed chunk-render path
The distributed renderChunk() path built its CaptureOptions without
requiresWebGpu, so it fell through to frameCapture's own fetch-based
detection, whose failure path silently treats "requires webgpu" as
"doesn't" if that fetch ever fails. Every other launch site (frame
capture, all four CLI diagnostic commands, the studio thumbnail server,
and the in-process render pipeline) already reads the value from HTML
already in memory or on disk instead of relying on that fallback.
Reads the compiled composition's index.html the same way probeStage
already does and derives requiresWebGpu from it before building
CaptureOptions. Adds a regression test that pins the exact path
renderChunk() reads at render time, since the composition doesn't
actually exercise WebGPU so an end-to-end render can't distinguish the
two cases on this host.
* test(producer): observe the real CaptureOptions renderChunk() builds
The previous regression test called plan() and compositionRequiresWebGpu()
directly, bypassing renderChunk() entirely, so it could not detect a
regression to the wiring it was meant to protect (confirmed: it stayed
green with the fix reverted).
Exports the existing createCaptureSession dependency seam so a test can
substitute a spy that delegates to the real implementation while
capturing the CaptureOptions object renderChunk() actually constructs.
Verified non-vacuous by reverting the fix and confirming the test fails.
* fix(gcp-cloud-run): resolve puppeteer-core from a real file in the webgpu probe
The image build's WebGPU adapter probe imported puppeteer-core from an
inline `bun -e` eval string. The eval's synthetic /app/[eval] location
can't resolve the workspace-hoisted dependency, so every build of this
stage failed closed with "Cannot find package 'puppeteer-core'" before
the probe itself ever ran.
Moves the probe into a real script file next to probe-beginframe.ts,
which already imports puppeteer-core dynamically the same way and
resolves fine from its real workspace location. Verified locally against
a cached chrome-headless-shell binary.
* fix(engine): bound the webgpu adapter probe so a stuck driver can't hang the caller
requestAdapter() has no native timeout. A broken software GPU driver
that never settles it would hang assertWebGpuAdapterAvailable
indefinitely, including inside the GCP image build's probe RUN step,
producing an opaque build timeout instead of a clean probe failure.
Races the in-page request against a bounded timeout so a stuck adapter
reads as unavailable and fails the same way a genuinely absent one does.
* test(cli): mock the engine's webgpu exports in the browser-fallback test
* test(producer): run the typegpu adapter fixture only on hosts with a hardware WebGPU adapter
The fixture declares data-requires-webgpu, so a software-only host now refuses it
instead of silently rendering the shader blank. The old golden had no shader in it.
Tag it webgpu, skip that tag in the CI image and the test scripts, and re-record the
golden from a Metal render with the shader visible.
* fix(engine): bound the composition fetch that decides the webgpu flag
createCaptureSession fetched the served page with no timeout before launching the
browser, so a hung server stalled a render forever and hung a fake-timer test. Give it a
five second limit, and let that test say it has no WebGPU requirement the way the
producer does.
* fix(capture): refuse a software fallback WebGPU adapter instead of rendering on it
swiftshader reports a WebGPU adapter but cannot render canvas WebGPU, so a
data-requires-webgpu composition on a software-only host crashed on its first frame.
Refuse fallback adapters with the same clear WebGPU-unavailable error. The GCP image
probe now serves its page over http (file:// hides navigator.gpu) and reports a
software-only host instead of failing the image build.
* fix(engine): parse the composition root tag without a backtracking regex
* fix(engine): keep quoted attribute values whole when reading the composition root tag
* feat(gcp-cloud-run): add Google Cloud Run + Workflows distributed render adapter
Adds @hyperframes/gcp-cloud-run, the GCP counterpart to @hyperframes/aws-lambda
(issue #932). The OSS distributed primitives (plan, renderChunk x N, assemble)
are unchanged; this package is the storage/compute/orchestration glue.
Package: Cloud Run handler (one image, three actions), runs under bun; GCS
transport; in-image chrome-headless-shell resolver; client SDK
(renderToCloudRun, getRenderProgress, deploySite, computeRenderCost); Dockerfile;
Cloud Workflows definition; Terraform module; CLI cloudrun
deploy|sites|render|render-batch|progress|destroy with --output-resolution and
--strict-variables; 62 unit tests + docs + live smoke script.
Shared extraction (removes ~640 lines of adapter duplication): move the
cloud-agnostic config validator + content-hash into producer/distributed; both
adapters import them. Validated end-to-end on GCP at 37.4 dB PSNR vs baseline.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(cli): resolve @hyperframes/gcp-cloud-run in the CLI build + root build
The CLI bundle (esbuild) couldn't resolve `@hyperframes/gcp-cloud-run/sdk`,
failing Build/Typecheck/CLI-smoke (and the perf/windows/regression jobs that
build first). Mirror the aws-lambda handling: mark the gcp adapter + its /sdk
subpath external in tsup.config.ts with a source alias, and add gcp-cloud-run
to the root `build` filter so its dist exists for publish + runtime.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(ci): copy gcp-cloud-run manifest in Dockerfile.test for frozen install
The regression test image runs `bun install --frozen-lockfile` after copying
each workspace package.json individually. The CLI now depends on
@hyperframes/gcp-cloud-run (workspace:*), so the frozen install fails to
resolve it unless its manifest is present. Add the COPY line.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(cli): add machine-sizing flags to `cloudrun deploy`
Closes the parity gap with `lambda deploy` (which exposes --memory etc.).
`cloudrun deploy` now threads --cpu, --memory, --max-instances, and --timeout
into the Terraform apply; omitted flags keep the module defaults
(4 vCPU / 16Gi / 100 instances / 3600s). For finer control, apply the module
directly.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(gcp-cloud-run): address PR review (security, waste, limits, alerts)
- server.ts: bucket-allowlist guard no longer fails open silently. Unset env
logs a one-time WARNING; "*" is an explicit opt-out; otherwise it enforces.
- server.ts: stop double-shipping audio.aac. It already rides in the plan
tarball every consumer downloads, so drop the redundant standalone upload
(plan) + re-download/overwrite (assemble); assemble reads it from the untar,
falling back to a supplied AudioGcsUri for compat.
- server.ts: chunk extension via path.extname() instead of slice(lastIndexOf).
- workflow.yaml: clamp parallel concurrency_limit to math.min(chunkCount, 20)
— Cloud Workflows hard-caps concurrent iterations at 20.
- Dockerfile: pin bun (bun-v1.3.9) so an interop change can't silently break
the image rebuild.
- terraform: add min_instances var (default 0); add a workflow-failure alert
(finished_execution_count status=FAILED) alongside the request-count one.
- costAccounting: document that displayCost excludes GCS storage/egress.
Verified against the actual APIs: @google-cloud/workflows@4.4.0
ICreateExecutionRequest has no executionId (so the idempotency-token suggestion
isn't available in this client); Workflows concurrency cap is 20; failure
metric is workflows.googleapis.com/finished_execution_count (status label).
174 adapter tests pass, fallow/oxlint/oxfmt/terraform clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(gcp-cloud-run): address round-2 review — error code + CFR forwarding
- workflow.yaml: rename the zero-chunk failure code PLAN_TOO_LARGE →
PLAN_PRODUCED_ZERO_CHUNKS. The old code implied a size-ceiling breach (the
opposite cause), misleading anyone triaging the alert.
- workflow.yaml: forward Config.cfr to the assemble step
(`Cfr: ${("cfr" in config) and config.cfr}`). It was read by the handler
but never sent, so exact-CFR was silently off for every Cloud Run render.
Uses the same `in`-operator guard already proven in the retryable predicate.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(release): include gcp-cloud-run in set-version PACKAGES list
set-version.ts (driven by release:prepare) bumps an explicit package list to
the shared version on each release. gcp-cloud-run was wired into the build +
publish.yml but missing here, so a release would leave it at a stale version
and publish.yml would push the wrong version. Add it so the new package
version-bumps + publishes in lockstep with the others.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>