Skip to content

[core] [world] Gate CBOR queue transport on specVersion#1627

Merged
VaguelySerious merged 12 commits intomainfrom
peter/cbor-transport-compat
Apr 7, 2026
Merged

[core] [world] Gate CBOR queue transport on specVersion#1627
VaguelySerious merged 12 commits intomainfrom
peter/cbor-transport-compat

Conversation

@VaguelySerious
Copy link
Copy Markdown
Member

Summary

PR #1537 changed the world-vercel queue transport from JSON to CBOR. This breaks replay/reenqueue from the dashboard (and any queue operation targeting an older deployment) because old deployments expect JSON messages.

  • Bumps specVersion to 3 — new runs use CBOR queue transport
  • queue() selects transport based on opts.specVersion: JSON for < 3, CBOR for >= 3
  • Handler uses DualTransport that deserializes CBOR first, falls back to JSON — so new deployments handle messages from both old and new senders
  • runInput (resilient start payload) only included for specVersion >= 3, since old deployments ignore it anyway and Uint8Array doesn't survive plain JSON serialization
  • Fixes isLegacySpecVersion to check against SPEC_VERSION_LEGACY (1) instead of SPEC_VERSION_CURRENT, preventing v2 runs from being incorrectly treated as v1-compat after the bump
  • All world.queue() call sites now pass specVersion in options

Test plan

  • packages/core tests pass (580/580)
  • packages/world-vercel tests pass (21/21)
  • e2e: replay a specVersion 2 run from the dashboard → should use JSON transport, old deployment processes it
  • e2e: start a new run (specVersion 3) → should use CBOR transport, new deployment processes it
  • e2e: new deployment receives re-enqueued JSON message from old deployment → DualTransport handles it

🤖 Generated with Claude Code

VaguelySerious and others added 2 commits April 6, 2026 15:56
Bump SPEC_VERSION_CURRENT to 3 and fix isLegacySpecVersion to check
against SPEC_VERSION_LEGACY instead of SPEC_VERSION_CURRENT, preventing
v2 runs from being incorrectly treated as legacy when the version bumps.

Add JsonTransport and DualTransport to world-vercel queue: the handler
uses DualTransport (CBOR-first, JSON fallback) to accept messages from
both old and new deployments, while the send path selects CBOR or JSON
based on the target run's specVersion. Pass specVersion through queue
opts from start(), reenqueueRun(), wakeUpRun(), and resumeHook().

Only include runInput in the queue payload for specVersion >= 3, creating
clean separation between old and new behavior.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Bump specVersion to 3. Use JSON transport for queue messages targeting
older deployments (specVersion < 3), CBOR for new ones. Handler uses
dual transport that deserializes both formats, so new deployments can
receive messages from old and new senders.

Fixes replay/reenqueue from the dashboard to older deployments that
expect JSON queue messages.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@VaguelySerious VaguelySerious requested a review from a team as a code owner April 6, 2026 22:58
@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Apr 6, 2026

🦋 Changeset detected

Latest commit: ff8a79c

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 20 packages
Name Type
@workflow/world-vercel Patch
@workflow/world Patch
@workflow/core Patch
@workflow/cli Patch
@workflow/vitest Patch
@workflow/web-shared Patch
@workflow/world-local Patch
@workflow/world-postgres Patch
@workflow/world-testing Patch
@workflow/builders Patch
@workflow/next Patch
@workflow/nitro Patch
workflow Patch
@workflow/astro Patch
@workflow/nest Patch
@workflow/rollup Patch
@workflow/sveltekit Patch
@workflow/vite Patch
@workflow/nuxt Patch
@workflow/ai Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel
Copy link
Copy Markdown
Contributor

vercel bot commented Apr 6, 2026

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

Project Deployment Actions Updated (UTC)
example-nextjs-workflow-turbopack Ready Ready Preview, Comment Apr 7, 2026 0:02am
example-nextjs-workflow-webpack Ready Ready Preview, Comment Apr 7, 2026 0:02am
example-workflow Ready Ready Preview, Comment Apr 7, 2026 0:02am
workbench-astro-workflow Ready Ready Preview, Comment Apr 7, 2026 0:02am
workbench-express-workflow Ready Ready Preview, Comment Apr 7, 2026 0:02am
workbench-fastify-workflow Ready Ready Preview, Comment Apr 7, 2026 0:02am
workbench-hono-workflow Ready Ready Preview, Comment Apr 7, 2026 0:02am
workbench-nitro-workflow Ready Ready Preview, Comment Apr 7, 2026 0:02am
workbench-nuxt-workflow Ready Ready Preview, Comment Apr 7, 2026 0:02am
workbench-sveltekit-workflow Ready Ready Preview, Comment Apr 7, 2026 0:02am
workbench-vite-workflow Ready Ready Preview, Comment Apr 7, 2026 0:02am
workflow-docs Ready Ready Preview, Comment, Open in v0 Apr 7, 2026 0:02am
workflow-swc-playground Ready Ready Preview, Comment Apr 7, 2026 0:02am

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 6, 2026

📊 Benchmark Results

📈 Comparing against baseline from main branch. Green 🟢 = faster, Red 🔺 = slower.

workflow with no steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Express 0.041s (-5.4% 🟢) 1.005s (~) 0.965s 10 1.00x
💻 Local Nitro 0.041s (-3.7%) 1.006s (~) 0.965s 10 1.01x
🐘 Postgres Nitro 0.048s (-17.9% 🟢) 1.011s (~) 0.963s 10 1.19x
💻 Local Next.js (Turbopack) 0.048s 1.006s 0.957s 10 1.19x
🐘 Postgres Express 0.057s (-6.1% 🟢) 1.010s (~) 0.954s 10 1.40x
🐘 Postgres Next.js (Turbopack) 0.059s 1.010s 0.951s 10 1.47x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 0.230s (-3.3%) 1.874s (-17.9% 🟢) 1.644s 10 1.00x
▲ Vercel Next.js (Turbopack) 0.249s (-49.3% 🟢) 2.313s (-10.4% 🟢) 2.064s 10 1.08x
▲ Vercel Express ⚠️ missing - - - -

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 1 step

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Next.js (Turbopack) 1.115s 2.005s 0.890s 10 1.00x
🐘 Postgres Nitro 1.123s (-1.3%) 2.010s (~) 0.887s 10 1.01x
💻 Local Nitro 1.124s (~) 2.006s (~) 0.882s 10 1.01x
💻 Local Express 1.132s (~) 2.006s (~) 0.874s 10 1.01x
🐘 Postgres Next.js (Turbopack) 1.145s 2.009s 0.864s 10 1.03x
🐘 Postgres Express 1.146s (~) 2.009s (~) 0.863s 10 1.03x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 1.862s (-32.2% 🟢) 3.337s (-27.6% 🟢) 1.475s 10 1.00x
▲ Vercel Next.js (Turbopack) 1.890s (+2.0%) 3.579s (-4.1%) 1.689s 10 1.02x
▲ Vercel Express ⚠️ missing - - - -

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 10 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 10.692s (-1.8%) 11.019s (~) 0.327s 3 1.00x
💻 Local Next.js (Turbopack) 10.820s 11.025s 0.205s 3 1.01x
🐘 Postgres Next.js (Turbopack) 10.828s 11.017s 0.189s 3 1.01x
🐘 Postgres Express 10.857s (~) 11.019s (~) 0.162s 3 1.02x
💻 Local Nitro 10.918s (~) 11.023s (~) 0.105s 3 1.02x
💻 Local Express 10.942s (~) 11.024s (~) 0.082s 3 1.02x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 17.886s (-1.6%) 19.101s (-3.5%) 1.215s 2 1.00x
▲ Vercel Next.js (Turbopack) 17.916s (-15.5% 🟢) 19.254s (-17.3% 🟢) 1.338s 2 1.00x
▲ Vercel Express ⚠️ missing - - - -

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 25 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 14.105s (-3.3%) 15.021s (~) 0.916s 4 1.00x
🐘 Postgres Next.js (Turbopack) 14.457s 15.020s 0.562s 4 1.02x
🐘 Postgres Express 14.508s (-0.6%) 15.025s (~) 0.517s 4 1.03x
💻 Local Next.js (Turbopack) 14.687s 15.029s 0.342s 4 1.04x
💻 Local Express 14.953s (~) 15.029s (~) 0.076s 4 1.06x
💻 Local Nitro 15.005s (+0.6%) 15.279s (+1.7%) 0.274s 4 1.06x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Next.js (Turbopack) 30.540s (-8.8% 🟢) 33.068s (-7.1% 🟢) 2.528s 2 1.00x
▲ Vercel Nitro 31.119s (~) 32.112s (-2.4%) 0.993s 2 1.02x
▲ Vercel Express ⚠️ missing - - - -

🔍 Observability: Next.js (Turbopack) | Nitro

workflow with 50 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 13.024s (-7.0% 🟢) 13.591s (-4.1%) 0.567s 7 1.00x
🐘 Postgres Next.js (Turbopack) 13.850s 14.017s 0.167s 7 1.06x
🐘 Postgres Express 13.937s (-1.8%) 14.161s (-4.8%) 0.224s 7 1.07x
💻 Local Next.js (Turbopack) 16.067s 16.697s 0.630s 6 1.23x
💻 Local Express 16.361s (-2.6%) 17.030s (~) 0.670s 6 1.26x
💻 Local Nitro 16.583s (~) 17.031s (~) 0.448s 6 1.27x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 50.374s (-6.6% 🟢) 51.691s (-7.3% 🟢) 1.317s 2 1.00x
▲ Vercel Next.js (Turbopack) 53.558s (-3.5%) 55.194s (-3.8%) 1.636s 2 1.06x
▲ Vercel Express ⚠️ missing - - - -

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.all with 10 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 1.185s (-5.8% 🟢) 2.009s (~) 0.824s 15 1.00x
🐘 Postgres Next.js (Turbopack) 1.218s 2.009s 0.791s 15 1.03x
🐘 Postgres Express 1.241s (-3.5%) 2.008s (~) 0.767s 15 1.05x
💻 Local Nitro 1.495s (-3.8%) 2.005s (~) 0.510s 15 1.26x
💻 Local Express 1.508s (-1.8%) 2.006s (~) 0.498s 15 1.27x
💻 Local Next.js (Turbopack) 1.571s 2.005s 0.434s 15 1.33x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 1.983s (-22.4% 🟢) 3.257s (-22.4% 🟢) 1.274s 10 1.00x
▲ Vercel Next.js (Turbopack) 2.522s (-10.6% 🟢) 4.134s (-8.0% 🟢) 1.611s 8 1.27x
▲ Vercel Express ⚠️ missing - - - -

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.all with 25 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 2.285s (-2.2%) 3.011s (~) 0.726s 10 1.00x
🐘 Postgres Express 2.352s (+1.0%) 3.011s (~) 0.659s 10 1.03x
🐘 Postgres Next.js (Turbopack) 2.407s 3.010s 0.603s 10 1.05x
💻 Local Next.js (Turbopack) 2.740s 3.208s 0.468s 10 1.20x
💻 Local Express 2.770s (-11.5% 🟢) 3.008s (-22.6% 🟢) 0.238s 10 1.21x
💻 Local Nitro 2.899s (-7.2% 🟢) 3.108s (-20.0% 🟢) 0.208s 10 1.27x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 2.662s (+1.9%) 3.837s (-9.4% 🟢) 1.175s 8 1.00x
▲ Vercel Next.js (Turbopack) 2.768s (-16.2% 🟢) 4.322s (-20.1% 🟢) 1.553s 8 1.04x
▲ Vercel Express ⚠️ missing - - - -

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.all with 50 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 3.388s (-2.8%) 4.009s (~) 0.622s 8 1.00x
🐘 Postgres Express 3.457s (-0.9%) 4.010s (~) 0.553s 8 1.02x
🐘 Postgres Next.js (Turbopack) 3.665s 4.009s 0.343s 8 1.08x
💻 Local Express 7.473s (-10.4% 🟢) 8.023s (-11.1% 🟢) 0.550s 4 2.21x
💻 Local Next.js (Turbopack) 7.735s 8.018s 0.282s 4 2.28x
💻 Local Nitro 8.395s (+2.9%) 9.026s (+2.9%) 0.631s 4 2.48x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 2.911s (-12.7% 🟢) 4.255s (-15.1% 🟢) 1.344s 8 1.00x
▲ Vercel Next.js (Turbopack) 3.112s (-30.5% 🟢) 4.729s (-22.9% 🟢) 1.617s 7 1.07x
▲ Vercel Express ⚠️ missing - - - -

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.race with 10 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 1.193s (-4.0%) 2.008s (~) 0.815s 15 1.00x
🐘 Postgres Next.js (Turbopack) 1.229s 2.008s 0.779s 15 1.03x
🐘 Postgres Express 1.253s (~) 2.007s (~) 0.754s 15 1.05x
💻 Local Next.js (Turbopack) 1.513s 2.006s 0.493s 15 1.27x
💻 Local Express 1.533s (~) 2.006s (~) 0.473s 15 1.29x
💻 Local Nitro 1.537s (~) 2.006s (~) 0.469s 15 1.29x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Next.js (Turbopack) 2.016s (-4.7%) 3.803s (-6.3% 🟢) 1.787s 8 1.00x
▲ Vercel Nitro 2.332s (-2.3%) 3.575s (-10.8% 🟢) 1.243s 9 1.16x
▲ Vercel Express ⚠️ missing - - - -

🔍 Observability: Next.js (Turbopack) | Nitro

Promise.race with 25 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 2.262s (-2.5%) 3.008s (~) 0.746s 10 1.00x
🐘 Postgres Express 2.337s (-0.8%) 3.009s (~) 0.673s 10 1.03x
🐘 Postgres Next.js (Turbopack) 2.391s 3.009s 0.617s 10 1.06x
💻 Local Next.js (Turbopack) 2.895s 3.760s 0.865s 8 1.28x
💻 Local Nitro 2.971s (~) 3.675s (~) 0.704s 9 1.31x
💻 Local Express 2.985s (+0.8%) 3.565s (-5.2% 🟢) 0.580s 9 1.32x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 2.391s (-9.0% 🟢) 3.586s (-12.2% 🟢) 1.195s 9 1.00x
▲ Vercel Next.js (Turbopack) 3.464s (+29.0% 🔺) 4.857s (+10.0% 🔺) 1.393s 7 1.45x
▲ Vercel Express ⚠️ missing - - - -

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.race with 50 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 3.363s (-3.1%) 4.011s (~) 0.648s 8 1.00x
🐘 Postgres Express 3.449s (-1.4%) 4.010s (~) 0.562s 8 1.03x
🐘 Postgres Next.js (Turbopack) 3.674s 4.012s 0.338s 8 1.09x
💻 Local Express 8.092s (-5.6% 🟢) 8.527s (-5.5% 🟢) 0.435s 4 2.41x
💻 Local Next.js (Turbopack) 8.108s 8.520s 0.412s 4 2.41x
💻 Local Nitro 8.873s (~) 9.276s (-2.6%) 0.402s 4 2.64x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Next.js (Turbopack) 3.429s (-33.1% 🟢) 4.960s (-26.7% 🟢) 1.531s 7 1.00x
▲ Vercel Nitro 3.861s (+40.2% 🔺) 5.231s (+13.7% 🔺) 1.371s 6 1.13x
▲ Vercel Express ⚠️ missing - - - -

🔍 Observability: Next.js (Turbopack) | Nitro

workflow with 10 sequential data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 0.610s (-26.9% 🟢) 1.006s (~) 0.396s 60 1.00x
🐘 Postgres Next.js (Turbopack) 0.803s 1.023s 0.220s 59 1.32x
🐘 Postgres Express 0.819s (-5.1% 🟢) 1.006s (-1.7%) 0.187s 60 1.34x
💻 Local Next.js (Turbopack) 0.837s 1.004s 0.168s 60 1.37x
💻 Local Nitro 0.975s (~) 1.021s (-5.1% 🟢) 0.046s 59 1.60x
💻 Local Express 1.011s (+3.7%) 1.654s (+53.8% 🔺) 0.643s 37 1.66x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 9.505s (+8.2% 🔺) 10.933s (+1.5%) 1.427s 6 1.00x
▲ Vercel Next.js (Turbopack) 11.143s (+18.2% 🔺) 12.867s (+12.3% 🔺) 1.724s 5 1.17x
▲ Vercel Express ⚠️ missing - - - -

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 25 sequential data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 1.529s (-22.3% 🟢) 2.029s (-10.1% 🟢) 0.500s 45 1.00x
🐘 Postgres Next.js (Turbopack) 1.930s 2.124s 0.195s 43 1.26x
🐘 Postgres Express 1.969s (-6.0% 🟢) 2.315s (-20.5% 🟢) 0.346s 39 1.29x
💻 Local Next.js (Turbopack) 2.699s 3.008s 0.309s 30 1.76x
💻 Local Express 3.006s (~) 3.508s (-1.1%) 0.502s 26 1.97x
💻 Local Nitro 3.017s (-6.1% 🟢) 3.689s (-1.9%) 0.672s 25 1.97x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 28.066s (+5.5% 🔺) 29.418s (+3.3%) 1.352s 4 1.00x
▲ Vercel Next.js (Turbopack) 31.347s (+10.1% 🔺) 33.341s (+8.8% 🔺) 1.995s 3 1.12x
▲ Vercel Express ⚠️ missing - - - -

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 50 sequential data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 3.029s (-24.3% 🟢) 3.467s (-19.9% 🟢) 0.438s 35 1.00x
🐘 Postgres Next.js (Turbopack) 3.840s 4.077s 0.236s 30 1.27x
🐘 Postgres Express 3.978s (-5.6% 🟢) 4.367s (-12.2% 🟢) 0.389s 28 1.31x
💻 Local Next.js (Turbopack) 8.799s 9.161s 0.362s 14 2.90x
💻 Local Express 8.947s (-1.5%) 9.401s (-1.6%) 0.454s 13 2.95x
💻 Local Nitro 9.203s (+1.8%) 10.018s (+4.8%) 0.815s 12 3.04x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 75.407s (-5.5% 🟢) 76.339s (-6.5% 🟢) 0.932s 2 1.00x
▲ Vercel Next.js (Turbopack) 85.779s (+15.9% 🔺) 87.844s (+15.8% 🔺) 2.064s 2 1.14x
▲ Vercel Express ⚠️ missing - - - -

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 0.216s (-22.4% 🟢) 1.007s (~) 0.791s 60 1.00x
🐘 Postgres Next.js (Turbopack) 0.251s 1.007s 0.756s 60 1.16x
🐘 Postgres Express 0.275s (-4.4%) 1.007s (~) 0.732s 60 1.28x
💻 Local Express 0.551s (-3.8%) 1.004s (~) 0.453s 60 2.55x
💻 Local Next.js (Turbopack) 0.569s 1.005s 0.435s 60 2.64x
💻 Local Nitro 0.578s (-6.0% 🟢) 1.004s (-1.7%) 0.426s 60 2.68x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 1.463s (-19.2% 🟢) 2.779s (-15.7% 🟢) 1.316s 22 1.00x
▲ Vercel Next.js (Turbopack) 1.577s (+10.8% 🔺) 3.550s (+24.6% 🔺) 1.973s 17 1.08x
▲ Vercel Express ⚠️ missing - - - -

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 0.358s (-27.2% 🟢) 1.006s (~) 0.648s 90 1.00x
🐘 Postgres Express 0.476s (-3.1%) 1.006s (~) 0.530s 90 1.33x
🐘 Postgres Next.js (Turbopack) 0.480s 1.006s 0.526s 90 1.34x
💻 Local Express 2.340s (-7.1% 🟢) 3.009s (~) 0.669s 30 6.54x
💻 Local Next.js (Turbopack) 2.574s 3.008s 0.434s 30 7.19x
💻 Local Nitro 2.669s (+5.8% 🔺) 3.181s (+4.6%) 0.512s 29 7.46x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 2.558s (-4.6%) 4.092s (-9.8% 🟢) 1.535s 23 1.00x
▲ Vercel Next.js (Turbopack) 79.483s (+2572.9% 🔺) 81.006s (+1645.5% 🔺) 1.523s 4 31.08x
▲ Vercel Express ⚠️ missing - - - -

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 0.566s (-27.2% 🟢) 1.006s (~) 0.441s 120 1.00x
🐘 Postgres Next.js (Turbopack) 0.759s 1.014s 0.256s 119 1.34x
🐘 Postgres Express 0.775s (-3.9%) 1.007s (~) 0.232s 120 1.37x
💻 Local Express 10.403s (-7.9% 🟢) 11.030s (-6.9% 🟢) 0.627s 11 18.39x
💻 Local Next.js (Turbopack) 10.607s 11.209s 0.602s 11 18.75x
💻 Local Nitro 11.197s (+2.0%) 11.847s (+1.6%) 0.651s 11 19.79x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Next.js (Turbopack) 5.644s (-11.6% 🟢) 7.567s (-5.7% 🟢) 1.923s 16 1.00x
▲ Vercel Nitro 6.592s (~) 8.092s (-1.0%) 1.500s 15 1.17x
▲ Vercel Express ⚠️ missing - - - -

🔍 Observability: Next.js (Turbopack) | Nitro

Stream Benchmarks (includes TTFB metrics)
workflow with stream

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 0.160s (-21.0% 🟢) 0.999s (~) 0.001s (-7.7% 🟢) 1.011s (~) 0.851s 10 1.00x
💻 Local Next.js (Turbopack) 0.170s 1.003s 0.012s 1.018s 0.848s 10 1.06x
🐘 Postgres Express 0.191s (-12.3% 🟢) 0.996s (~) 0.002s (+23.1% 🔺) 1.010s (~) 0.819s 10 1.19x
🐘 Postgres Next.js (Turbopack) 0.191s 1.000s 0.002s 1.010s 0.819s 10 1.19x
💻 Local Express 0.207s (-1.3%) 1.004s (~) 0.010s (-15.4% 🟢) 1.016s (~) 0.809s 10 1.29x
💻 Local Nitro 0.209s (-1.4%) 1.004s (~) 0.012s (+3.4%) 1.018s (~) 0.809s 10 1.30x

▲ Production (Vercel)

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 1.862s (+12.8% 🔺) 3.059s (-1.6%) 0.513s (+20.9% 🔺) 3.944s (~) 2.082s 10 1.00x
▲ Vercel Next.js (Turbopack) 2.556s (+55.6% 🔺) 3.825s (+32.0% 🔺) 0.685s (+4.8%) 4.901s (+21.1% 🔺) 2.345s 10 1.37x
▲ Vercel Express ⚠️ missing - - - - -

🔍 Observability: Nitro | Next.js (Turbopack)

stream pipeline with 5 transform steps (1MB)

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 0.514s (-17.0% 🟢) 1.007s (~) 0.004s (+31.4% 🔺) 1.023s (~) 0.509s 59 1.00x
🐘 Postgres Next.js (Turbopack) 0.613s 1.009s 0.004s 1.021s 0.408s 59 1.19x
🐘 Postgres Express 0.617s (+1.7%) 1.005s (~) 0.004s (~) 1.022s (~) 0.406s 59 1.20x
💻 Local Next.js (Turbopack) 0.752s 1.011s 0.010s 1.116s 0.364s 54 1.46x
💻 Local Nitro 0.814s (+14.0% 🔺) 1.012s (~) 0.009s (+3.0%) 1.115s (+9.1% 🔺) 0.301s 54 1.58x
💻 Local Express 0.946s (+0.5%) 1.012s (~) 0.008s (-8.4% 🟢) 1.226s (~) 0.280s 49 1.84x

▲ Production (Vercel)

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 4.059s (+0.8%) 5.082s (-5.8% 🟢) 0.214s (-41.9% 🟢) 5.749s (-6.6% 🟢) 1.691s 11 1.00x
▲ Vercel Next.js (Turbopack) 4.269s (-8.0% 🟢) 5.581s (-12.0% 🟢) 0.217s (-12.3% 🟢) 6.399s (-9.9% 🟢) 2.129s 10 1.05x
▲ Vercel Express ⚠️ missing - - - - -

🔍 Observability: Nitro | Next.js (Turbopack)

10 parallel streams (1MB each)

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 0.902s (-5.0% 🟢) 1.071s (-10.4% 🟢) 0.000s (+167.9% 🔺) 1.079s (-10.7% 🟢) 0.176s 56 1.00x
🐘 Postgres Next.js (Turbopack) 0.942s 1.155s 0.000s 1.162s 0.220s 52 1.04x
🐘 Postgres Express 0.968s (+1.6%) 1.197s (+2.4%) 0.000s (-100.0% 🟢) 1.225s (+3.4%) 0.257s 49 1.07x
💻 Local Express 1.187s (-17.2% 🟢) 2.018s (~) 0.000s (+40.0% 🔺) 2.020s (-8.2% 🟢) 0.834s 30 1.32x
💻 Local Nitro 1.221s (-1.9%) 2.020s (~) 0.000s (~) 2.022s (~) 0.801s 30 1.35x
💻 Local Next.js (Turbopack) 1.251s 2.020s 0.000s 2.022s 0.771s 30 1.39x

▲ Production (Vercel)

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 2.512s (-2.6%) 3.148s (-16.0% 🟢) 0.000s (+Infinity% 🔺) 3.607s (-13.6% 🟢) 1.094s 17 1.00x
▲ Vercel Next.js (Turbopack) 3.502s (+9.3% 🔺) 4.931s (+5.7% 🔺) 0.000s (+100.0% 🔺) 5.406s (+5.9% 🔺) 1.904s 12 1.39x
▲ Vercel Express ⚠️ missing - - - - -

🔍 Observability: Nitro | Next.js (Turbopack)

fan-out fan-in 10 streams (1MB each)

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 1.695s (-4.3%) 2.105s (~) 0.000s (+Infinity% 🔺) 2.112s (-1.4%) 0.417s 29 1.00x
🐘 Postgres Express 1.797s (+1.7%) 2.143s (+3.4%) 0.000s (-48.2% 🟢) 2.176s (+3.2%) 0.379s 28 1.06x
🐘 Postgres Next.js (Turbopack) 1.842s 2.107s 0.000s 2.121s 0.279s 29 1.09x
💻 Local Express 3.296s (-8.4% 🟢) 4.032s (-1.6%) 0.000s (+16.7% 🔺) 4.034s (-1.6%) 0.738s 15 1.94x
💻 Local Nitro 3.731s (-5.6% 🟢) 3.960s (-5.2% 🟢) 0.001s (-30.0% 🟢) 4.320s (-4.8%) 0.589s 14 2.20x
💻 Local Next.js (Turbopack) 3.731s 4.164s 0.000s 4.168s 0.436s 15 2.20x

▲ Production (Vercel)

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 3.550s (-2.4%) 4.629s (-3.7%) 0.000s (-100.0% 🟢) 4.964s (-5.6% 🟢) 1.413s 13 1.00x
▲ Vercel Next.js (Turbopack) 4.111s (+1.6%) 5.416s (-5.2% 🟢) 0.001s (-88.3% 🟢) 5.964s (-3.0%) 1.852s 11 1.16x
▲ Vercel Express ⚠️ missing - - - - -

🔍 Observability: Nitro | Next.js (Turbopack)

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World 🥇 Fastest Framework Wins
💻 Local Next.js (Turbopack) 12/21
🐘 Postgres Nitro 21/21
▲ Vercel Nitro 17/21
Fastest World by Framework

Winner determined by most benchmark wins

Framework 🥇 Fastest World Wins
Express 🐘 Postgres 19/21
Next.js (Turbopack) 🐘 Postgres 15/21
Nitro 🐘 Postgres 19/21
Column Definitions
  • Workflow Time: Runtime reported by workflow (completedAt - createdAt) - primary metric
  • TTFB: Time to First Byte - time from workflow start until first stream byte received (stream benchmarks only)
  • Slurp: Time from first byte to complete stream consumption (stream benchmarks only)
  • Wall Time: Total testbench time (trigger workflow + poll for result)
  • Overhead: Testbench overhead (Wall Time - Workflow Time)
  • Samples: Number of benchmark iterations run
  • vs Fastest: How much slower compared to the fastest configuration for this benchmark

Worlds:

  • 💻 Local: In-memory filesystem world (local development)
  • 🐘 Postgres: PostgreSQL database world (local development)
  • ▲ Vercel: Vercel production/preview deployment
  • 🌐 Turso: Community world (local development)
  • 🌐 MongoDB: Community world (local development)
  • 🌐 Redis: Community world (local development)
  • 🌐 Jazz: Community world (local development)

📋 View full workflow run


Some benchmark jobs failed:

  • Local: success
  • Postgres: success
  • Vercel: failure

Check the workflow run for details.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 6, 2026

🧪 E2E Test Results

Some tests failed

Summary

Passed Failed Skipped Total
✅ ▲ Vercel Production 879 0 67 946
✅ 💻 Local Development 854 0 178 1032
✅ 📦 Local Production 854 0 178 1032
✅ 🐘 Local Postgres 854 0 178 1032
✅ 🪟 Windows 78 0 8 86
❌ 🌍 Community Worlds 16 60 8 84
✅ 📋 Other 216 0 42 258
Total 3751 60 659 4470

❌ Failed Tests

🌍 Community Worlds (60 failed)

mongodb-dev (1 failed):

  • dev e2e should rebuild on imported step dependency change

redis-dev (1 failed):

  • dev e2e should rebuild on imported step dependency change

turso-dev (1 failed):

  • dev e2e should rebuild on imported step dependency change

turso (57 failed):

  • addTenWorkflow | wrun_01KNJKXFVQDGBZ9MRECZD8HQ85
  • addTenWorkflow | wrun_01KNJKXFVQDGBZ9MRECZD8HQ85
  • wellKnownAgentWorkflow (.well-known/agent) | wrun_01KNJKYPXPGMS585793FKQ7D0K
  • should work with react rendering in step
  • promiseAllWorkflow | wrun_01KNJKXPWT86RCADHQT72MC5FG
  • promiseRaceWorkflow | wrun_01KNJKXVEF3FXD2PMWFMEYWD49
  • promiseAnyWorkflow | wrun_01KNJKXXR3TY4F4FDCJRHYT8ZA
  • importedStepOnlyWorkflow | wrun_01KNJKZ2ESV51C675F7F7QGZAK
  • hookWorkflow | wrun_01KNJKYAX87G6PQ7KT2S1T08C2
  • hookWorkflow is not resumable via public webhook endpoint | wrun_01KNJKYP8XGR9ND4RBJSH8GDT0
  • webhookWorkflow | wrun_01KNJKYZEMY6YWAG9CMR5QXGKK
  • sleepingWorkflow | wrun_01KNJKZ4NB17QSQ79JC28Q3WJK
  • parallelSleepWorkflow | wrun_01KNJKZHBYW7AQ2DCJSR54YBVA
  • nullByteWorkflow | wrun_01KNJKZP1PH231VNJYNP7V9TP1
  • workflowAndStepMetadataWorkflow | wrun_01KNJKZR9EBYT17Q5YYH72B8DY
  • fetchWorkflow | wrun_01KNJM2PZHEM9WXYHHPQP3PBTD
  • promiseRaceStressTestWorkflow | wrun_01KNJM2TH31P4RG63HCDR4SAKN
  • error handling error propagation workflow errors nested function calls preserve message and stack trace
  • error handling error propagation workflow errors cross-file imports preserve message and stack trace
  • error handling error propagation step errors basic step error preserves message and stack trace
  • error handling error propagation step errors cross-file step error preserves message and function names in stack
  • error handling retry behavior regular Error retries until success
  • error handling retry behavior FatalError fails immediately without retries
  • error handling retry behavior RetryableError respects custom retryAfter delay
  • error handling retry behavior maxRetries=0 disables retries
  • error handling catchability FatalError can be caught and detected with FatalError.is()
  • error handling not registered WorkflowNotRegisteredError fails the run when workflow does not exist
  • error handling not registered StepNotRegisteredError fails the step but workflow can catch it
  • error handling not registered StepNotRegisteredError fails the run when not caught in workflow
  • hookCleanupTestWorkflow - hook token reuse after workflow completion | wrun_01KNJM69XBX4RVSSFBEP0VZ5YG
  • concurrent hook token conflict - two workflows cannot use the same hook token simultaneously | wrun_01KNJM6YAH1CF0GGC97CDG957Q
  • hookDisposeTestWorkflow - hook token reuse after explicit disposal while workflow still running | wrun_01KNJM7KXPB2VJ7WANP0QJ0552
  • stepFunctionPassingWorkflow - step function references can be passed as arguments (without closure vars) | wrun_01KNJM880BE52XEHRKQDKHTTWQ
  • stepFunctionWithClosureWorkflow - step function with closure variables passed as argument | wrun_01KNJM8H1BHGYKHRCY92ETHC98
  • closureVariableWorkflow - nested step functions with closure variables | wrun_01KNJM8PKSW15NDGKF8KYBT8CF
  • spawnWorkflowFromStepWorkflow - spawning a child workflow using start() inside a step | wrun_01KNJM8RQFYGCP02EXE6V5S8H7
  • health check (queue-based) - workflow and step endpoints respond to health check messages
  • pathsAliasWorkflow - TypeScript path aliases resolve correctly | wrun_01KNJM973WWR14D0J8096BRJXD
  • Calculator.calculate - static workflow method using static step methods from another class | wrun_01KNJM9D1KXXBN60CPCK7CJABX
  • AllInOneService.processNumber - static workflow method using sibling static step methods | wrun_01KNJM9KPK5T6MMAKSSQCAH6HY
  • ChainableService.processWithThis - static step methods using this to reference the class | wrun_01KNJM9TH4VERGN9X77WF2NFF0
  • thisSerializationWorkflow - step function invoked with .call() and .apply() | wrun_01KNJMA16NX5QEYRFF9F6273P0
  • customSerializationWorkflow - custom class serialization with WORKFLOW_SERIALIZE/WORKFLOW_DESERIALIZE | wrun_01KNJMA848WXR2SNEY15FCMMYB
  • instanceMethodStepWorkflow - instance methods with "use step" directive | wrun_01KNJMAG7A63QEJAG9BNZ15308
  • crossContextSerdeWorkflow - classes defined in step code are deserializable in workflow context | wrun_01KNJMAVWX6GDHGY6K1X9W2F8F
  • stepFunctionAsStartArgWorkflow - step function reference passed as start() argument | wrun_01KNJMB4Y52B9GA7KJG9RPM98P
  • cancelRun - cancelling a running workflow | wrun_01KNJMBBP7S818K0WPQT14C0R5
  • cancelRun via CLI - cancelling a running workflow | wrun_01KNJMBMZWB0NX8A8G4MMWQ8CY
  • pages router addTenWorkflow via pages router
  • pages router promiseAllWorkflow via pages router
  • pages router sleepingWorkflow via pages router
  • hookWithSleepWorkflow - hook payloads delivered correctly with concurrent sleep | wrun_01KNJMC1437XH7M6X9Q0DCC1XP
  • sleepInLoopWorkflow - sleep inside loop with steps actually delays each iteration | wrun_01KNJMCNNDYAJQ2G6J0BM6QS5G
  • sleepWithSequentialStepsWorkflow - sequential steps work with concurrent sleep (control) | wrun_01KNJMD19FKZCAT2TYQQN5FVD0
  • importMetaUrlWorkflow - import.meta.url is available in step bundles | wrun_01KNJMD82NAFMCA1PWKYP983TD
  • metadataFromHelperWorkflow - getWorkflowMetadata/getStepMetadata work from module-level helper (#1577) | wrun_01KNJMDA7JMEBFPQCDXF4Q5Q27
  • resilient start: addTenWorkflow completes when run_created returns 500 | wrun_01KNJMDCFCCH8GR636VNQT576F

Details by Category

✅ ▲ Vercel Production
App Passed Failed Skipped
✅ astro 79 0 7
✅ example 79 0 7
✅ express 79 0 7
✅ fastify 79 0 7
✅ hono 79 0 7
✅ nextjs-turbopack 84 0 2
✅ nextjs-webpack 84 0 2
✅ nitro 79 0 7
✅ nuxt 79 0 7
✅ sveltekit 79 0 7
✅ vite 79 0 7
✅ 💻 Local Development
App Passed Failed Skipped
✅ astro-stable 72 0 14
✅ express-stable 72 0 14
✅ fastify-stable 72 0 14
✅ hono-stable 72 0 14
✅ nextjs-turbopack-canary 61 0 25
✅ nextjs-turbopack-stable 78 0 8
✅ nextjs-webpack-canary 61 0 25
✅ nextjs-webpack-stable 78 0 8
✅ nitro-stable 72 0 14
✅ nuxt-stable 72 0 14
✅ sveltekit-stable 72 0 14
✅ vite-stable 72 0 14
✅ 📦 Local Production
App Passed Failed Skipped
✅ astro-stable 72 0 14
✅ express-stable 72 0 14
✅ fastify-stable 72 0 14
✅ hono-stable 72 0 14
✅ nextjs-turbopack-canary 61 0 25
✅ nextjs-turbopack-stable 78 0 8
✅ nextjs-webpack-canary 61 0 25
✅ nextjs-webpack-stable 78 0 8
✅ nitro-stable 72 0 14
✅ nuxt-stable 72 0 14
✅ sveltekit-stable 72 0 14
✅ vite-stable 72 0 14
✅ 🐘 Local Postgres
App Passed Failed Skipped
✅ astro-stable 72 0 14
✅ express-stable 72 0 14
✅ fastify-stable 72 0 14
✅ hono-stable 72 0 14
✅ nextjs-turbopack-canary 61 0 25
✅ nextjs-turbopack-stable 78 0 8
✅ nextjs-webpack-canary 61 0 25
✅ nextjs-webpack-stable 78 0 8
✅ nitro-stable 72 0 14
✅ nuxt-stable 72 0 14
✅ sveltekit-stable 72 0 14
✅ vite-stable 72 0 14
✅ 🪟 Windows
App Passed Failed Skipped
✅ nextjs-turbopack 78 0 8
❌ 🌍 Community Worlds
App Passed Failed Skipped
❌ mongodb-dev 4 1 0
❌ redis-dev 4 1 0
❌ turso-dev 4 1 0
❌ turso 4 57 8
✅ 📋 Other
App Passed Failed Skipped
✅ e2e-local-dev-nest-stable 72 0 14
✅ e2e-local-postgres-nest-stable 72 0 14
✅ e2e-local-prod-nest-stable 72 0 14

📋 View full workflow run

Replace SPEC_VERSION_CURRENT comparisons with named constants
(SPEC_VERSION_SUPPORTS_CBOR_QUEUE_TRANSPORT, SPEC_VERSION_SUPPORTS_EVENT_SOURCING)
so future SPEC_VERSION_CURRENT bumps don't shift existing feature gates.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Peter Wielander <mittgfu@gmail.com>
vercel bot and others added 2 commits April 6, 2026 23:48
…d, causing `toEqual` to fail due to exact equality mismatch.

This commit fixes the issue reported at packages/core/e2e/e2e.test.ts:1550

**Bug explanation:**

The `withHealthCheck` handler in `packages/core/src/runtime/helpers.ts` returns a JSON response with three fields: `{ healthy: true, endpoint: url.pathname, specVersion: SPEC_VERSION_CURRENT }`. However, the HTTP-based health check e2e test in `packages/core/e2e/e2e.test.ts` (lines 1549-1553 and 1567-1571) uses `toEqual` to assert against objects that only have two fields: `{ healthy: true, specVersion: SPEC_VERSION_CURRENT }`, missing the `endpoint` field.

In Vitest, `toEqual` performs exact deep equality checking. When the actual response object contains `{ healthy: true, endpoint: "/.well-known/workflow/v1/flow", specVersion: "..." }` but the expected object only has `{ healthy: true, specVersion: "..." }`, the assertion fails because the actual object has an extra `endpoint` property not present in the expected object.

This affects both the flow endpoint health check assertion and the step endpoint health check assertion.

**Fix explanation:**

Added the missing `endpoint` field to both `toEqual` assertions:
- For the flow endpoint: `endpoint: '/.well-known/workflow/v1/flow'` (the pathname portion of `/.well-known/workflow/v1/flow?__health`)
- For the step endpoint: `endpoint: '/.well-known/workflow/v1/step'` (the pathname portion of `/.well-known/workflow/v1/step?__health`)

These values match what `url.pathname` returns in the handler (the `?__health` query parameter is not part of the pathname). This makes the test assertions exactly match the actual response structure from the handler.

Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>
Co-authored-by: VaguelySerious <mittgfu@gmail.com>
Signed-off-by: Peter Wielander <mittgfu@gmail.com>
VaguelySerious and others added 3 commits April 6, 2026 16:55
Signed-off-by: Peter Wielander <mittgfu@gmail.com>
Update HealthCheckResult to include specVersion and parse it from the
stream response, so observability tools can query deployment capabilities
through both the HTTP and queue-based health check paths.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use Record<string, unknown> instead of narrower casts that tsc
rejects as insufficient overlap.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Member

@TooTallNate TooTallNate left a comment

Choose a reason for hiding this comment

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

This is a well-structured backwards-compatibility fix for the CBOR transport introduced in #1537. The approach is sound: gate send-side transport on the target run's specVersion, and use a dual CBOR-first/JSON-fallback transport on the receive side.

What I verified:

spec-version.ts:

  • isLegacySpecVersion now correctly checks v <= SPEC_VERSION_LEGACY (1) instead of v < SPEC_VERSION_CURRENT. This is the right fix — without it, bumping SPEC_VERSION_CURRENT to 3 would cause specVersion 2 runs to be incorrectly treated as v1-compat (direct entity mutation instead of event-sourced). All 6 callsites of isLegacySpecVersion are compatible with this semantic change.
  • Named constants (SPEC_VERSION_SUPPORTS_EVENT_SOURCING, SPEC_VERSION_SUPPORTS_CBOR_QUEUE_TRANSPORT) clearly document which features each version introduced.

world-vercel/queue.ts:

  • Send path creates a per-call QueueClient with the right transport based on opts.specVersion. undefined defaults to SPEC_VERSION_CURRENT, which is correct — callers without specVersion are new runs.
  • DualTransport.deserialize() tries CBOR decode first, catches on failure, falls back to JSON. Correct — valid CBOR will never accidentally parse as JSON since CBOR's first byte (typically 0xa or 0xb map/array major types) would be invalid JSON.
  • The handler uses DualTransport (receives both formats); send-side uses CborTransport for new runs (handler re-enqueues target the same new deployment). Correct.

Queue callsite coverage:

  • start.ts — passes specVersion via opts. Also gates runInput on >= SPEC_VERSION_SUPPORTS_CBOR_QUEUE_TRANSPORT since Uint8Array doesn't survive JSON. Correct.
  • reenqueueRun (runs.ts:120) — passes run.specVersion ?? SPEC_VERSION_LEGACY. Correct.
  • wakeUpRun (runs.ts:213) — same pattern. Correct.
  • resumeHook (resume-hook.ts:195) — passes workflowRun.specVersion ?? SPEC_VERSION_LEGACY. Correct.
  • healthCheck (helpers.ts:228) — no opts, defaults to SPEC_VERSION_CURRENT. Correct — health checks target the current deployment.

Health check response format:

  • Changed from text/plain to application/json with { healthy, endpoint, specVersion }. This is a minor breaking change for anything parsing the response, but the e2e tests are updated and the JSON format is strictly more useful (enables version detection from the dashboard).

Changeset: patch for all three packages. Correct.

LGTM.

const buffer = Buffer.concat(chunks);
try {
return decode(buffer);
} catch {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Non-blocking observation: The CBOR-first, JSON-fallback strategy is safe because CBOR's binary encoding always starts with a type-length byte (e.g. 0xA2 for a 2-entry map) that is never valid as the first byte of a JSON string (which must start with {, [, ", digit, t, f, n, or whitespace in ASCII). So there's no ambiguity — cbor-x.decode() will throw on JSON input, and JSON.parse() will succeed.

The only theoretical concern is a corrupted message that happens to be valid CBOR but not valid JSON — but that's a data integrity issue unrelated to the dual transport.

* @param v - The spec version number, or undefined/null for legacy runs
* @returns true if the run is a legacy run
*/
export function isLegacySpecVersion(v: number | undefined | null): boolean {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Non-blocking: This is the key correctness fix. The old v < SPEC_VERSION_CURRENT would have made specVersion 2 runs legacy after the bump to 3, breaking event-sourced replay for all existing v2 runs. v <= SPEC_VERSION_LEGACY is the right semantic — only version 1 is truly legacy (direct entity mutation).

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.

2 participants