Skip to content

Reuse private CDN signing windows for 150 s in the wedding demo - #38

Merged
kvz merged 3 commits into
mainfrom
cdn-rotation
Sep 29, 2026
Merged

kvz merged 3 commits into
mainfrom
cdn-rotation

Conversation

@kvz

@kvz kvz commented Sep 29, 2026 •

Copy link
Copy Markdown
Member

Why

Private wedding photos redirect through /api/transloadit/media to signed Smart CDN URLs. The
route re-signs every 60 s, and the CDN caches on the full signed query, exp included. So a
reload two minutes later gets new URLs, which are new cache keys and may need another cold
transform. The lead's production
sample showed six of six MISSes at about 2 s, while the same exact URLs returned HITs in
25–135 ms. transloadit/node-sdk#527 adds an optional rotationIntervalMs to Viewer's
createStorageRoute, bounded by half the lifetime, with the default, maximum grant and
per-request authorization unchanged.

What changes

  • A red-first route test (01b3344):

    • Requests within one 150 s window get the same signed URL.
    • At the window boundary a new URL is issued with exp exactly 300 s after its window start.
    • Every request calls Convex media:forDelivery, and a revocation in the middle of a window
      returns 404 on the next request.
    • Responses stay private, no-store.

    It failed on Viewer 0.0.3, which rotates every 60 s: expected exp 1790705100000, received
    1790705220000.

  • The fix (1eebcfc):

    • Pin @transloadit/viewer to exactly 0.0.5, with its exact age-gate preapproval. It was
      published by Version Packages node-sdk#528 (gitHead db3b77f, SLSA provenance). Its integrity
      matches the publication handoff.
    • Set lifetimeMs: 300_000 (unchanged) and rotationIntervalMs: 150_000 in the route.
    • Correct docs/wedding-gallery.md, which said signing "rotates at most once a minute".
  • Council follow-up (dbea6d4): the docs now name Viewer 0.0.5, and the route comment states the
    150-300 s validity instead of "half its lifetime".

The published tarball differs from 0.0.3 only in the createStorageRoute option, its types and
docs; 0.0.3 to 0.0.4 was docs only. The installed package is byte-identical to the verified
tarball, and its dependencies are unchanged.

This PR is demo-only. Viewer is a root dev dependency, and the PR adds no changeset, so
@transloadit/convex does not change. It includes no auth relaxation, no longer grant, no uploads
and no API2 or production config changes. The only configuration added is Root's branch-only
Vercel preview mapping for NEXT_PUBLIC_CONVEX_URL.

Checks

  • Route suite: 9/9 on 0.0.5, and the 150 s window test is now green.
  • yarn check: format, lint and typecheck pass (only pre-existing infos in untouched scripts/).
    Tests: 200/200, on 1eebcfc and again on dbea6d4.
  • Council (council.ts review, no --post): Model B found no issues. The arbiter hit a provider
    usage limit, so these are raw reviews. Model A's findings:
    • Stale 0.0.3 docs pin: fixed.
    • Route comment tied to R = L/2: reworded. The test also pins both exact values.
    • Stale PR body: it reviewed the draft body, since updated.
    • 0.0.4/0.0.5 contents: covered by the tarball diff above. next/server's rotationInterval
      is unchanged since 0.0.3.
  • Preview: a branch-only Vercel NEXT_PUBLIC_CONVEX_URL maps to this branch's Convex preview.

🤖 Generated with Claude Code

Red-first regression for the demo's private media route: requests within one
150 s window receive the same signed URL, a new URL never gets more than the
five-minute grant, and every request still reauthorizes, so a revocation in the
middle of a window returns 404 at once. It fails on Viewer 0.0.3, whose route
rotates every 60 s. The published Viewer with rotationIntervalMs follows in
this PR.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@vercel

vercel Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
convex Ready Ready Preview Sep 29, 2026 8:12pm UTC

Request Review

Pin @transloadit/viewer 0.0.5, which adds rotationIntervalMs, and sign
private media per 150 s window with the five-minute lifetime unchanged.
Repeat views within a window get the same URL and can hit the CDN cache;
every request still reauthorizes and a new URL keeps 150-300 s of validity.
The wedding-gallery docs no longer claim a once-a-minute rotation.

Demo-only: Viewer is a dev dependency, so @transloadit/convex is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The wedding-gallery docs still named Viewer 0.0.3, and the route comment
described the 150-300 s validity as "half its lifetime", which only holds
while the interval stays half the lifetime. Both now state the actual values.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kvz
kvz marked this pull request as ready for review September 29, 2026 20:13
@kvz
kvz merged commit 621036f into main Sep 29, 2026
12 checks passed
@kvz

kvz commented Sep 29, 2026

Copy link
Copy Markdown
Member Author

Independent production acceptance passed on September 29, 2026, after the merge and automatic deploy.

  • Production alias convex-demo.transload.it points to the READY deployment of 621036fd33da8c994f4ea3784a08956113bf23de.
  • Main CI and production verification are green. The demo consumes published @transloadit/viewer@0.0.5 (alpha); the Convex package remains 0.4.0.
  • Real wall-clock probe, 20:30:02–20:35:02 UTC: identical signed CDN URL across the former 60-second boundary; CDN HIT and identical bytes; new URL at the 150-second boundary; maximum validity remains 300 seconds.
  • Anonymous and completed-logout app requests both returned 404 and private, no-store. The already-issued CDN grant remained usable only until its original expiry, as designed.
  • After expiry, the exact previously warmed URL returned 400, Transloadit-Error: AUTH_EXPIRED, cdn-cache: EXPIRED, and cache-control: no-store, with no cache-busting parameter.

Nine visible private images decoded in the browser. Exact-URL network repeats in the initial sample were CDN HITs at 25–109 ms; cold image TTFBs were about 1.9–2.1 seconds. These are local samples, not a latency SLA or a controlled before/after benchmark. The wider signing window reduces cache-key churn; it does not remove cold transforms or per-request authorization.

The first temporary probe stopped on an insufficiently instrumented assertion in its logout block. The UI shows the disabled login form while sign-out is still pending; the probe now waits for completion rather than visibility. The entire production sequence above was rerun successfully without a product change. The precise original failed predicate was not recovered.

Existing synthetic media only; no asset uploads or deletions. Test guests were logged out. No bearer URLs, cookies, credentials, or response bodies retained in the evidence.

This branch was successfully deployed

1 active deployment
Preview — dbea6d48 Deployed Sep 29, 2026 by vercel[bot]
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.

1 participant