Repository navigation
perf(cloud): move project bytes once through shared content storage - #5772
Conversation
A project video under compiled/ that chunks extract their own frames from now goes only to the chunks that show it, like video-sources/ files. A chunk skips the readiness wait for a deferred video it does not show, so it no longer logs media_load_failed for a file it was never given.
Edit accuracy: accurate 271 (base branch 271), smooth 172 of thoseThe gate passes. Quarantined, measured but not gated (0) Unstable (1)
|
A chunk that extracts its own frames from a deferred project video now reads it from S3 by byte range through a short-lived pre-signed URL, instead of downloading and hashing the whole object first. S3 attests the object's stored SHA-256 before the URL is signed; every other artifact is still downloaded and verified as before.
…er surface their signed query
…d of downloading them
…covers wrapped causes Signed-URL reads now pass -rw_timeout, -reconnect and -reconnect_delay_max (both Lambda builds accept them) and ffmpeg also -reconnect_on_network_error, so a transient S3 error or a stalled read fails fast or recovers instead of waiting out the process timeout. Query redaction now descends into error.cause.
jrusso1020
left a comment
There was a problem hiding this comment.
Requesting changes at aa33a0e3. I read the full diff against main (merge base deeb8d43), with a focus on backward compatibility, because this changes the stored site format that adopters' existing stacks and site ids depend on. A live run on Lambda and Cloud Run at this head is in progress on our side. I'll follow up with its results; nothing below depends on it.
Blockers
1. A new SDK or CLI against a stack deployed before this PR fails every render in the plan step.
deploySitenow writessites/<id>/files.jsonand returns it asprojectS3Uri, andrenderToLambdapasses that straight through asProjectS3Uri.- On main, the plan handler downloads
ProjectS3Uritoproject.tar.gzand untars it (handler.tsatdeeb8d43, lines 427-434, and the v1 path at 360-366). So an older stack gets a JSON file it can't untar. - Nothing checks the stack's version, and the fallback the README documents for exactly this situation (
packages/aws-lambda/README.md:79-83, "passplanProtocol: "v1"explicitly until the redeploy is complete") no longer works. The site format itself changed, so v1 breaks the same way. - What's needed: either keep a path that works against an older stack (detect that the stack predates
files.jsonsupport and upload a tarball site, as before), or at least fail before submitting with a clear "redeploy the stack first" error. Either way, update the README's upgrade section so it no longer offers a fallback that doesn't work.
2. --site-id and siteHandleFromId can't find sites uploaded before this PR.
siteHandleFromIdalways buildss3://<bucket>/sites/<id>/files.json.- A site created by
lambda sites createon any earlier version has onlysites/<id>/project.tar.gz. SoensureSiteStoredthrows NoSuchKey on the manifest download before anything is submitted, and the handler would do the same. - The docs tell adopters to keep and reuse site ids:
docs/deploy/aws-lambda.mdx:90-94,docs/packages/cli.mdx:1939,1985,1999,docs/deploy/templates-on-lambda.mdx:86, andrender-batch. - What's needed: fall back to
project.tar.gzwhenfiles.jsonis missing (the handler already accepts tarballs), or give a clear "site was created by an older version; runsites createagain" error. Add a test that uses an old-format site.
3. The docs are out of date for this change.
docs/deploy/aws-lambda.mdx:94still describes the tarballHeadObjectshort-circuit.- Nothing documents:
- the shared
cas/sha256/prefix and its 30-day lifecycle rule; SITE_FILES_MISSING;- the one-time site-id change, which means every existing project uploads again on its first render after the upgrade.
- the shared
- Users upgrading need all of that in the deploy docs and the release notes.
Interaction with #5813
Both PRs branch from the same deeb8d43 and overlap in 9 files. git merge-tree against #5813 at 98183d65 conflicts only in HyperframesRenderStack.snapshot.test.ts. build.mjs merges cleanly, keeping both @aws-sdk/client-lambda and @aws-sdk/s3-request-presigner external, with splitting: true.
However, this PR lowers the aws-lambda package budget to 56,203 packed / 193,016 unpacked bytes, while #5813 was over the old budget. Whichever PR lands second has to re-measure against the new budget and rerun the upgrade path live. The two PRs are never tested together on CI, because each one runs against main.
Checked and fine
- Old SDK or CLI against a new stack: tarball
ProjectS3Urivalues still work (isSiteManifestUriis false, so the handler untars as before). - Caller IAM:
deploySitenow uses Head, Put and in-place Copy calls, which are covered bys3:GetObject/s3:PutObjectinpolicies.ts:142. The function role's bucket read/write covers presigning and copying. No new permissions are needed. files.jsonpath checks reject..,., empty segments and backslashes, and every downloaded file is verified against its SHA-256.- The in-place refresh uses
MetadataDirective: REPLACE, which S3 requires for a copy onto itself. urlInputArgs(TLS verification, which throws when no CA bundle is found) is only reached forhttpsinputs. User-authoredhttpssources are still downloaded first inhtmlCompiler,extractAllVideoFramesandaudioMixer, so local renders, including on Windows where none of the three bundle paths exist, never call ffmpeg with an https input. Only Lambda's presigned remote files do.- All 12 commits are signed and verified.
Build,Typecheck,Fallow auditandTest reachabilitypass. Other checks are still running.
Evidence still needed
As disclosed, there is no live run at this head, and gcp-cloud-run/src/sdk/deploySite.ts changed, which also changes Cloud Run site ids. Before I can approve, I need a live Lambda run of the upgrade path (main stack → this head, including a pre-existing site id) and a Cloud Run deploy and render at the head being approved.
— Rames
jrusso1020
left a comment
There was a problem hiding this comment.
Live results at aa33a0e3, run by our hyperframes session in HeyGen's engineering AWS account (us-east-1) and GCP project hyperframes-dev (us-central1). Everything has been torn down. My change request stands: blockers 1 and 2 are now confirmed live.
Lambda
| # | Check | Result |
|---|---|---|
| 1 | Stack deployed from main's CLI, then rendered with this PR's CLI without redeploying | FAIL, as predicted. No warning. The CLI uploaded sites/6f8cc4933c10562f/files.json. PlanV2 threw TAR_BAD_ARCHIVE: Unrecognized archive format and was retried 4 more times by States.ALL before the execution failed (hf5772-skew-render). |
| 2 | sites create from main (tarball site 68b647887ab30f45), stack upgraded to this head, then render --site-id 68b647887ab30f45 |
FAIL, as predicted. Exit 1 with only The specified key does not exist., raised client-side in ensureSiteStored before StartExecution. The old project.tar.gz is still in the bucket. The error doesn't name the key or suggest running sites create again. |
| 3 | Upgrade from main to this head, using the role from lambda policies role |
PASS. The CloudFormation update succeeded, and the bucket now has ExpireSharedContent (cas/sha256/, 30 days) next to ExpireIntermediates. Reserved concurrency stayed 8. Running sites create twice gave uploaded:true then uploaded:false. Two sites sharing one clip left exactly one object under cas/sha256/. The render succeeded: 900 frames, 30.000 s. |
| 4 | Chunk range reads (6 chunks of a 40 s 720p H.264 clip) | PASS. Output is byte-identical to main's render of the same fixture. Per-render S3 dropped from 15 objects / 22.2 MB to 8 / 7.2 MB, with no per-render copy of the clip. CloudWatch has 0 matches for X-Amz-Signature, X-Amz-Credential or Amz-Security-Token. |
| 5 | examples/aws-lambda/scripts/smoke.sh |
PASS, exit 0. v1/v2 at N=2 and N=4 scored 37.11-37.12 dB, and the semantic comparison passes. |
| 6 | Merged with #5813 (98183d65) |
Only the CDK snapshot test conflicts. The aws-lambda tests pass, 134 of 134. Packed size: this PR alone is 53,138 / 183,824 B; merged it's 54,862 / 190,114 B, inside this PR's budget. The merged tree rendered on the upgraded stack: the Map ran 60 of 60 children, peak concurrency was 58, and PSNR against main was 48.04 dB. |
Nit from item 4: nothing logs that a chunk took the URL path. It's shown by the artifact layout and the identical output, not by a log line. One log line per remote source, without the query string, would make it observable.
Cloud Run
examples/gcp-cloud-run/scripts/smoke.sh --protocols v1,v2: PASS. Both protocols succeeded at 37.11 dB, and the v1/v2 comparison passes.- Old site on this PR: main's SDK
deploySitecreated tarball site68b647887ab30f45. Rendering it with this PR'srenderToCloudRun({siteHandle})succeeded, 900 of 900 frames. Cloud Run sites are still tarballs, so old sites keep working there. - Running this PR's
deploySiteon the same project gave the new id6f8cc4933c10562fand uploaded once. A render fromprojectDirsucceeded. Old and new sites rendered at 49.9 dB PSNR against each other. - Not run: an old Cloud Run service with this PR's SDK. The payload is the same gzip tarball, so it should work.
- Separate from this PR: the GCP
smoke.shon main fails to build its image withunsupported TARGETARCH ''(Dockerfile:67, from #5576), becausegcr.io/cloud-builders/dockerdoesn't setTARGETARCH. We built the images with BuildKit and passed them with--image.
What's needed to approve
- Fix blockers 1 and 2 from my review: keep a path that works against an older stack, or fail before submitting with a clear "redeploy first" error; and fall back to
project.tar.gzfor old site ids, or name the fix in the error. - Update the docs (blocker 3).
- Rerun items 1 and 2 at the new head.
— Rames
A site id from an earlier sites create has only sites/<id>/project.tar.gz. renderToLambda now falls back to that tarball, which the plan step still reads, and when neither key exists it fails before starting with both keys named and the fix (run sites create again).
…d sites A stack deployed before files.json sites untars the site file in its plan step and fails after four retries. renderToLambda now reads the state machine definition first and stops with STACK_TOO_OLD and the redeploy command when the plan task does not list SITE_MANIFEST_INVALID, which new SAM and CDK stacks now treat as terminal. The README's upgrade section drops the planProtocol v1 fallback, which hits the same failure.
The deploy guide still described the tarball HeadObject short-circuit. It now covers the cas/sha256/ store and its 30-day lifecycle rule, the STACK_TOO_OLD, SITE_FILES_MISSING and SITE_NOT_FOUND errors, that old site ids keep working, and that every project uploads once more after the upgrade. A test keeps a troubleshooting entry for each pre-submit error.
A chunk reading a video straight from the bucket left no trace in its log. It now logs one line per remote source with the path and the URL minus its query string, so signatures never reach the log.
aa33a0e to
2991d68
Compare
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Automations to automatically generate PRs for you. |
A process's stderr tail was trimmed before its URLs were redacted, so a cut inside a signed URL left a query fragment the URL pattern no longer matched, and the fragment reached the error. The stderr tail is now redacted before every trim, and the redactor also strips a signing parameter cut off from its URL.
|
@jrusso1020 thanks for the review and the live table. All three blockers and the nit are fixed at 1. New SDK/CLI against an older stack ( 2. 3. Docs ( Nit: URL reads are now observable ( Also fixed (CI at the previous head): an ffmpeg error kept Package size: after the update aws-lambda packs to 53,834 / 187,116 B, within the 56,203 / 193,016 budget. #5813 still has to re-measure if it lands second. Cloud Run: unchanged, since your run showed old Cloud Run sites still render. I could not run a Cloud Run deploy and render at this head: there is no gcloud access to the GCP project from my environment. That run still needs someone with access, as you did last time. |
… spawn uses it The stderr tail was redacted on each chunk and then cut, so once a cut severed a long URL's scheme, the rest of its signature in the next chunk matched neither pattern. The tail is now kept raw with 2x slack and redacted only when the text is produced, then cut to size. The unused producer audioExtractor (its own spawn, unredacted stderr) is deleted and the parity harness's PCM decode goes through ManagedChildProcess; a test fails on any ffmpeg/ffprobe spawn in engine, producer or aws-lambda that does not.
The raw tail is kept at twice the cap, but that cut can itself land inside a signature, and when redaction shrinks the rest below the cap the fragment survived. After a cut, the severed token is dropped before redaction.
|
@jrusso1020 two more changes since my last reply, plus main merged in. Main merged in ( Stderr redaction, chunk splits ( A fragment the slack cut could leave ( One owner for ffmpeg/ffprobe stderr. Head Your items 1 and 2 were rerun live at |
somanshreddy
left a comment
There was a problem hiding this comment.
Review at 4b26e89d. COMMENT, not APPROVE: I found no blocker, but Rames' CHANGES_REQUESTED (5561068381) is still open, and this changes public SDK/CLI behavior for existing callers (listed below), which needs the human-of-record's sign-off.
Rames' blockers: fixed
- B1, old stack: fixed.
renderToLambda.ts:96-97,163-173callsDescribeStateMachinebefore any upload. A definition withoutSITE_MANIFEST_INVALIDstops withSTACK_TOO_OLDand names the redeploy command. Main's CDK construct and SAM template never contain that string; the head adds it to Plan and PlanV2 and pins it with a snapshot test. My probes with an old definition, a missing definition, and an old stack plus an old tarball site all stopped withSTACK_TOO_OLD, making 0 S3 calls and 0 executions. README's v1 fallback is replaced with "redeploy first". - B2, old site ids: fixed.
deploySite.ts:94-99,126-135falls back toproject.tar.gzwhenfiles.jsonreturns 404. If neither exists, it stops before starting withSITE_NOT_FOUND, and the message names both keys andsites create. There's an old-format test. - B3, docs: fixed in substance (cas prefix, 30-day rule, upgrade order, new id, IAM, troubleshooting). Leftovers:
aws-lambda.mdx:37,129still say "plan tarball(s)".- The release note "stop with STACK_TOO_OLD before uploading" isn't true for
render-batch, which runsdeploySite(render-batch.ts:235) beforerenderToLambda.
- Nit, remote-source log line: fixed (
renderChunk.ts, query string stripped, test added).
Should-fix
render --site-idwith expired cas files gives raw errors instead ofSITE_FILES_MISSING(deploySite.ts:102-110,render.ts:138). The CLI always passesprojectDir, so themissinglist is unreachable from the CLI.- Run from a different directory, the error is
[s3Transport] upload source missing: …/index.html. - If the local file was edited since, the PUT carries the old
ChecksumSHA256. Real S3 would answer with a rawBadDigest(inferred; my fake doesn't check digests). - Integrity holds either way, but the docs promise a re-upload. Map both cases to
SITE_FILES_MISSINGand name the directory mismatch.
- Run from a different directory, the error is
- Per-render overhead: every
renderToLambdanow makes aDescribeStateMachinecall, and asiteHandlerender adds afiles.jsonGET plus a HEAD per file (probe: 5 renders made 5 Describe calls).render-batchdefaults to 50 concurrent entries, and DescribeStateMachine's quota is far below StartExecution's, so large batches could throttle intofailed-to-start(inferred, not measured). Memoize the stack check per ARN and check site storage once per batch. - Remote (in-place) videos skip the STUDIO-5433 text-payload sniff.
assetMediaType.ts:129skips paths absent locally, andhtmlCompiler'sassertMediaPayload(URL)throws, which counts as "not a document". So an.mp4that is really HTML surfaces as a retryable, system-owned ffprobe error instead of user-ownedNOT_MEDIA_PAYLOAD. Found by reading the code, not run.
Nits
files.jsonreturning 403 (caller lackss3:ListBucket) passes through rawAccessDeniedwith no tarball fallback. Verified with an injected 403. Worth a doc line.urlInput.ts:40redaction: a two-errorcausecycle overflows the stack (RangeError replaces the original error), andAggregateError.errorsaren't redacted. Both verified. The string redaction held on every case I tried (quoted, parenthesized, uppercase scheme,%3F, cut tail,x-amz-security-token).listSiteFilesfollows symlinks out of the project root (e.g.assets -> ~/Downloadsuploads everything). Intended but undocumented.- Shared store: deleting a site never removes its
cas/objects, anduploaded:falsereveals that identical content already exists in the bucket. Both are fine under one stack = one trust domain; a doc sentence would help adopters with deletion obligations. - Size budget is at +5% (60,823 / 209,843), so little headroom for the next aws-lambda PR.
Public API / CLI caller impact (escalating, not blocking)
SiteHandle.projectS3Urinow points atsites/<id>/files.json. Any caller that downloads or untars it breaks.SiteHandle.bytesis now the raw file total, anduploadedAtis always the current time.- Site ids change once for every project (
site-v2, symlinks included), and the publichashProjectDiroutput changes. - A caller-supplied
siteIdreused with changed content now throwsPLAN_ARTIFACT_DIGEST_MISMATCH(main silently reused the stale tarball). renderToLambdacallers needstates:DescribeStateMachineand S3 GET/HEAD/PUT/Copy even onsiteHandlepaths, plusListBucketfor the tarball fallback.- New error names:
STACK_TOO_OLD,SITE_NOT_FOUND,SITE_FILES_MISSING,SITE_MANIFEST_INVALID. - Engine: an https input to the URL-read path now throws on a machine with none of the three CA-bundle paths (e.g. Windows). Only Lambda's presigned inputs reach it.
Checked with no finding
- Path traversal in
files.jsonis rejected (siteManifest.ts:95-100). - SHA-256 is verified on every download.
- Concurrent
deploySitecalls are safe (IfNoneMatch:*, then re-inspect). - The 7-day refresh runs on every deploy and every
siteHandlerender. - Presign lifetime is 3600 s against Lambda's 900 s cap, and a retry presigns again.
- TLS/reconnect args are added only for
https://, and the ffmpeg/ffprobe arg split matches the bundled binaries.
Tests at head
| Suite | Result |
|---|---|
| aws-lambda | 195/195 |
| gcp-cloud-run | 104/104 |
| CLI lambda | 54/54 |
| core redaction | 36/36 |
| producer PR-touched | 203/203 |
| producer unit lane | 889 pass, 1 fail (environmental MPEG-TS case) |
| engine PR-touched files | 420/425 |
All 5 engine failures fail the same way on main. CI: 73 pass, 9 skipped.
Not verified: no live AWS or Cloud Run run at this head; real-S3 BadDigest and 403 behavior; Step Functions throttling numbers.
— Somu
Main split the chunk worker's frame helpers into chunkFrames.ts; the URL-read sources, readiness skips and size hints move with them.
jrusso1020
left a comment
There was a problem hiding this comment.
Re-review at 09350ee1. All three of my blockers from aa33a0e3 are fixed in the code. My CHANGES_REQUESTED stays in place until there is a live run at this head on Lambda and Cloud Run, which this PR's bar requires. That run is now in progress on our side.
Blockers: fixed in code
- Old stack.
renderToLambda.ts:96-97callsassertStackReadsSitesbefore anything is uploaded. It reads the definition withDescribeStateMachineand throwsSTACK_TOO_OLDwith the redeploy command unlessSITE_MANIFEST_INVALIDis present, and only this PR's definitions contain it. If I make the check always pass, an aws-lambda test fails. The README no longer offers the v1 fallback and says to redeploy first. - Old site ids. In
deploySite.ts:94-99,126-135, a 404 onfiles.jsonfalls back toproject.tar.gz. When neither exists, it throwsSITE_NOT_FOUND, naming both keys andsites create. If I remove the fallback, 2 tests fail. - Docs.
aws-lambda.mdx:100-107covers thefiles.jsonsite, thecas/sha256/30-day rule, the upgrade order, old site ids and the one-time re-upload. Lines 233-239 add troubleshooting forSTACK_TOO_OLD,SITE_FILES_MISSINGandSITE_NOT_FOUND.
Tests at this head: aws-lambda 195/195, gcp-cloud-run 104/104. CI: 73 pass, 10 skipped, nothing red. All commits are signed.
Why there's still no approval: no live run of this tree
- Miguel's live upgrade run is at
2c9a0917. That came before main merged #5813 in (final96bf7fba), and before the stderr redaction for every spawn, theaudioExtractorremoval and the size-budget changes. Our Oct 18 combined run used #5813 at98183d65, not its final head. So the Lambda code at this head has never rendered live, and the run doesn't say which account it was in. - The Cloud Run part of this PR is byte-identical to
aa33a0e3, which passed live inhyperframes-dev. The shared producer and engine code under it has changed since, though.
What I need: at this head, in the dev accounts, (1) main stack + this CLI → STACK_TOO_OLD with no upload; (2) a main tarball site, then upgrade, then --site-id renders; (3) a fresh Lambda render and smoke with no signed-URL fragments in the logs; (4) Cloud Run smoke v1/v2 plus an old tarball site. I'll approve when those pass.
Non-blocking (agreeing with Somu's review)
render-batchrunsdeploySite(render-batch.ts:235) beforerenderToLambda. Against an old stack it uploads files first and then stops withSTACK_TOO_OLD, so the release note's "before uploading" isn't true for that command.- From the CLI,
render --site-idalways passesprojectDir, so an expiredcas/file shows up as a raw upload error instead ofSITE_FILES_MISSING.
Need: yes · Size: large, but the consolidation is deliberate · Reuse: the existing content-store and plan-v2 artifact paths · Simpler: no.
No live run at this head yet.
— Rames
jrusso1020
left a comment
There was a problem hiding this comment.
Approving at 09350ee1. This clears my request for changes (5561068381). My code blockers were already fixed at this head (see 5576018060). The one thing missing was a live run at this head, and it now passes on both clouds. It ran on Lambda in engineering-767398024897 (us-east-1) and on Cloud Run in hyperframes-dev (us-central1), against main 0ce14d64 (#5898). The fixture was mp4-h264-sdr, 640x360, 60 frames.
Back-compat, Lambda
- Old stack, new SDK: a stack deployed by main's CLI, rendered with the head CLI, fails fast with
STACK_TOO_OLD. The bucket stayed at 0 objects and no executions started, so nothing is uploaded before the check. - Old site after upgrade: a site created by main (
sites/<id>/project.tar.gz) still renders after the stack is upgraded to head.render --site-id <old>SUCCEEDED 60/60, and the execution input pointed at that tarball. - Unknown site: an unknown
--site-idfails withSITE_NOT_FOUNDand starts no execution.
At head, Lambda
- A fresh
files.jsonsite render is byte-identical to the old-tarball render. smoke.sh --plan-protocol both --chunk-counts 2,4exits 0, where it exited 6 ataa33a0e3. v1 and v2 match at N=2 and N=4, all with PSNR around 37.1 dB.- The render log group has 0 hits for
X-Amz-Signature,X-Amz-CredentialandAmz-Security-Token.
Cloud Run
smoke.sh --protocols v1,v2exits 0, with v1 and v2 matching.- A tarball site deployed by main's SDK renders with the head SDK under both v1 and v2, and the two outputs are byte-identical.
Everything was torn down in both accounts and verified.
Notes, not blockers
- The CLI keeps its stack-state file in the cwd. That's worth a line in the upgrade docs.
- My earlier nits (render-batch ordering and the unreachable
SITE_FILES_MISSING) still stand as follow-ups.
Need: yes · Size: ok · Reuse: existing deploySite/renderToLambda paths · Simpler: no.
Live run: yes, Lambda + Cloud Run at this head, run by the hyperframes session.
— Rames
Package size after merging current main
@hyperframes/producerpacks to 731,628 / 2,674,226 B after main's chunk-sizing change and this PR's URL reads, 2,248 B over its budget. None of the merged code is left unused, so the growth is real. The budget is raised to the measured size x1.05, the repo rule the size check applies.@hyperframes/aws-lambdadrops to 60,823 / 209,843, its measured size + 5%.Review fixes at
2c9a091701fBranch updated to current main (
b46ebedeae9, merged like the earlier main integration) and green on CI.40f7fb5a4ddrenderToLambdareads the state machine definition before uploading anything and stops withSTACK_TOO_OLDand the redeploy command unless the plan task listsSITE_MANIFEST_INVALIDas terminal, which this version's SAM template and CDK construct now do. The README's upgrade section no longer offers theplanProtocol: "v1"fallback.--site-idcannot find sites from earlier versionsf7ad850e21esites/<id>/files.jsonis missing, the render uses that site'sproject.tar.gz, which the plan step still reads. With neither, it fails before starting withSITE_NOT_FOUND, naming both keys and saying to runsites createagain.fbdb38675f9cas/sha256/store and its 30-day rule, the 7-day refresh, the upgrade order, old site ids, the one-time re-upload, the new IAM action for SDK callers, and troubleshooting forSTACK_TOO_OLD,SITE_FILES_MISSING,SITE_NOT_FOUND. A test keeps an entry for each pre-submit error.2991d68c42freading video by URLline per remote source with the path and the URL minus its query.82425cba7d4...Signature=...unmatched.ManagedChildProcess(every spawn's stderr) now redacts before trimming, and the redactor also strips a signing parameter cut off from its URL.Live upgrade path at
2c9a091701f, us-east-2, stack deployed by main's CLI (b46ebedeae9, built from source):STACK_TOO_OLDbefore anything was uploaded; 0 executions, bucket unchanged.lambda deployfrom this head,render --site-id <id from main's sites create>: rendered from that site'sproject.tar.gz, SUCCEEDED, 3600 frames, 120.000 s. A render of the same project as a new site gave identical frames (PSNR inf). CloudWatch: 16reading video by URLlines, 0 hits forX-Amz-Signature,X-Amz-CredentialorAmz-Security-Token. Cost $0.07 per render. Everything torn down.Release notes
sites/<id>/files.json) over a bucket-widecas/sha256/store, which older stacks cannot read. Renders against an older stack stop withSTACK_TOO_OLDbefore uploading.cas/sha256/expire 30 days after their last write; renders refresh files they reuse. A site whose files expired re-uploads them from the project directory, or stops withSITE_FILES_MISSINGwhen the SDK has none.states:DescribeStateMachineon the state machine.Packaging repair —
aa33a0e3eed498955918bb4d6f97881111557c83The actual hosted failure was duplicated presigner/Smithy code exceeding the published package budget. The four public ESM entries now build together and share internal chunks; all entry paths and the separate Lambda ZIP builder remain unchanged. The newly declared presigner stays external. The reachability pin now reflects the reviewed real remote-MP4 integration test classification.
Measured published package: 53,526 packed / 183,824 unpacked bytes. The gate required lowering the budget to 56,203 / 193,016, and that tighter limit passes. The packed verifier finds no unresolved internal imports; all four published entry imports retain their export counts and external dependency boundaries. AWS suite 167/167 and package/reachability script tests 49/49 pass. Normal commit hooks pass. Local declaration generation still reports the disclosed cached Smithy type mismatch; this is not a full local build success, clean-install or live Lambda acceptance claim. Current-head locked-dependency CI and independent combined review remain required. All earlier reports follow unchanged.
Current-main integrated successor
5ebfa8145a551da195cd0d5cbec6527538660062Normal signed merge of main deeb8d4 resolves the six actual storage/planning conflicts. It retains remote site manifests, shared digest references and per-chunk range routing alongside main's stable source paths, digest cache, stage timings and audio-mix/upload overlap. Remote and local artifacts use the same bounded32 publication queue; metadata is published last. Remote audio receives the URL map at main's later audio stage; deferred video sources retain stable project-relative identity.
Current combined checks: planV2 65 pass; real ffmpeg remote-MP4 planning1 pass; plan-hash24 pass; plan56 pass; S3 transport/publisher/handler47 pass. Producer typecheck, touched-file format/lint and normal signed commit hooks pass. The separate local AWS-adapter typecheck is non-green because cached S3 Smithy4.14.2 types conflict with the task-only declared presigner's separately resolved Smithy types; no source cast/suppression, manifest change or shared-cache edit was used. Hosted locked-dependency CI must establish the integrated adapter result. No new live AWS benchmark, deployment or all-workspace-build claim. Fresh full-target CI and independent combined review remain required.
Consolidated scope — owner-authorized queue reduction
This survivor preserves #5772, #5774, #5778, #5767, #5776 as original commits; every original head is an ancestor of
da6cc6d12b99ef2c255561c745ba1974bd2bdd0b. Superseded PR branches stay intact.Full preserved source union: shared content storage/remote probing and per-chunk file/range routing. This integrates both original stacks; all reports below keep their historical limitations. Combined tests and normal hooks are recorded in the task report; fresh current-main CI and independent review remain required. No deployment or new live cloud benchmark claim.
All original reports and captures follow, attributed to their original heads. Their limits remain; fresh full-target CI and independent review are required before normal merge. No admin bypass, deployment, paid call or feature activation was performed by this consolidation.
Original report — #5772 at 57df7e8
What changed
Rendering a project through a symlink (
ln -s my-project fx; hyperframes lambda render fx) uploaded a site that held a single entry,. -> <local path>, while the site id was a hash of the real files. The broken upload was cached under the correct id, so every later render of that project failed with[plan] entry file not founduntil the cached site was deleted by hand. Symlinked files or folders inside a project had the same split: the id ignored them, the upload shipped them as links that do not exist on the worker.The cause was two owners of "which files are the project": the id hash walked real files, and the upload ran
tarover.. Now one function decides:listSiteFiles(projectDir)inprojectHash.tslists the files a site ships. It follows symlinks (the root and anything inside, with a guard against folders that link back to a parent), leaves out the same top-level folders the plan stage skips, and throws on a dangling symlink, naming the path.hashProjectDirhashes that list, and bothdeploySiteverbs (AWS Lambda and Cloud Run) pack exactly that list.tarDirectorytakes an optional file list and follows symlinks only when given one, so plan and output archives are unchanged.deploySite, packs the same list.Site ids change once. The id now includes a scheme tag, so every site uploaded before this change, including broken ones sitting under the right old id, is bypassed. Each project re-uploads once after upgrading; after that the cache works as before. Caller-supplied
siteIdvalues are untouched.What I measured
mainwith the realdeploySite, a fake bucket and the worker's own untar: a symlink to therender-symlinked-assetsfixture produced a tarball with noindex.htmland noshared/brand.css, andplan()threw[plan] entry file not found. On this branch the same flow shipsindex.htmlandshared/brand.cssas a real file, andplan()writes the plan dir.mainand pass here, 3 runs in a row, along with the existing transport tests.What I did NOT exercise
plan.ts) still applies the skip list itself; it reads an already-filtered site in the cloud path, so it cannot disagree there. Moving it ontolistSiteFilesis a possible follow-up.Original report — #5774 at 62eb3c8
What changed
A Lambda render used to move a project's video three times before any chunk started: the SDK tarred and uploaded the whole project, the plan step downloaded and untarred it, then re-uploaded the video as a plan artifact under that render's own prefix. Nothing was reused across renders.
cas/sha256/<first two hex>/<sha256>, shared by every render. The key comes from the SHA-256 alone (contentStore.tsowns the prefix, the key and the retention numbers).deploySiteuploads each project file (the list fromlistSiteFiles) to the shared area, skipping any whose key already exists (HEAD), then writes a smallsites/<siteId>/files.jsonmapping path to hash. No tarball, no gzip.files.json, verifying each file's SHA-256 as it downloads. Plan v2 artifacts now live in the shared area too, so the video the SDK already stored is found by HEAD and never re-uploaded. A.tar.gzProjectS3Uristill works for hand-built uploads (smoke.sh, the README).IfNoneMatch: *) withChecksumSHA256, chunks and assemble still verify each artifact's SHA-256 on download, and an existing object is reused only when its stored digest and size match.files.jsonpaths that escape the project are refused.--site-idbuilds its handle through one SDK helper (siteHandleFromId) instead of two hardcoded tarball URIs.Retention: 30 days, refreshed on reuse
cas/sha256/after 30 days, added to both the CDK stack and the SAM template. A test pins the SAM rules to the CDK rules and to the constants.CopyObject, same bytes, same SHA-256 metadata and checksum). That restarts its 30-day clock.render --site-id,render-batch, or an SDKsiteHandle), it HEADs every file in the site'sfiles.json. That also refreshes any file older than 7 days. Any missing file is re-uploaded from the local project directory, after checking it against its recorded SHA-256. The CLI always passes the project directory, so it never fails on expiry. An SDK caller who passes a site handle withoutprojectDirgetsSITE_FILES_MISSING, with the missing-file count and the fix (runsites create, or passprojectDir).IAM
No change needed. The function already has read and write on the whole render bucket (CDK
grantReadWrite, SAMS3CrudPolicy). That covers HEAD, GET, PUT and the in-place copy on the new prefix. Nothing was widened.Not changed
Cloud Run keeps today's tarball path. Chunk range reads (a sibling change) read deferred videos through a presigned URL to
<artifact prefix>/<aa>/<sha256>and check S3's stored SHA-256. That key and checksum are exactly what the shared area holds, so it works against these objects unchanged.Stacked on the symlink fix: this uses its
listSiteFilesas the only definition of "the project's files".What I measured
bun test, one file at a time, inpackages/aws-lambda. All pass: transport 14, deploySite 9, publisher 7, handler 18, CDK contract 6, CDK snapshot 13, renderToLambda 11, plus the untouched suites.uploaded: false) in 2.8 s, timed twice. After two renders the bucket held exactly one copy of the video (undercas/sha256/), and the deployed bucket showed both lifecycle rules (renders/7 days,cas/sha256/30 days). Numbers and the teardown are in perf(aws-lambda): the plan step probes project videos in place instead of downloading them #5778.What I did NOT exercise
CopyObjectrequest shape is tested.Original report — #5778 at 6d3ed92
What changed
The plan step used to download every project video in full, only to read its header and its last second. Now a video stays in the shared content store (from the PR below this one), and the plan reads those two parts over HTTP.
.mp4 .mov .m4v .webm .mkv) it passes a pre-signed GET URL (1 hour) with the file's SHA-256 and size asremoteFilestoplanV2WithPublisher. A.tar.gzproject and the v1 plan path behave exactly as before.meta/videos.jsonrecords the project path, never the URL.meta/remote-files.jsonlists path, SHA-256 and size, so the plan hash covers those files.compiled/<path>, routed like any compiled asset. A new optional publisher method,referenceStoredBlob, confirms the object is already in the store (stored digest and size must match, and the 7-day age refresh applies) instead of uploading it. A local plan directory, or a publisher without that method, refuses remote files loudly.Two bugs fixed along the way
extractMediaMetadataread the entire video into memory as a possible PNG whenever any of its three color tags was missing, which is common for H.264. It now does that only when ffprobe reports PNG. A regression test fails without the fix.readFileSync, which Node refuses above 2 GiB. It now streams, using one shared hasher (sha256FileHex) forfreezePlanand plan v2.What I measured
meta/videos.jsonpoints atcompiled/assets/clip.mp4with no URL in it, the publisher references the digest and never receives its bytes, and changing the digest changes the plan hash.srcPathmapping, the handler keeping videos remote, a missing object accepted, a referenced digest not counted as durable, and the PNG fix reverted.cas/sha256/, last written at the CLI upload. Neither plan read it whole or re-uploaded it, and no render has its own copy.What I did NOT exercise
data-has-audio) is still fetched whole by the audio mixer when it needs that audio. That is correct but not faster; the plan's audio work is a separate change.Original report — #5767 at 1cdc271
Distributed render chunks (plan v2, Lambda and Cloud Run) now download only the project videos they show. Before this, every chunk downloaded every video in the project.
What changed
planV2.ts,artifactTargets). Chunks extract their own frames from deferred video sources. A deferred source that the project already ships undercompiled/now goes only to the chunks that show it, asvideo-sources/files already did. Everything else undercompiled/(HTML, images, fonts, CSS, videos the plan does not defer) still goes to every chunk. Lambda and Cloud Run both read these per-chunk lists, so neither adapter changed.renderChunk.ts). A chunk skips the browser readiness wait for a deferred video it does not show. That is exactly the set the router no longer gives it, taken from the chunk's own frame extraction result. Without this, the chunk logsmedia_load_failedfor a file it was not given on purpose. A video the chunk shows keeps today's readiness wait. A missing source for a shown video still fails the chunk (MISSING_PLAN_ARTIFACT, unchanged).<video>src fails the chunk. In practice chunks run best-effort and only log a warning, and this PR removes that warning too. The test is replaced by "ships each compiled/ video source only to the chunks that show it", which uses two clips: one shown in a single chunk, one shown in both.I tried and rejected giving chunks the size hints that local renders use (
collectVideoMetadataHints). They set aheightattribute that resizes a video styled with only a CSS width, so chunk 0 changed pixels. The before/after chunk hashes caught it. Local renders have the same behaviour today; that is a separate fix.What I measured
Linux host, a 60 s 640x360 composition at 30 fps in 4 chunks. It has a 60 MB video shown for 0-60 s and a 1 MB video shown for 0-10 s.
media_load_failedwarnings in either run."all"instead of[1], and the chunk test gets"all"instead of[0].media_load_failed.planV2,renderChunk,plan,planSizeCap,publicExports,chunkBoundaryandrenderChunkFallbacktests are green, as are the aws-lambdahandlerand Cloud Runservertests and producertsc --noEmit.What I did NOT exercise
listPlanV2ArtifactsForTarget,materializePlanV2TargetandrenderChunkpath locally.compiled/still goes to every chunk even though no chunk reads it. Scoping those needs the plan to record their plan-relative path. That is a follow-up.Original report — #5776 at 2895705
What changed
On the AWS Lambda path (plan protocol v2), a chunk that extracts its own frames from a long project video no longer downloads and hashes the whole video before its browser starts. It reads the video straight from S3 by byte range: only the container index and the part its seek needs.
<video>from the plan's metadata. This is what a local render already does for every video whose frames it supplies (collectVideoReadinessSkipIds/collectVideoMetadataHints).materializePlanV2Target(..., { remoteSources })) is storage-neutral, so the same switch is available to it later.Why the bytes are still the right bytes
A deferred video is no longer hashed on the chunk, so integrity rests on how plan v2 stores artifacts:
.../artifacts/sha256/<2>/<sha256>, and the manifest that names them is itself pinned by thePlanHashthe state machine passes.If-None-Match: *andChecksumSHA256, so S3 rejects a body that does not hash to the key, and no publisher can overwrite it.HeadObjectwithChecksumMode: ENABLEDand refuses (PLAN_ARTIFACT_DIGEST_MISMATCH) unless the stored SHA-256 and size equal the manifest's.zero_output). The chunk already fails when any frame it needs is missing.httpsinput, ffmpeg and ffprobe get-tls_verify 1 -ca_file <bundle>(urlInputArgs). The bundle is the first of/etc/pki/tls/certs/ca-bundle.crt(the Lambdanodejs:22image, Amazon Linux 2023),/etc/ssl/certs/ca-certificates.crtand/etc/ssl/cert.pemthat exists. If none exists, the read fails with a clear error instead of going ahead unverified. Local paths and plainhttpare unchanged.The signed URL never shows up in errors
extractVideoFramesRangestrips the query string from every http(s) URL in each string field of an error it throws (message, diagnostic, stack). It uses the sameredactUrlQueryStringsthat telemetry already uses, now exported from core. ffprobe diagnostics were already redacted.What I measured
<video>sizing.public.ecr.aws/lambda/nodejs:22(Amazon Linux 2023):/etc/pki/tls/certs/ca-bundle.crtexists and/etc/ssl/certs/ca-certificates.crtdoes not. With the bundled ffmpeg and ffprobe builds,-tls_verify 1 -ca_fileaccepts S3, rejects a self-signed certificate, and fails when the CA file is missing. Without-tls_verifythe self-signed site is accepted, which is the gap this closes.-tls_verify 1 -ca_file. Another checks the refusal when no bundle exists, and a third checks that a failing ffmpeg run leaves no signed query in the error. Breaking each fix turns its test red.One real AWS Lambda render of a 30-minute 1080p30 fixture: a 932 MB H.264 background video that is on screen the whole time, plus a GSAP title and AAC audio. I deployed this branch with reserved concurrency 300 and rendered with
--max-parallel-chunks 225, giving 225 chunks of 240 frames.ffprobe -count_packets: h264 1920x1080 at 30/1, 54000 frames, 1800.000 s. AAC audio is 1800.000 s (84376 packets) at a mean of -27.1 dB, the same as the baseline. Frames grabbed at 900 s and 1799 s show the right background timecode and title state.The baseline was measured at 16 chunks, so the end-to-end times are not comparable. The window before browser launch is: it is spent on the download whatever the chunk length, and it is what this PR removes.
What I did NOT exercise
httpinputs, and hosts whose CA bundle is somewhere else. Plainhttpinputs get no TLS options; only the local test servers use them. A host with its bundle in another path fails with a clear error instead of reading unverified.