Skip to content

[docs] v5 GA content: What's new, World upgrade guide, migration skills - #3100

Merged
VaguelySerious merged 34 commits into
mainfrom
peter/docs-5-ga
Aug 21, 2026
Merged

[docs] v5 GA content: What's new, World upgrade guide, migration skills#3100
VaguelySerious merged 34 commits into
mainfrom
peter/docs-5-ga

Conversation

@VaguelySerious

@VaguelySerious VaguelySerious commented Jul 24, 2026

Copy link
Copy Markdown
Member

Adds the two new v5 pages, the two migration skills, and a sidebar drill-in change, and brings the v5 content tree up to date with main. v5 stays the pre-release version: making it the default is #3714, stacked on this branch.

Companion to #3090 (exit changeset pre mode) and #3091 (pin 4.x publishes to previous).

What is in each PR

Splitting these apart means the routing move can be read on its own rather than as the smaller half of a content diff.

This PR #3714
Adds whats-new and worlds/upgrading-to-v5 Serves content/docs/v5 and content/worlds/v5 unprefixed, moves v4 under /v4
Leaves whats-new out of the sidebar Puts whats-new first in the v5 sidebar
Content, skills, docs-typecheck module map Version tables, redirects, banner, worlds version plumbing, link-lint spaces, smoke checks

Three pieces of machinery here belong to the new pages rather than to the flip, so they are authored against the pre-release layout and move with it in #3714:

  • worlds-guide-page.tsx generalizes building-a-world-page.tsx from one hard-coded slug to a slug parameter, which is what lets upgrading-to-v5 reuse the bespoke worlds-tree layout. Both routes now pass their own slug.
  • The upgrading-to-v5 route sits at app/[lang]/v5/worlds/, where v5 world pages are served from until the flip.
  • Two cross-version links are dropped, and [docs] Make v5 the default documentation version #3714 restores them. While v4 is the unprefixed version, a v5 page cannot link to a v4 page at all: rewriteHrefForVersion pulls an unprefixed href on a v5 page into the /v5 view, so there is no href that reaches v4. The prose points at the version picker instead.

Sidebar drill-in on the docs home

geistdocs' sidebar has two panes: the top-level menu, and a section pane it drills into for the first root folder containing the active page. /docs permanently redirects to /docs/getting-started, so every bare link to the documentation landed on a page that immediately replaced the top-level menu with the framework list, hiding the rest of the docs.

collapseDocsHomeSection empties that section's children on its own landing page, which keeps the root menu visible with the row highlighted — findActiveRootSection only matches folders that have children. Nothing is lost, since the page body is a card grid of exactly those children, and every other page in the section still drills in normally.

Knowing the active page server-side is what this needs, so both docs layouts move inside their [[...slug]] segment to get params.slug. The per-navigation layout remount that causes is fine in practice — verified in a browser that geistdocs re-scrolls the active sidebar item into view after each navigation.

What's new page and migration skill

  • /v5/docs/whats-new: new top-level page. It is deliberately not in the v5 sidebar here — [docs] Make v5 the default documentation version #3714 adds it, first in the tree, along with the version flip. Until then it is reachable by URL. Leads with faster/cheaper runs (single-pass invocations, retained VM across inline steps, one-round-trip hook resume, skipped persistence round trips, payload compression — up to 80% lower compute and 70% lower storage cost on Vercel Workflows), then multi-region, in-flight cancellation, attributes GA, richer serialization, the redesigned trace viewer, and hook tokens outliving their run. A <CopyPrompt> after the install snippet gives a paste-ready agent prompt for the upgrade. The page is now scoped to application code only; World-spec changes moved to their own page (see below).

  • /worlds/upgrading-to-v5: new page carrying the World-spec delta — the interface changes, the contract changes that compile cleanly and then behave incorrectly, the new optional surface, slot-numbered event ID allocation, and the World-upgrade <CopyPrompt>. It lives in the worlds tree, off the main docs sidebar, reachable from a bottom section on the What's new page, a callout on Building a World, and the migration skill.

  • Breaking-change tables (application code vs. custom Worlds/build integrations) were built from the package diff against stable, then re-swept twice: once against the 5.0.0 draft changelog and once against post-branch main — picking up the late-landing GA breakers (createLocalWorld/createVercelWorld removal (Remove obsolete world factory aliases #3112), deterministic-sandbox hardening (feat(core): deterministic sandbox hardening #3045), step-queue-topic retirement). A third sweep was empirical rather than diff-based: migrating all 17 apps in vercel/workflow-examples from 4.3.1 to a 5.0.0-beta tarball (workflow-examples#51) and adding whatever actually broke. Two rows came out of it, both application-facing:

    • getWorld() / getWorldHandlers() / createWorld() are async. This broke the instrumentation.ts bootstrap in two examples and one Vercel deployment. It was already in the World page's table, but that page opens by telling apps on the first-party Worlds that they need nothing from it, and the app-facing table had no getWorld mention at all. Worth being explicit about the silent case: TypeScript catches it, plain JavaScript does not, and .start on the promise is undefined so ?.() short-circuits and the worker never starts.
    • The standalone bundles were renamed and the step route removed. flow.js/webhook.jsflow.mjs/webhook.mjs, named exports only (a default import resolves to undefined), and step.js__step_registrations.mjs, which flow.mjs imports rather than being routed. This breaks any app that wires workflow build output into its own server, which is what the custom-adapter example does. how-it-works/framework-integrations already had all of it correct, so this is a pointer from the summary table plus the filenames named in the build-integration row.
  • skills/migrating-workflow-v4-to-v5 (v0.2.7): mechanical rewrite rules, behavior changes the agent must report rather than silently "fix", World contract changes, a fixed output shape, and a fail-the-migration checklist.

  • Env vars documented: WORKFLOW_MAX_EVENTS_OVERRIDE (Runtime Tuning › Limits), WORKFLOW_MAX_EVENTS (Worlds › Local), WORKFLOW_DISABLE_ANALYTICS_READS (Worlds › Vercel).

Refresh for the last two weeks of main

Merged origin/main and swept the shipped changesets since the branch was last updated, then documented what was missing:

Both PRs that were pending at the time of the earlier review have merged and are now in this branch via origin/main:

  • [core] Gate the unconsumed-event check on delivery idleness #3439: its delivery-idleness gate needs no doc change beyond the parked-delivery wording World-side incrementing event ID (specVersion 6) #3389 added to the corrupted-event-log page.
  • World-side incrementing event ID (specVersion 6) #3389 (slot-numbered event IDs, spec version 6): merged, so its own docs (building-a-world, event-sourcing, corrupted-event-log, runtime-tuning, postgres) are in-tree and this branch links into their anchors instead of describing them in prose. Reconciled in the merge:
    • building-a-world: capability listing is the union (hookRetention, slotEventIds, preconditionGuard, maxConcurrency); the specVersion paragraph no longer claims an exact match, since the runtime now accepts [SPEC_VERSION_CURRENT, SPEC_VERSION_MAX_SUPPORTED]; the fail-closed capability prose keeps its detail and gains slotEventIds as required rather than optional.
    • upgrading-to-v5: the duplicated allocation rules are cut down to the three binding rules (uniqueness, density, bump-and-report) and link to Event ID Allocation and Event IDs for the full text, keeping only what is upgrade-specific: eventCount superseding the stateUpdatedAt/stateEventCount/stateCursor triple, and existing ULID-numbered runs replaying unchanged.
    • postgres: version-pairing callout keeps the GA wording and adopts World-side incrementing event ID (specVersion 6) #3389's accurate error description (the error names the supported spec versions and the one the World declares).
    • whats-new gains two user-facing entries: a breaking-change row for event IDs being slot numbers (unique per run, no decodable timestamp) and an "Also new" entry for an event arriving mid-replay no longer failing the run with CORRUPTED_EVENT_LOG.
    • Migration skill (v0.2.7) gains both: required slot allocation in the World section, and the event-ID shape as an app-code behavior change to report.
    • packages/docs-typecheck/src/type-checker.ts: module map is the union of @workflow/world-local (this branch) and @workflow/world-sim (sim-world - deterministic scenario testing for race conditions #3328).

World contract changes from #3519

#3519 removed two capability flags after this branch was written, and one of them invalidated advice the upgrade guide was giving. Caught up in 76ed7ffa9:

  • Slot-numbered event IDs are a requirement, not a capability. slotEventIds is gone from WorldCapabilities. The runtime reads a position out of every ID it loads and fails the run when it cannot, so there is no flag and no fallback path: a World whose IDs are not positions type-checks, starts runs, and fails on the first replay with Event id is not slot-numbered. building-a-world says so in the capabilities paragraph (naming what is deliberately not a capability) and opens Event ID Allocation with a warning callout; upgrading-to-v5 drops the "declare capabilities.slotEventIds" instruction and the parenthetical in the optional-surface table.

  • Existing ULID-numbered runs cannot be replayed by the new code. The guide said a World switching slots on "keeps replaying its existing ULID-numbered runs unchanged", which is no longer true and is the sentence here most likely to cost someone an incident. Replaced with a warning callout: drain those runs on 4.x before deploying a v5 World, or accept that in-flight ones fail. Platforms that pin a run to its creating deployment (Vercel) resolve it for free; a single deployment serving every run does not.

  • preconditionGuard is gone, and nothing replaced it. Allocating positions at the commit makes a reader's log a prefix rather than a prefix with a hole, replay is deterministic on a prefix, and a write reports the events it was pushed past — so a stale replay costs a merge, not a rejection. A World that implemented the guard can delete it. The section survives, renamed "Optional: Rejecting a Stale Write", and now leads with the fact that no World in the repo does this and a v5 World should not need to, scoped to the one case that still wants it: a store that allocates positions away from the commit.

  • New stated requirement: allocate the position at the commit. This was implicit and is what the prefix property rests on, so both pages say it, and it is what identifies the World that still has a use for a stale-write rejection.

  • Verification points at the conformance suite. @workflow/world-testing grew numbers events by position, which fails a World whose IDs do not decode to slots, whose run is not dense from 1, or whose IDs are not canonical. The upgrade guide's verification section names it, since it turns a first-replay failure into one line of test output.

  • The migration skill (v0.2.8) carried both stale claims too — it told agents to declare capabilities.slotEventIds and that existing ULID runs keep replaying. Both replaced, plus allocate-at-the-commit, the world-testing pointer, and a note that a World which implemented preconditionGuard can delete it.

That merge has since happened, so runtime-tuning.mdx now carries main's current text and the WORKFLOW_PRECONDITION_GUARD section is gone.

Spec version 6, and a second migration skill

Merged origin/main again (18 commits, including #3519) and added the changes from #3542, which is not merged yet but ships before GA.

/worlds/upgrading-to-v5 › Spec versions is rewritten. #3542 makes SPEC_VERSION_CURRENT the slot-identity version, which narrows the accepted range from [5, 6] to a single version: a World declaring 5 is now refused before its first run rather than failing partway through one, and world-local / world-postgres / world-sim stamp 6 to match the slot IDs they already allocate. The instruction is unchanged and now has a reason attached — declare SPEC_VERSION_CURRENT, not a literal, and not SPEC_VERSION_SUPPORTS_SLOT_IDENTITY, which is a literal by another name and is what world-vercel used to declare. The section that explained why world-local and world-postgres stay on 5 is gone, since they no longer do.

The guide does not link building-a-world#declaring-the-spec-version, the anchor #3542 adds, because the link lint checks anchors and that heading is not in this tree yet. Worth adding once #3542 lands.

skills/migrating-world-v4-to-v5 (v0.1.0) is a second migration skill, for the World rather than the application. The two share almost no surface, and the World half has a failure mode the application half does not: a port done from the type errors alone compiles, starts runs, and fails on the first replay. So the skill leads with event ID allocation and treats the signature changes as the small half.

  • Intake asks where the slot is computed and what settles a race for it, since an in-process MAX(slot) + 1 is the failure that survives light testing.
  • The four binding rules (uniqueness, density, bump-and-report, allocate-at-the-commit), with a worked SQL shape that computes the ID inside the insert and lets a unique key arbitrate, matching what @workflow/world-postgres does.
  • specVersion, the interface rewrites, the contract changes, the optional surface, and the drain-or-pin rollout warning.
  • Verification leads with wiring up @workflow/world-testing, and the fail-the-migration checklist is written against the allocation rules (a bumped write returns without the skipped events, the slot is handed out before the commit, and so on).
  • migrating-workflow-v4-to-v5 (v0.2.9) now hands the World work off to it instead of summarizing it, and /docs/whats-new's "If you maintain a World" section carries the install line.

Stale content the merge exposed, all from #3519 removing the guard:

  • /docs/whats-new had a breaking-change row saying event creation is guarded by default with WORKFLOW_PRECONDITION_GUARD=0 to opt out. The variable does not exist in v5, and the guard does exist on stable, so the row is now the removal rather than the feature.
  • The application skill (step 3) carried the same claim.
  • The upgrade guide's eventCount supersedes the stateUpdatedAt / stateEventCount / stateCursor triple paragraph pointed at a triple that no longer exists anywhere.

Merge resolutions (three conflicts, two files):

  • building-a-world: kept this branch's interface snippet and capabilities prose, which are a superset of main's (they carry specVersion, analytics, createRunId, describeRun, processExitTriggersQueueRedelivery and the fail-closed paragraph). Took main's "Optional: Rejecting a Stale Write" section wholesale, since it is the post-[core] Drop pre-slot event ID support and preconditionGuard capability #3519 version and drops the triple.
  • precondition-failed-error: took main's, same reason.

Full-tree staleness sweep

The whole v5 content tree (165 docs pages + worlds + cookbook) was audited against the actual v4→v5 delta, with every finding verified against SDK source. 30 files fixed in 44a53f0be:

  • Removed client SWC mode: code-transform.mdx restructured around the real v5 modes (step/workflow/detect) with a migration callout; framework-integrations.mdx and the NestJS guide now pass/describe mode: "step".
  • v4 execution model: passages describing one-step-per-queue-message ("enqueues the step for background execution", "the step function's HTTP request", "step execution requests") now describe inline execution with queue fallback — across foundations, how-it-works, tracing, and testing pages.
  • Building a World: the World interface listing gains the now-required specVersion, the v5 capabilities (preconditionGuard, maxConcurrency — with the fail-closed warning), analytics, createRunId (the multi-region hook), describeRun, experimentalSetAttributes, a required steps.get runId, and queue guidance for delaySeconds (a World that ignores it busy-loops every sleep()) and region.
  • Inverted/impossible claims: the precondition guard is default-on (not opt-in via =1); set-world/create-world no longer describe runtime WORKFLOW_TARGET_WORLD switching, which build-time World injection makes impossible.
  • Comparisons: child runs use start() directly from a workflow (six step-wrapped-start() rows across four pages), Temporal search attributes map to setAttributes(), multi-region is shipped.
  • Additions: compression-before-encryption in the encryption internals, AbortController/AbortSignal in workflow globals, region on start() (API reference + foundations), all eight run error codes including MAX_EVENTS_EXCEEDED, stream flush-window note.
  • Adjacent bugs: 14 samples awaited the synchronous createWebhook() (whose thenable handle resolves the received request), an unused import, a missing sidebar entry, and an undeclared class that failed snippet typecheck.

One deliberate soft-fix: the Python guide's "each step compiles into an isolated route" was made topology-neutral rather than corrected, pending confirmation of the Python SDK's route model.

Verification

  • pnpm test:docs: 972 documentation snippet typechecks pass (the snippet type-checker now covers @workflow/world-local, so the set-world example and the code-transform input samples are machine-checked instead of skipped).
  • bun ./scripts/lint.ts: 0 errored files, 0 errors — every link/anchor resolves in both URL spaces.
  • pnpm build: exit 0; key fixes confirmed in prerendered HTML (version markers, Detect Mode, error-code table, set-world example).
  • Docs smoke suite (including the new worlds-version and community-world guards) green against pnpm start; curl matrix over redirects and worlds routes (/worlds/vercel 200 v5-titled, /worlds/turso 200, /v4/worlds/turso single 307, /v5/* → unprefixed 308s, markdown + sitemap routes 200).
  • Re-verified after the refresh: bun ./scripts/lint.ts 0 errors, pnpm build exit 0 (/[lang]/worlds/upgrading-to-v5 present), docs smoke suite green (it includes an HTML-meta check for the new page), lint-rendered-links green against a local pnpm start, and the new page's routes checked by curl (/worlds/upgrading-to-v5 200, .md 200).
  • All Vercel toolbar review threads addressed and resolved; all code-review findings fixed.
  • Branch is merged with origin/main (factory-alias removal Remove obsolete world factory aliases #3112, sandbox hardening feat(core): deterministic sandbox hardening #3045, migration-guides redirects docs: redirect retired migration-guides URLs to comparisons #3127), so the GA-forward claims in the breaking-change tables are true in-tree and docs: redirect retired migration-guides URLs to comparisons #3127's redirect destinations pass this branch's lint.

Empty changeset — docs and skills only, no published package changes.

Docs Preview

Page v5
What's new in v5 /v5/docs/whats-new
Upgrading a World to v5 /v5/worlds/upgrading-to-v5
Building a World /v5/worlds/building-a-world
Getting started (sidebar drill-in) /v5/docs/getting-started

v5 goes stable, so the docs swap places: content/docs/v5 and
content/worlds/v5 now serve unprefixed (/docs, /worlds, /cookbook) and
v4 moves under a /v4 prefix. The versioned source config drives both
route trees, so the swap is a routePrefix + source re-binding plus the
app/[lang]/v5 -> app/[lang]/v4 tree rename.

- Switcher labels: "v5 (Latest)" / "v4 (Maintenance)".
- The v5 pre-release banner becomes an amber maintenance notice on v4
  pages, pointing at the current version.
- /v5/* redirects onto its unprefixed equivalent (bare /v5 needs its own
  rule, since the wildcard expands to an empty destination). v4 content
  keeps its /v5/... hrefs on purpose: render-time href rewriting would
  pull an unprefixed /docs link into the /v4 view.
- Version-switcher fallbacks are inverted: pages that exist only in v5
  now get /v4 fallbacks, and the two v4-only pages fall back to the
  root tree. /docs/configuration/* is a real page again.
- The link lint's two-space model swaps with the URL space: unprefixed
  is v5, /v4-prefixed is v4, and redirect destinations resolve against
  the HTTP space (they are matched before rendering).
- Content sweep: drop pre-release version gates and beta dist-tag
  references from v5 content, and de-prefix self-referential /v5 links.
@VaguelySerious
VaguelySerious requested review from a team and ijjk as code owners July 24, 2026 21:49
@changeset-bot

changeset-bot Bot commented Jul 24, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 58d0d68

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

This PR includes changesets to release 0 packages

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

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

vercel Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

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

Project Deployment Actions Updated (UTC)
example-nextjs-workflow-turbopack Ready Ready Preview, v0 Aug 21, 2026 7:02pm
example-nextjs-workflow-webpack Ready Ready Preview, v0 Aug 21, 2026 7:02pm
example-workflow Ready Ready Preview, v0 Aug 21, 2026 7:02pm
workbench-astro-workflow Ready Ready Preview, v0 Aug 21, 2026 7:02pm
workbench-express-workflow Ready Ready Preview, v0 Aug 21, 2026 7:02pm
workbench-fastify-workflow Ready Ready Preview, v0 Aug 21, 2026 7:02pm
workbench-hono-workflow Ready Ready Preview, v0 Aug 21, 2026 7:02pm
workbench-nestjs-workflow Ready Ready Preview, v0 Aug 21, 2026 7:02pm
workbench-nitro-workflow Ready Ready Preview, v0 Aug 21, 2026 7:02pm
workbench-nuxt-workflow Ready Ready Preview, v0 Aug 21, 2026 7:02pm
workbench-python-workflow Ready Ready Preview, v0 Aug 21, 2026 7:02pm
workbench-sveltekit-workflow Ready Ready Preview, v0 Aug 21, 2026 7:02pm
workbench-tanstack-start-workflow Ready Ready Preview, v0 Aug 21, 2026 7:02pm
workbench-vite-workflow Ready Ready Preview, v0 Aug 21, 2026 7:02pm
workflow-docs Ready Ready Preview, v0 Aug 21, 2026 7:02pm
workflow-swc-playground Ready Ready Preview, v0 Aug 21, 2026 7:02pm
workflow-tarballs Ready Ready Preview, v0 Aug 21, 2026 7:02pm
workflow-web Ready Ready Preview, v0 Aug 21, 2026 7:02pm

- New top-level `/docs/whats-new` page above Getting Started: highlights,
  verified breaking-change table, and a "Migrating from V4" section.
- New `skills/migrating-workflow-v4-to-v5` agent skill covering the async
  `getWorld()`, removed `runStep`/`stepEntrypoint`/private subpaths, the
  `world.streams.*` argument reorder, `steps.get(runId, ...)`, the
  `getConflict()` -> `Run` shape, and the Nest builder subpath.
- Landing on `/docs/getting-started` (where bare `/docs` links redirect) no
  longer drills the sidebar into the framework list; the root menu stays
  visible. Both docs layouts moved into their `[[...slug]]` segment so the
  active slug is available server-side.
@github-actions

github-actions Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

Some tests failed

❌ Failed E2E Tests

▲ Vercel Production (200 failed)

astro-node (3 failed):

  • hookCleanupTestWorkflow - hook token reuse after workflow completion | wrun_41M0JVE0RZ0GPRAS5S5AWPTN6W | 🔍 observability
  • concurrent hook token conflict - two workflows cannot use the same hook token simultaneously | wrun_41M0JVDJQD0GYKNBHAZRA1DNTG | 🔍 observability
  • hookGetConflictWorkflow - hook.getConflict() resolves with the conflicting run when token is already registered | wrun_41M0JVENWW0GQE9XR6606SQ603 | 🔍 observability

astro-quickjs (8 failed):

  • concurrent hook token conflict - two workflows cannot use the same hook token simultaneously | wrun_41M0JVDJQD0GYKNBHAZRA1DNTG | 🔍 observability
  • hookGetConflictWorkflow - hook.getConflict() resolves with the conflicting run when token is already registered | wrun_41M0JVENWW0GQE9XR6606SQ603 | 🔍 observability
  • setAttributes start: initial attributes are seeded on run creation
  • setAttributes start: reserved-prefix initial attributes are seeded with allowReservedAttributes
  • setAttributes setAttributesWorkflow: workflow-body calls append native attr_set events and merge correctly
  • setAttributes setAttributesInsideStepWorkflow: step-body calls append attributed native events
  • setAttributes fire-and-forget: void setAttributes lands without awaiting
  • setAttributes workflow throws after awaited setAttributes: attribute still persists on the failed run

example-node (7 failed):

  • hookCleanupTestWorkflow - hook token reuse after workflow completion | wrun_41M0JVE0RZ0GPRAS5S5AWPTN6W | 🔍 observability
  • hookGetConflictWorkflow - hook.getConflict() resolves with the conflicting run when token is already registered | wrun_41M0JVENWW0GQE9XR6606SQ603 | 🔍 observability
  • hookMinRetentionWorkflow - terminal Hook cannot resume and its token stays unavailable | wrun_41M0JVG5JD0GVPE1WDZJ3DP916 | 🔍 observability
  • setAttributes start: initial attributes are seeded on run creation
  • setAttributes start: reserved-prefix initial attributes are seeded with allowReservedAttributes
  • setAttributes setAttributesWorkflow: workflow-body calls append native attr_set events and merge correctly
  • setAttributes fire-and-forget: void setAttributes lands without awaiting

example-quickjs (8 failed):

  • hookCleanupTestWorkflow - hook token reuse after workflow completion | wrun_41M0JVE0RZ0GPRAS5S5AWPTN6W | 🔍 observability
  • hookGetConflictWorkflow - hook.getConflict() resolves with the conflicting run when token is already registered | wrun_41M0JVENWW0GQE9XR6606SQ603 | 🔍 observability
  • setAttributes start: initial attributes are seeded on run creation
  • setAttributes start: reserved-prefix initial attributes are seeded with allowReservedAttributes
  • setAttributes setAttributesWorkflow: workflow-body calls append native attr_set events and merge correctly
  • setAttributes setAttributesInsideStepWorkflow: step-body calls append attributed native events
  • setAttributes fire-and-forget: void setAttributes lands without awaiting
  • setAttributes workflow throws after awaited setAttributes: attribute still persists on the failed run

express-node (9 failed):

  • hookCleanupTestWorkflow - hook token reuse after workflow completion | wrun_41M0JVE0RZ0GPRAS5S5AWPTN6W | 🔍 observability
  • concurrent hook token conflict - two workflows cannot use the same hook token simultaneously | wrun_41M0JVDJQD0GYKNBHAZRA1DNTG | 🔍 observability
  • hookGetConflictWorkflow - hook.getConflict() resolves with the conflicting run when token is already registered | wrun_41M0JVENWW0GQE9XR6606SQ603 | 🔍 observability
  • hookMinRetentionWorkflow - terminal Hook cannot resume and its token stays unavailable | wrun_41M0JVG5JD0GVPE1WDZJ3DP916 | 🔍 observability
  • setAttributes start: initial attributes are seeded on run creation
  • setAttributes start: reserved-prefix initial attributes are seeded with allowReservedAttributes
  • setAttributes setAttributesWorkflow: workflow-body calls append native attr_set events and merge correctly
  • setAttributes fire-and-forget: void setAttributes lands without awaiting
  • setAttributes Promise.all of disjoint-key writes: every key lands

express-quickjs (8 failed):

  • hookCleanupTestWorkflow - hook token reuse after workflow completion | wrun_41M0JVE0RZ0GPRAS5S5AWPTN6W | 🔍 observability
  • concurrent hook token conflict - two workflows cannot use the same hook token simultaneously | wrun_41M0JVDJQD0GYKNBHAZRA1DNTG | 🔍 observability
  • hookGetConflictWorkflow - hook.getConflict() resolves with the conflicting run when token is already registered | wrun_41M0JVENWW0GQE9XR6606SQ603 | 🔍 observability
  • setAttributes start: initial attributes are seeded on run creation
  • setAttributes start: reserved-prefix initial attributes are seeded with allowReservedAttributes
  • setAttributes setAttributesWorkflow: workflow-body calls append native attr_set events and merge correctly
  • setAttributes setAttributesInsideStepWorkflow: step-body calls append attributed native events
  • setAttributes fire-and-forget: void setAttributes lands without awaiting

fastify-node (9 failed):

  • hookCleanupTestWorkflow - hook token reuse after workflow completion | wrun_41M0JVE0RZ0GPRAS5S5AWPTN6W | 🔍 observability
  • concurrent hook token conflict - two workflows cannot use the same hook token simultaneously | wrun_41M0JVDJQD0GYKNBHAZRA1DNTG | 🔍 observability
  • hookGetConflictWorkflow - hook.getConflict() resolves with the conflicting run when token is already registered | wrun_41M0JVENWW0GQE9XR6606SQ603 | 🔍 observability
  • hookMinRetentionWorkflow - terminal Hook cannot resume and its token stays unavailable | wrun_41M0JVG5JD0GVPE1WDZJ3DP916 | 🔍 observability
  • setAttributes start: initial attributes are seeded on run creation
  • setAttributes start: reserved-prefix initial attributes are seeded with allowReservedAttributes
  • setAttributes setAttributesWorkflow: workflow-body calls append native attr_set events and merge correctly
  • setAttributes fire-and-forget: void setAttributes lands without awaiting
  • setAttributes Promise.all of disjoint-key writes: every key lands

fastify-quickjs (10 failed):

  • hookCleanupTestWorkflow - hook token reuse after workflow completion | wrun_41M0JVE0RZ0GPRAS5S5AWPTN6W | 🔍 observability
  • concurrent hook token conflict - two workflows cannot use the same hook token simultaneously | wrun_41M0JVDJQD0GYKNBHAZRA1DNTG | 🔍 observability
  • hookGetConflictWorkflow - hook.getConflict() resolves with the conflicting run when token is already registered | wrun_41M0JVENWW0GQE9XR6606SQ603 | 🔍 observability
  • setAttributes start: initial attributes are seeded on run creation
  • setAttributes start: reserved-prefix initial attributes are seeded with allowReservedAttributes
  • setAttributes setAttributesWorkflow: workflow-body calls append native attr_set events and merge correctly
  • setAttributes setAttributesInsideStepWorkflow: step-body calls append attributed native events
  • setAttributes fire-and-forget: void setAttributes lands without awaiting
  • setAttributes Promise.all of disjoint-key writes: every key lands
  • setAttributes validation DX: invalid writes throw catchable FatalErrors naming rule and limit

hono-node (7 failed):

  • hookCleanupTestWorkflow - hook token reuse after workflow completion | wrun_41M0JVE0RZ0GPRAS5S5AWPTN6W | 🔍 observability
  • hookGetConflictWorkflow - hook.getConflict() resolves with the conflicting run when token is already registered | wrun_41M0JVENWW0GQE9XR6606SQ603 | 🔍 observability
  • setAttributes start: initial attributes are seeded on run creation
  • setAttributes setAttributesWorkflow: workflow-body calls append native attr_set events and merge correctly
  • setAttributes setAttributesInsideStepWorkflow: step-body calls append attributed native events
  • setAttributes fire-and-forget: void setAttributes lands without awaiting
  • setAttributes Promise.all of disjoint-key writes: every key lands

hono-quickjs (10 failed):

  • hookCleanupTestWorkflow - hook token reuse after workflow completion | wrun_41M0JVE0RZ0GPRAS5S5AWPTN6W | 🔍 observability
  • concurrent hook token conflict - two workflows cannot use the same hook token simultaneously | wrun_41M0JVDJQD0GYKNBHAZRA1DNTG | 🔍 observability
  • hookMinRetentionWorkflow - terminal Hook cannot resume and its token stays unavailable | wrun_41M0JVG5JD0GVPE1WDZJ3DP916 | 🔍 observability
  • setAttributes start: initial attributes are seeded on run creation
  • setAttributes start: reserved-prefix initial attributes are seeded with allowReservedAttributes
  • setAttributes setAttributesWorkflow: workflow-body calls append native attr_set events and merge correctly
  • setAttributes setAttributesInsideStepWorkflow: step-body calls append attributed native events
  • setAttributes fire-and-forget: void setAttributes lands without awaiting
  • setAttributes Promise.all of disjoint-key writes: every key lands
  • setAttributes workflow throws after awaited setAttributes: attribute still persists on the failed run

nest-node (11 failed):

  • hookCleanupTestWorkflow - hook token reuse after workflow completion | wrun_41M0JVE0RZ0GPRAS5S5AWPTN6W | 🔍 observability
  • concurrent hook token conflict - two workflows cannot use the same hook token simultaneously | wrun_41M0JVDJQD0GYKNBHAZRA1DNTG | 🔍 observability
  • hookGetConflictWorkflow - hook.getConflict() resolves with the conflicting run when token is already registered | wrun_41M0JVENWW0GQE9XR6606SQ603 | 🔍 observability
  • hookMinRetentionWorkflow - terminal Hook cannot resume and its token stays unavailable | wrun_41M0JVG5JD0GVPE1WDZJ3DP916 | 🔍 observability
  • setAttributes start: initial attributes are seeded on run creation
  • setAttributes start: reserved-prefix initial attributes are seeded with allowReservedAttributes
  • setAttributes setAttributesWorkflow: workflow-body calls append native attr_set events and merge correctly
  • setAttributes setAttributesInsideStepWorkflow: step-body calls append attributed native events
  • setAttributes fire-and-forget: void setAttributes lands without awaiting
  • setAttributes Promise.all of disjoint-key writes: every key lands
  • setAttributes workflow throws after awaited setAttributes: attribute still persists on the failed run

nest-quickjs (9 failed):

  • hookCleanupTestWorkflow - hook token reuse after workflow completion | wrun_41M0JVE0RZ0GPRAS5S5AWPTN6W | 🔍 observability
  • concurrent hook token conflict - two workflows cannot use the same hook token simultaneously | wrun_41M0JVDJQD0GYKNBHAZRA1DNTG | 🔍 observability
  • hookGetConflictWorkflow - hook.getConflict() resolves with the conflicting run when token is already registered | wrun_41M0JVENWW0GQE9XR6606SQ603 | 🔍 observability
  • setAttributes start: initial attributes are seeded on run creation
  • setAttributes start: reserved-prefix initial attributes are seeded with allowReservedAttributes
  • setAttributes setAttributesWorkflow: workflow-body calls append native attr_set events and merge correctly
  • setAttributes setAttributesInsideStepWorkflow: step-body calls append attributed native events
  • setAttributes fire-and-forget: void setAttributes lands without awaiting
  • setAttributes workflow throws after awaited setAttributes: attribute still persists on the failed run

nextjs-turbopack-node (5 failed):

  • hookCleanupTestWorkflow - hook token reuse after workflow completion | wrun_41M0JVE0RZ0GPRAS5S5AWPTN6W | 🔍 observability
  • concurrent hook token conflict - two workflows cannot use the same hook token simultaneously | wrun_41M0JVDJQD0GYKNBHAZRA1DNTG | 🔍 observability
  • hookGetConflictWorkflow - hook.getConflict() resolves with the conflicting run when token is already registered | wrun_41M0JVENWW0GQE9XR6606SQ603 | 🔍 observability
  • setAttributes fire-and-forget: void setAttributes lands without awaiting
  • setAttributes workflow throws after awaited setAttributes: attribute still persists on the failed run

nextjs-turbopack-quickjs (9 failed):

  • hookCleanupTestWorkflow - hook token reuse after workflow completion | wrun_41M0JVE0RZ0GPRAS5S5AWPTN6W | 🔍 observability
  • concurrent hook token conflict - two workflows cannot use the same hook token simultaneously | wrun_41M0JVDJQD0GYKNBHAZRA1DNTG | 🔍 observability
  • hookGetConflictWorkflow - hook.getConflict() resolves with the conflicting run when token is already registered | wrun_41M0JVENWW0GQE9XR6606SQ603 | 🔍 observability
  • hookMinRetentionWorkflow - terminal Hook cannot resume and its token stays unavailable | wrun_41M0JVG5JD0GVPE1WDZJ3DP916 | 🔍 observability
  • setAttributes start: initial attributes are seeded on run creation
  • setAttributes setAttributesWorkflow: workflow-body calls append native attr_set events and merge correctly
  • setAttributes setAttributesInsideStepWorkflow: step-body calls append attributed native events
  • setAttributes fire-and-forget: void setAttributes lands without awaiting
  • setAttributes workflow throws after awaited setAttributes: attribute still persists on the failed run

nextjs-webpack-node (7 failed):

  • hookCleanupTestWorkflow - hook token reuse after workflow completion | wrun_41M0JVE0RZ0GPRAS5S5AWPTN6W | 🔍 observability
  • hookGetConflictWorkflow - hook.getConflict() resolves with the conflicting run when token is already registered | wrun_41M0JVENWW0GQE9XR6606SQ603 | 🔍 observability
  • hookMinRetentionWorkflow - terminal Hook cannot resume and its token stays unavailable | wrun_41M0JVG5JD0GVPE1WDZJ3DP916 | 🔍 observability
  • setAttributes start: initial attributes are seeded on run creation
  • setAttributes start: reserved-prefix initial attributes are seeded with allowReservedAttributes
  • setAttributes setAttributesWorkflow: workflow-body calls append native attr_set events and merge correctly
  • setAttributes fire-and-forget: void setAttributes lands without awaiting

nextjs-webpack-quickjs (7 failed):

  • hookCleanupTestWorkflow - hook token reuse after workflow completion | wrun_41M0JVE0RZ0GPRAS5S5AWPTN6W | 🔍 observability
  • setAttributes start: initial attributes are seeded on run creation
  • setAttributes start: reserved-prefix initial attributes are seeded with allowReservedAttributes
  • setAttributes setAttributesWorkflow: workflow-body calls append native attr_set events and merge correctly
  • setAttributes setAttributesInsideStepWorkflow: step-body calls append attributed native events
  • setAttributes fire-and-forget: void setAttributes lands without awaiting
  • setAttributes validation DX: invalid writes throw catchable FatalErrors naming rule and limit

nitro-node (8 failed):

  • hookCleanupTestWorkflow - hook token reuse after workflow completion | wrun_41M0JVE0RZ0GPRAS5S5AWPTN6W | 🔍 observability
  • concurrent hook token conflict - two workflows cannot use the same hook token simultaneously | wrun_41M0JVDJQD0GYKNBHAZRA1DNTG | 🔍 observability
  • hookGetConflictWorkflow - hook.getConflict() resolves with the conflicting run when token is already registered | wrun_41M0JVENWW0GQE9XR6606SQ603 | 🔍 observability
  • setAttributes start: initial attributes are seeded on run creation
  • setAttributes start: reserved-prefix initial attributes are seeded with allowReservedAttributes
  • setAttributes setAttributesWorkflow: workflow-body calls append native attr_set events and merge correctly
  • setAttributes setAttributesInsideStepWorkflow: step-body calls append attributed native events
  • setAttributes fire-and-forget: void setAttributes lands without awaiting

nitro-quickjs (9 failed):

  • hookCleanupTestWorkflow - hook token reuse after workflow completion | wrun_41M0JVE0RZ0GPRAS5S5AWPTN6W | 🔍 observability
  • concurrent hook token conflict - two workflows cannot use the same hook token simultaneously | wrun_41M0JVDJQD0GYKNBHAZRA1DNTG | 🔍 observability
  • hookGetConflictWorkflow - hook.getConflict() resolves with the conflicting run when token is already registered | wrun_41M0JVENWW0GQE9XR6606SQ603 | 🔍 observability
  • setAttributes start: initial attributes are seeded on run creation
  • setAttributes start: reserved-prefix initial attributes are seeded with allowReservedAttributes
  • setAttributes setAttributesWorkflow: workflow-body calls append native attr_set events and merge correctly
  • setAttributes setAttributesInsideStepWorkflow: step-body calls append attributed native events
  • setAttributes fire-and-forget: void setAttributes lands without awaiting
  • setAttributes workflow throws after awaited setAttributes: attribute still persists on the failed run

nuxt-node (8 failed):

  • hookCleanupTestWorkflow - hook token reuse after workflow completion | wrun_41M0JVE0RZ0GPRAS5S5AWPTN6W | 🔍 observability
  • concurrent hook token conflict - two workflows cannot use the same hook token simultaneously | wrun_41M0JVDJQD0GYKNBHAZRA1DNTG | 🔍 observability
  • hookGetConflictWorkflow - hook.getConflict() resolves with the conflicting run when token is already registered | wrun_41M0JVENWW0GQE9XR6606SQ603 | 🔍 observability
  • hookMinRetentionWorkflow - terminal Hook cannot resume and its token stays unavailable | wrun_41M0JVG5JD0GVPE1WDZJ3DP916 | 🔍 observability
  • setAttributes start: initial attributes are seeded on run creation
  • setAttributes start: reserved-prefix initial attributes are seeded with allowReservedAttributes
  • setAttributes setAttributesWorkflow: workflow-body calls append native attr_set events and merge correctly
  • setAttributes fire-and-forget: void setAttributes lands without awaiting

nuxt-quickjs (9 failed):

  • hookCleanupTestWorkflow - hook token reuse after workflow completion | wrun_41M0JVE0RZ0GPRAS5S5AWPTN6W | 🔍 observability
  • concurrent hook token conflict - two workflows cannot use the same hook token simultaneously | wrun_41M0JVDJQD0GYKNBHAZRA1DNTG | 🔍 observability
  • hookGetConflictWorkflow - hook.getConflict() resolves with the conflicting run when token is already registered | wrun_41M0JVENWW0GQE9XR6606SQ603 | 🔍 observability
  • hookMinRetentionWorkflow - terminal Hook cannot resume and its token stays unavailable | wrun_41M0JVG5JD0GVPE1WDZJ3DP916 | 🔍 observability
  • setAttributes start: initial attributes are seeded on run creation
  • setAttributes start: reserved-prefix initial attributes are seeded with allowReservedAttributes
  • setAttributes setAttributesWorkflow: workflow-body calls append native attr_set events and merge correctly
  • setAttributes setAttributesInsideStepWorkflow: step-body calls append attributed native events
  • setAttributes fire-and-forget: void setAttributes lands without awaiting

sveltekit-node (3 failed):

  • hookCleanupTestWorkflow - hook token reuse after workflow completion | wrun_41M0JVE0RZ0GPRAS5S5AWPTN6W | 🔍 observability
  • concurrent hook token conflict - two workflows cannot use the same hook token simultaneously | wrun_41M0JVDJQD0GYKNBHAZRA1DNTG | 🔍 observability
  • hookGetConflictWorkflow - hook.getConflict() resolves with the conflicting run when token is already registered | wrun_41M0JVENWW0GQE9XR6606SQ603 | 🔍 observability

sveltekit-quickjs (2 failed):

  • hookCleanupTestWorkflow - hook token reuse after workflow completion | wrun_41M0JVE0RZ0GPRAS5S5AWPTN6W | 🔍 observability
  • concurrent hook token conflict - two workflows cannot use the same hook token simultaneously | wrun_41M0JVDJQD0GYKNBHAZRA1DNTG | 🔍 observability

tanstack-start-node (6 failed):

  • hookCleanupTestWorkflow - hook token reuse after workflow completion | wrun_41M0JVE0RZ0GPRAS5S5AWPTN6W | 🔍 observability
  • hookMinRetentionWorkflow - terminal Hook cannot resume and its token stays unavailable | wrun_41M0JVG5JD0GVPE1WDZJ3DP916 | 🔍 observability
  • setAttributes start: initial attributes are seeded on run creation
  • setAttributes start: reserved-prefix initial attributes are seeded with allowReservedAttributes
  • setAttributes setAttributesWorkflow: workflow-body calls append native attr_set events and merge correctly
  • setAttributes fire-and-forget: void setAttributes lands without awaiting

tanstack-start-quickjs (10 failed):

  • hookCleanupTestWorkflow - hook token reuse after workflow completion | wrun_41M0JVE0RZ0GPRAS5S5AWPTN6W | 🔍 observability
  • concurrent hook token conflict - two workflows cannot use the same hook token simultaneously | wrun_41M0JVDJQD0GYKNBHAZRA1DNTG | 🔍 observability
  • hookGetConflictWorkflow - hook.getConflict() resolves with the conflicting run when token is already registered | wrun_41M0JVENWW0GQE9XR6606SQ603 | 🔍 observability
  • setAttributes start: initial attributes are seeded on run creation
  • setAttributes start: reserved-prefix initial attributes are seeded with allowReservedAttributes
  • setAttributes setAttributesWorkflow: workflow-body calls append native attr_set events and merge correctly
  • setAttributes setAttributesInsideStepWorkflow: step-body calls append attributed native events
  • setAttributes fire-and-forget: void setAttributes lands without awaiting
  • setAttributes workflow throws after awaited setAttributes: attribute still persists on the failed run
  • setAttributes validation DX: invalid writes throw catchable FatalErrors naming rule and limit

vite-node (8 failed):

  • hookCleanupTestWorkflow - hook token reuse after workflow completion | wrun_41M0JVE0RZ0GPRAS5S5AWPTN6W | 🔍 observability
  • concurrent hook token conflict - two workflows cannot use the same hook token simultaneously | wrun_41M0JVDJQD0GYKNBHAZRA1DNTG | 🔍 observability
  • hookGetConflictWorkflow - hook.getConflict() resolves with the conflicting run when token is already registered | wrun_41M0JVENWW0GQE9XR6606SQ603 | 🔍 observability
  • hookMinRetentionWorkflow - terminal Hook cannot resume and its token stays unavailable | wrun_41M0JVG5JD0GVPE1WDZJ3DP916 | 🔍 observability
  • setAttributes start: initial attributes are seeded on run creation
  • setAttributes start: reserved-prefix initial attributes are seeded with allowReservedAttributes
  • setAttributes setAttributesWorkflow: workflow-body calls append native attr_set events and merge correctly
  • setAttributes fire-and-forget: void setAttributes lands without awaiting

vite-quickjs (10 failed):

  • hookCleanupTestWorkflow - hook token reuse after workflow completion | wrun_41M0JVE0RZ0GPRAS5S5AWPTN6W | 🔍 observability
  • concurrent hook token conflict - two workflows cannot use the same hook token simultaneously | wrun_41M0JVDJQD0GYKNBHAZRA1DNTG | 🔍 observability
  • hookGetConflictWorkflow - hook.getConflict() resolves with the conflicting run when token is already registered | wrun_41M0JVENWW0GQE9XR6606SQ603 | 🔍 observability
  • hookMinRetentionWorkflow - terminal Hook cannot resume and its token stays unavailable | wrun_41M0JVG5JD0GVPE1WDZJ3DP916 | 🔍 observability
  • setAttributes start: initial attributes are seeded on run creation
  • setAttributes start: reserved-prefix initial attributes are seeded with allowReservedAttributes
  • setAttributes setAttributesWorkflow: workflow-body calls append native attr_set events and merge correctly
  • setAttributes setAttributesInsideStepWorkflow: step-body calls append attributed native events
  • setAttributes fire-and-forget: void setAttributes lands without awaiting
  • setAttributes Promise.all of disjoint-key writes: every key lands

⚠️ Flaky E2E Tests (passed on retry)

These tests failed at least once and passed on a retry. A recurring entry here is a real race worth investigating.

21 flaky tests
  • abortThrowIfAbortedMidFlightWorkflow: throwIfAborted in a polling loop bails when abort fires (nest)
  • abortThrowIfAbortedMidFlightWorkflow: throwIfAborted in a polling loop bails when abort fires (nuxt)
  • addTenWorkflow (hono)
  • addTenWorkflow (nuxt)
  • deploymentId: 'latest' is a no-op in non-Vercel worlds (nitro)
  • fibonacciWorkflow - recursive workflow composition via start() (nextjs-webpack)
  • health check (CLI) - workflow health command reports healthy endpoints (nextjs-webpack)
  • hookMinRetentionWorkflow - terminal Hook cannot resume and its token stays unavailable (nitro)
  • hookMinRetentionWorkflow - terminal Hook cannot resume and its token stays unavailable (tanstack-start)
  • parallelStepsThenWebhookWorkflow - no hook_conflict from same-tick replay race (fastify)
  • Promise.all of disjoint-key writes: every key lands (tanstack-start)
  • setAttributesInsideStepWorkflow: step-body calls append attributed native events (nuxt)
  • setAttributesInsideStepWorkflow: step-body calls append attributed native events (vite)
  • start: reserved-prefix initial attributes are seeded with allowReservedAttributes (hono)
  • validation DX: invalid writes throw catchable FatalErrors naming rule and limit (example)
  • validation DX: invalid writes throw catchable FatalErrors naming rule and limit (express)
  • validation DX: invalid writes throw catchable FatalErrors naming rule and limit (hono)
  • validation DX: invalid writes throw catchable FatalErrors naming rule and limit (nest)
  • validation DX: invalid writes throw catchable FatalErrors naming rule and limit (nitro)
  • workflow throws after awaited setAttributes: attribute still persists on the failed run (fastify)
  • workflow throws after awaited setAttributes: attribute still persists on the failed run (nitro)

🛠 Infra Events (absorbed by the harness)

Platform anomalies the e2e harness detected and worked around (e.g. a run the queue never picked up, replaced by a fresh run). Clustered timestamps indicate a backend blip; a steady drip indicates a platform issue worth escalating.

  • cold-start-warmup · suite warmup (tanstack-start) · at 19:02:50Z · abandoned wrun_01M0JV7THN48YQEYQFRGN79RND

E2E Test Summary

Summary
Passed Failed Skipped Total
❌ ▲ Vercel Production 3378 200 742 4320
✅ 💻 Local Development 3922 0 558 4480
✅ 📦 Local Production 3922 0 558 4480
✅ 🐘 Local Postgres 3922 0 558 4480
✅ 🪟 Windows 320 0 0 320
✅ 🌐 Cross-language Conformance 9 0 132 141
✅ vercel-multi-region 27 0 0 27
Total 15500 200 2548 18248
Details by Category

❌ ▲ Vercel Production

App Passed Failed Skipped
❌ astro-node 129 3 28
❌ astro-quickjs 124 8 28
❌ example-node 125 7 28
❌ example-quickjs 124 8 28
❌ express-node 123 9 28
❌ express-quickjs 124 8 28
❌ fastify-node 123 9 28
❌ fastify-quickjs 122 10 28
❌ hono-node 125 7 28
❌ hono-quickjs 122 10 28
❌ nest-node 121 11 28
❌ nest-quickjs 123 9 28
❌ nextjs-turbopack-node 152 5 3
❌ nextjs-turbopack-quickjs 148 9 3
❌ nextjs-webpack-node 150 7 3
❌ nextjs-webpack-quickjs 150 7 3
❌ nitro-node 124 8 28
❌ nitro-quickjs 123 9 28
❌ nuxt-node 124 8 28
❌ nuxt-quickjs 123 9 28
✅ python-node 8 0 152
❌ sveltekit-node 148 3 9
❌ sveltekit-quickjs 149 2 9
❌ tanstack-start-node 126 6 28
❌ tanstack-start-quickjs 122 10 28
❌ vite-node 124 8 28
❌ vite-quickjs 122 10 28

✅ 💻 Local Development

App Passed Failed Skipped
✅ astro-stable-node 134 0 26
✅ astro-stable-quickjs 134 0 26
✅ express-stable-node 134 0 26
✅ express-stable-quickjs 134 0 26
✅ fastify-stable-node 134 0 26
✅ fastify-stable-quickjs 134 0 26
✅ hono-stable-node 134 0 26
✅ hono-stable-quickjs 134 0 26
✅ nest-stable-node 134 0 26
✅ nest-stable-quickjs 134 0 26
✅ nextjs-turbopack-canary-node 141 0 19
✅ nextjs-turbopack-canary-quickjs 141 0 19
✅ nextjs-turbopack-stable-node 160 0 0
✅ nextjs-turbopack-stable-quickjs 160 0 0
✅ nextjs-webpack-canary-node 141 0 19
✅ nextjs-webpack-canary-quickjs 141 0 19
✅ nextjs-webpack-stable-node 160 0 0
✅ nextjs-webpack-stable-quickjs 160 0 0
✅ nitro-stable-node 134 0 26
✅ nitro-stable-quickjs 134 0 26
✅ nuxt-stable-node 134 0 26
✅ nuxt-stable-quickjs 134 0 26
✅ sveltekit-stable-node 153 0 7
✅ sveltekit-stable-quickjs 153 0 7
✅ tanstack-start-node 134 0 26
✅ tanstack-start-quickjs 134 0 26
✅ vite-stable-node 134 0 26
✅ vite-stable-quickjs 134 0 26

✅ 📦 Local Production

App Passed Failed Skipped
✅ astro-stable-node 134 0 26
✅ astro-stable-quickjs 134 0 26
✅ express-stable-node 134 0 26
✅ express-stable-quickjs 134 0 26
✅ fastify-stable-node 134 0 26
✅ fastify-stable-quickjs 134 0 26
✅ hono-stable-node 134 0 26
✅ hono-stable-quickjs 134 0 26
✅ nest-stable-node 134 0 26
✅ nest-stable-quickjs 134 0 26
✅ nextjs-turbopack-canary-node 141 0 19
✅ nextjs-turbopack-canary-quickjs 141 0 19
✅ nextjs-turbopack-stable-node 160 0 0
✅ nextjs-turbopack-stable-quickjs 160 0 0
✅ nextjs-webpack-canary-node 141 0 19
✅ nextjs-webpack-canary-quickjs 141 0 19
✅ nextjs-webpack-stable-node 160 0 0
✅ nextjs-webpack-stable-quickjs 160 0 0
✅ nitro-stable-node 134 0 26
✅ nitro-stable-quickjs 134 0 26
✅ nuxt-stable-node 134 0 26
✅ nuxt-stable-quickjs 134 0 26
✅ sveltekit-stable-node 153 0 7
✅ sveltekit-stable-quickjs 153 0 7
✅ tanstack-start-node 134 0 26
✅ tanstack-start-quickjs 134 0 26
✅ vite-stable-node 134 0 26
✅ vite-stable-quickjs 134 0 26

✅ 🐘 Local Postgres

App Passed Failed Skipped
✅ astro-stable-node 134 0 26
✅ astro-stable-quickjs 134 0 26
✅ express-stable-node 134 0 26
✅ express-stable-quickjs 134 0 26
✅ fastify-stable-node 134 0 26
✅ fastify-stable-quickjs 134 0 26
✅ hono-stable-node 134 0 26
✅ hono-stable-quickjs 134 0 26
✅ nest-stable-node 134 0 26
✅ nest-stable-quickjs 134 0 26
✅ nextjs-turbopack-canary-node 141 0 19
✅ nextjs-turbopack-canary-quickjs 141 0 19
✅ nextjs-turbopack-stable-node 160 0 0
✅ nextjs-turbopack-stable-quickjs 160 0 0
✅ nextjs-webpack-canary-node 141 0 19
✅ nextjs-webpack-canary-quickjs 141 0 19
✅ nextjs-webpack-stable-node 160 0 0
✅ nextjs-webpack-stable-quickjs 160 0 0
✅ nitro-stable-node 134 0 26
✅ nitro-stable-quickjs 134 0 26
✅ nuxt-stable-node 134 0 26
✅ nuxt-stable-quickjs 134 0 26
✅ sveltekit-stable-node 153 0 7
✅ sveltekit-stable-quickjs 153 0 7
✅ tanstack-start-node 134 0 26
✅ tanstack-start-quickjs 134 0 26
✅ vite-stable-node 134 0 26
✅ vite-stable-quickjs 134 0 26

✅ 🪟 Windows

App Passed Failed Skipped
✅ nextjs-turbopack-node 160 0 0
✅ nextjs-turbopack-quickjs 160 0 0

✅ 🌐 Cross-language Conformance

App Passed Failed Skipped
✅ python 9 0 132

✅ vercel-multi-region

App Passed Failed Skipped
✅ nextjs-turbopack 27 0 0

📋 View full workflow run

@github-actions

github-actions Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

📊 Workflow Benchmarks

The benchmark run for 58d0d68 failed. See the run logs for details.

commit b89ebc8 · Fri, 14 Aug 2026 17:22:37 GMT · run logs

Backend: vercel · app: nextjs-turbopack

Metric Scenario Best (ms) P75 (ms) P90 (ms) P99 (ms) Samples
TTFS step 1334 (+250%) 🔻 1507 🔴 (+35%) 🔻 1524 🔴 (+34%) 🔻 1699 🔴 (+11%) 30
TTFS stream 1408 (+40%) 🔻 1480 🔴 (+40%) 🔻 1505 🔴 (+41%) 🔻 1512 🔴 (+37%) 🔻 30
TTFS hook + stream 1614 (+26%) 🔻 1786 🔴 (+29%) 🔻 1809 🔴 (+27%) 🔻 2102 🔴 (+30%) 🔻 30
Fan-out TTFS Promise.all(100 steps) 8841 (-0.9%) 10402 (+4.5%) 10468 (+3.8%) 14270 (+5.5%) 10
Fan-out TTLS Promise.all(100 steps) 17220 (-2.6%) 18800 (±0%) 19248 (+1.3%) 24141 (+3.0%) 10
STSO 1020 steps (inline) 123 (±0%) 179 (-6.3%) 205 (-10%) 390 (-33%) 💚 1019
WO 1020 steps 177585 (-9.1%) 177585 (-9.1%) 177585 (-9.1%) 177585 (-9.1%) 1
SL stream latency 84 (+6.3%) 115 🔴 (+4.5%) 131 🔴 (+1.6%) 208 🔴 (-39%) 💚 30
SO stream overhead (text) 104 (-6.3%) 149 (-17%) 💚 175 (-15%) 💚 269 (-55%) 💚 30
SO stream overhead (structured) 110 (+15%) 177 (+13%) 204 (+22%) 🔻 500 (+175%) 🔻 30
📜 Previous results (5)

8fa19ed

Tue, 11 Aug 2026 18:35:45 GMT · run logs

vercel / nextjs-turbopack

Metric Scenario Best (ms) P75 (ms) P90 (ms) P99 (ms) Samples
TTFS step 415 (-53%) 💚 1417 🔴 (+15%) 🔻 1456 🔴 (+11%) 1530 🔴 (-15%) 💚 30
TTFS stream 239 (-77%) 💚 1460 🔴 (+29%) 🔻 1588 🔴 (+36%) 🔻 2146 🔴 (+73%) 🔻 30
TTFS hook + stream 476 (-66%) 💚 1740 🔴 (+12%) 1767 🔴 (+12%) 1961 🔴 (+23%) 🔻 30
STSO 1020 steps (inline) 142 (-9.6%) 200 (-19%) 💚 231 (-26%) 💚 434 (-39%) 💚 1019
WO 1020 steps 202212 (-17%) 💚 202212 (-17%) 💚 202212 (-17%) 💚 202212 (-17%) 💚 1
SL stream latency 103 (-18%) 💚 151 🔴 (-18%) 💚 212 🔴 (-10%) 384 🔴 (+22%) 🔻 30
SO stream overhead (text) 150 (+1.4%) 336 🔴 (+35%) 🔻 447 (+29%) 🔻 750 (+26%) 🔻 30
SO stream overhead (structured) 154 (-4.9%) 244 (-19%) 💚 301 (-14%) 352 (-67%) 💚 30

fe8ab90

Tue, 11 Aug 2026 17:47:11 GMT · run logs

vercel / nextjs-turbopack

Metric Scenario Best (ms) P75 (ms) P90 (ms) P99 (ms) Samples
TTFS step 1331 (+51%) 🔻 1502 🔴 (+22%) 🔻 1559 🔴 (+19%) 🔻 1738 🔴 (-3.8%) 30
TTFS stream 368 (-65%) 💚 1453 🔴 (+28%) 🔻 1483 🔴 (+27%) 🔻 1489 🔴 (+20%) 🔻 30
TTFS hook + stream 1600 (+14%) 1743 🔴 (+12%) 1834 🔴 (+16%) 🔻 2340 🔴 (+47%) 🔻 30
STSO 1020 steps (inline) 138 (-12%) 179 (-28%) 💚 199 (-36%) 💚 314 (-56%) 💚 1019
WO 1020 steps 180437 (-26%) 💚 180437 (-26%) 💚 180437 (-26%) 💚 180437 (-26%) 💚 1
SL stream latency 114 (-8.8%) 162 🔴 (-12%) 229 🔴 (-3.0%) 380 🔴 (+21%) 🔻 30
SO stream overhead (text) 124 (-16%) 💚 205 (-18%) 💚 240 (-31%) 💚 396 (-33%) 💚 30
SO stream overhead (structured) 133 (-18%) 💚 221 (-27%) 💚 254 (-28%) 💚 377 (-64%) 💚 30

faa7f6f

Tue, 11 Aug 2026 02:24:17 GMT · run logs

vercel / nextjs-turbopack

Metric Scenario Best (ms) P75 (ms) P90 (ms) P99 (ms) Samples
TTFS step 1276 (+21%) 🔻 1371 🔴 (+16%) 🔻 1386 🔴 (+15%) 🔻 1706 🔴 (±0%) 30
TTFS stream 1283 (+23%) 🔻 1352 🔴 (+18%) 🔻 1372 🔴 (+18%) 🔻 1400 🔴 (+16%) 🔻 30
TTFS hook + stream 1525 (+22%) 🔻 1642 🔴 (+20%) 🔻 1747 🔴 (+25%) 🔻 1822 🔴 (+21%) 🔻 30
STSO 1020 steps (inline) 89 (-7.3%) 126 (-23%) 💚 140 (-31%) 💚 221 (-72%) 💚 1019
WO 1020 steps 124892 (-27%) 💚 124892 (-27%) 💚 124892 (-27%) 💚 124892 (-27%) 💚 1
SL stream latency 81 (-11%) 111 🔴 (-28%) 💚 127 🔴 (-26%) 💚 213 🔴 (-62%) 💚 30
SO stream overhead (text) 108 (-18%) 💚 144 (-48%) 💚 161 (-51%) 💚 272 (-65%) 💚 30
SO stream overhead (structured) 101 (-17%) 💚 160 (-35%) 💚 176 (-44%) 💚 559 (+24%) 🔻 30

fefe947

Tue, 11 Aug 2026 01:59:30 GMT · run logs

vercel / nextjs-turbopack

Metric Scenario Best (ms) P75 (ms) P90 (ms) P99 (ms) Samples
TTFS step 1328 (+26%) 🔻 1430 🔴 (+21%) 🔻 1453 🔴 (+21%) 🔻 1901 🔴 (+11%) 30
TTFS stream 444 (-57%) 💚 1431 🔴 (+25%) 🔻 1466 🔴 (+26%) 🔻 1672 🔴 (+38%) 🔻 30
TTFS hook + stream 1452 (+16%) 🔻 1666 🔴 (+22%) 🔻 1710 🔴 (+22%) 🔻 1815 🔴 (+20%) 🔻 30
STSO 1020 steps (inline) 98 (+2.1%) 139 (-15%) 💚 163 (-20%) 💚 305 (-61%) 💚 1019
WO 1020 steps 140518 (-18%) 💚 140518 (-18%) 💚 140518 (-18%) 💚 140518 (-18%) 💚 1
SL stream latency 99 (+8.8%) 134 🔴 (-13%) 149 🔴 (-13%) 342 🔴 (-39%) 💚 30
SO stream overhead (text) 118 (-11%) 182 (-34%) 💚 206 (-38%) 💚 343 (-56%) 💚 30
SO stream overhead (structured) 121 (-0.8%) 170 (-31%) 💚 185 (-41%) 💚 283 (-37%) 💚 30

2f1c101

Mon, 27 Jul 2026 22:56:03 GMT · run logs

vercel / nextjs-turbopack

Metric Scenario Best (ms) P75 (ms) P90 (ms) P99 (ms) Samples
TTFS step 1255 (+70%) 🔻 1388 🔴 (+23%) 🔻 1402 🔴 (+16%) 🔻 1720 🔴 (+26%) 🔻 30
TTFS stream 372 (+18%) 🔻 1355 🔴 (+23%) 🔻 1376 🔴 (+20%) 🔻 1457 🔴 (+24%) 🔻 30
TTFS hook + stream 1342 (+5.8%) 1663 🔴 (+19%) 🔻 1712 🔴 (+17%) 🔻 1767 🔴 (+8.1%) 30
STSO 1020 steps (1-20) 165 (-2.9%) 333 🔴 (+10%) 377 🔴 (+7.1%) 451 🔴 (+19%) 🔻 19
STSO 1020 steps (101-120) 173 (-12%) 271 🔴 (-20%) 💚 359 🔴 (-12%) 478 🔴 (-4.4%) 19
STSO 1020 steps (1001-1020) 478 (-1.0%) 561 🔴 (-2.3%) 569 🔴 (-12%) 700 🔴 (+6.5%) 19
WO 1020 steps 406393 (-7.4%) 406393 (-7.4%) 406393 (-7.4%) 406393 (-7.4%) 1
SL stream latency 99 (+6.5%) 164 🔴 (-2.4%) 183 🔴 (-22%) 💚 370 🔴 (+46%) 🔻 30
SO stream overhead (text) 104 (-21%) 💚 168 (-42%) 💚 170 (-52%) 💚 589 (+40%) 🔻 30
SO stream overhead (structured) 103 (-24%) 💚 189 (-27%) 💚 215 (-23%) 💚 332 (-20%) 💚 30
ℹ️ Metric definitions & methodology

Best/P75/P90/P99 deltas compare against the most recent benchmark run on main at the time of this run. 🔻 flags a delta worse than +15%, 💚 one better than −15%.

Metrics — TTFS: time to first step body (in-deployment start() → first step body) · Fan-out TTFS: fan-out time to first step (in-deployment start() → first of the parallel step bodies to complete) · Fan-out TTLS: fan-out time to last step (in-deployment start() → last of the parallel step bodies to complete, i.e. when the Promise.all resolves) · STSO: step-to-step overhead (gap between consecutive step bodies) · WO: workflow overhead (whole-run time outside step bodies, in-deployment anchored) · SL: stream latency (in-deployment write → read propagation, readAt - writtenAt) · SO: stream overhead (end-to-end write+consume time beyond the modelled generation window)

Scenarios — step: one trivial no-op step, no stream; no hooks, so the run stays in turbo mode (in-process fast path) · stream: one streaming step; no hooks, so the run stays in turbo mode (in-process fast path) · hook + stream: registers a hook before one step, which exits turbo mode (dispatch path) · 1020 steps: 1020 trivial sequential steps; STSO is measured between consecutive steps in the given step ranges, and WO is the whole-run overhead outside step bodies · Promise.all(100 steps): 100 trivial no-op steps started together in a single Promise.all; Fan-out TTFS is the first of them to complete and Fan-out TTLS the last, both from the in-deployment clientStart, so their gap is the spread the runtime adds across the fan-out · stream latency: parallel reader/writer steps on a dedicated stream; SL is the in-deployment write->read propagation (readAt - writtenAt) · stream overhead (text): writer streams 300 variable-length text token deltas paced at 100/s for 3s (a haiku-size LLM's token throughput) while a parallel reader drains the whole stream; SO is the end-to-end write+consume time beyond the 3s generation window (overhead/backpressure) · stream overhead (structured): same workload as stream overhead (text), but each delta is an AI-SDK-style structured object ({ type: 'text-delta', id, text }) instead of a raw string, so the SO gap vs the text scenario is the added serialization cost

🔴 marks a percentile over its target (within target is left unmarked). Targets (p75/p90/p99, ms) — TTFS 200/300/600 · SL 50/60/125 · SO 250/500/1000

All timestamps are deployment-side; runs are triggered in-deployment, so the CI runner and api.vercel.com sit outside every measured window. TTFS = start() → first step body (includes dispatch + any cold start); Fan-out TTFS/TTLS = first/last step completion of one Promise.all from the same anchor (the gap is the runtime’s fan-out spread); STSO/WO between step bodies; CRTT inside the workflow (excludes the api.vercel.com read path).

Cold starts stay in the numbers (real bursty-workload latency, inflates P75+); Best is the warm floor.

The v5 World interface listing referenced analytics with a one-line
bullet only. Add a full 'Analytics Interface (Optional)' section:
the interface shape (runs/attributes/steps/events/hooks/waits, matching
packages/world/src/analytics.ts), the implementer contract (metadata-only
rows, latest-write-wins attribute filtering, paired time windows,
optional pageInfo, eventual consistency), feature-detection semantics
(tooling prefers analytics when present, falls back to Storage), and
which first-party Worlds implement it (Vercel only). Cross-linked from
the World-members list and to the Analytics API reference, which was
audited against source (pageInfo shape, attribute-key fields, 24h
default window, hooks.get signature) and is already accurate.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@karthikscale3 karthikscale3 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The Analytics section is implementor-facing, so I think it should define portable interface semantics and keep Vercel observability-pipeline behavior in the Vercel World documentation. I left two concrete wording suggestions below.


## Analytics Interface (Optional)

The optional `analytics` namespace serves observability surfaces — dashboards, `workflow inspect`, the local web UI — with **metadata-only** listings that can scan large numbers of runs without touching payload data. Tooling feature-detects it: when `world.analytics` is present it is preferred for listings and attribute search, and when it is absent, tooling falls back to the Storage APIs. Of the first-party Worlds, only the [Vercel World](/worlds/vercel) implements it (backed by the observability data pipeline); the Local and Postgres Worlds leave it `undefined`.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Separate the portable purpose and feature-detection behavior from the first-party implementation note, without tying the interface to Vercel's observability pipeline.

Suggested change
The optional `analytics` namespace serves observability surfaces — dashboards, `workflow inspect`, the local web UI — with **metadata-only** listings that can scan large numbers of runs without touching payload data. Tooling feature-detects it: when `world.analytics` is present it is preferred for listings and attribute search, and when it is absent, tooling falls back to the Storage APIs. Of the first-party Worlds, only the [Vercel World](/worlds/vercel) implements it (backed by the observability data pipeline); the Local and Postgres Worlds leave it `undefined`.
The optional `analytics` namespace provides **metadata-only** access to runs and their related records. It is intended for observability and discovery surfaces such as dashboards, `workflow inspect`, and the local web UI. Implementations can optimize these queries independently of payload storage.
Tooling feature-detects this namespace. When `world.analytics` is available, tooling prefers it for listings and attribute search; otherwise, it uses the Storage APIs. Among the first-party Worlds, only the [Vercel World](/worlds/vercel) currently implements it. The Local and Postgres Worlds leave it `undefined`.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Applied in 9abe275 — the intro now leads with the portable purpose (metadata-only access, implementations free to optimize independently of payload storage) and puts feature detection in its own paragraph, with the pipeline-backing note dropped from the interface docs. That Vercel-specific behavior now lives in the Vercel World page's Observability section instead.

Comment on lines +278 to +283
If you implement it, honor the contract that makes the namespace safe to expose broadly:

- **Metadata only.** Rows must never include run inputs/outputs, step data, or hook tokens — the `Analytics*` row schemas in `@workflow/world` define exactly which fields are allowed. There is no `resolveData` escape hatch here; payload access stays on the Storage APIs.
- **Attribute filtering is latest-write-wins.** `runs.list({ attributes })` matches a run's most recent value for each key (up to 8 pairs), and reserved `$`-prefixed keys are valid in filters even though user writes to them are rejected.
- **Time windows are the query contract.** `startTime`/`endTime` must be provided together; a bounded window lets the backend prune its scan. Responses may carry an optional `pageInfo` describing the scannable window (lookback days, window starts, upgrade availability) — return it if your backend has retention limits so tools can size date pickers.
- **Eventual consistency is acceptable.** Analytics data may trail live state by a few seconds; tools treat it as a listing surface, not a source of truth.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

These are useful interface guarantees, but they can be phrased without prescribing Vercel/ClickHouse query behavior. This also makes the paired-boundary rule explicit when both values are omitted.

Suggested change
If you implement it, honor the contract that makes the namespace safe to expose broadly:
- **Metadata only.** Rows must never include run inputs/outputs, step data, or hook tokens — the `Analytics*` row schemas in `@workflow/world` define exactly which fields are allowed. There is no `resolveData` escape hatch here; payload access stays on the Storage APIs.
- **Attribute filtering is latest-write-wins.** `runs.list({ attributes })` matches a run's most recent value for each key (up to 8 pairs), and reserved `$`-prefixed keys are valid in filters even though user writes to them are rejected.
- **Time windows are the query contract.** `startTime`/`endTime` must be provided together; a bounded window lets the backend prune its scan. Responses may carry an optional `pageInfo` describing the scannable window (lookback days, window starts, upgrade availability) — return it if your backend has retention limits so tools can size date pickers.
- **Eventual consistency is acceptable.** Analytics data may trail live state by a few seconds; tools treat it as a listing surface, not a source of truth.
If you implement this namespace, observe the following requirements:
- **Metadata only.** Analytics responses must not include run inputs or outputs, step data, hook tokens, or other payload data. The `Analytics*` schemas exported by `@workflow/world` define the complete set of permitted fields. Payload retrieval remains exclusively available through the Storage APIs.
- **Attribute filters use the latest value.** `runs.list({ attributes })` evaluates each filter against the run's most recently written value for that key. A request may contain up to eight key-value pairs. Reserved `$`-prefixed attributes are valid filters, although users cannot write them directly.
- **Time boundaries must be paired.** `startTime` and `endTime` may either both be omitted or both be supplied. Responses may include `pageInfo` describing retention and the available query window. Implementations with retention limits should return this information so tooling can present valid date ranges.
- **Results may be eventually consistent.** Analytics records may lag live workflow state. Consumers use this namespace for discovery and listing; Storage remains the authoritative interface for current workflow state and payload access.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Applied in 9abe275, verbatim — including the explicit both-omitted-or-both-supplied phrasing, which matches the AnalyticsListRunsParams doc comments in @workflow/world. The Vercel/ClickHouse specifics that were dropped here (bounded-window scan speed, plan lookback surfacing as observability-upgrade-required + pageInfo) moved to the Vercel World page's Observability section so they're still documented, just in the right place.

pranaygp and others added 4 commits July 27, 2026 15:06
… attributes across comparisons

Toolbar feedback on the Temporal comparison:
- Search attributes move out of the 'no direct equivalent' table into the
  migration mapping — setAttributes()/attributes-on-start() is the direct
  equivalent. trigger.dev gains the matching row (run tags / metadata.set());
  the other platforms have no run-tag concept to map.
- The retry-policy row no longer reads as a limitation: declarative Temporal
  policies (backoffCoefficient, nonRetryableErrorTypes, maximumAttempts) map
  to idiomatic code — maxRetries, FatalError, and backoff derived from
  getStepMetadata().attempt via RetryableError's retryAfter — with links to
  the errors-and-retries examples. Same treatment for the Cloudflare,
  Inngest, and Step Functions retry rows.
- code-transform: 'Changed in 5.0' callout on __step_registrations.js
  explaining the v4 step route + step queue topic merger, linking the v4
  version of the page.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* origin/main:
  Prewarm only appended replay payloads (#3131)
  feat: publish each run's X25519 public key on the run entity (#3095)
  feat(core): route sealed envelopes through the serialization layer (#3094)
  docs: redirect retired migration-guides URLs to comparisons (#3127)
  feat(core): add `encp` sealed-box encryption primitive (#3093)
  chore(core): clarify runtime comments (#3111)
  Remove obsolete world factory aliases (#3112)
  feat(core): deterministic sandbox hardening (#3045)
- Map @workflow/world-local in the docs snippet type-checker and drop
  the @skip-typecheck escape on the set-world example; also unskip the
  three complete input samples on code-transform. Four more snippets are
  now machine-checked (972 passing, was 968).
- World-upgrade prompt diffs against the release tag being upgraded to
  (workflow@5.x.y) instead of a moving main, falling back to main.
- World SDK index points World authors at the top-level interface
  members (specVersion, capabilities, createRunId, describeRun, ...)
  documented in Building a World.

The preceding merge of origin/main makes the GA-forward docs claims true
in-tree (factory aliases actually removed by #3112, sandbox hardening
from #3045 including the workflow-globals digest row this branch had
stale) and picks up the migration-guides redirects (#3127), whose
destinations now pass this branch's redirect-destination lint.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A correlation id names a step, hook or wait within its run, so the
lookup is now scoped to one run. Adds it to the World migration table
and to the v4→v5 migration skill.
pranaygp and others added 4 commits August 3, 2026 14:14
… to Vercel World page

Apply karthikscale3's review suggestions on Building a World: the analytics
namespace intro and implementation requirements now describe portable
interface semantics (paired-or-omitted time bounds, latest-value attribute
filters, metadata-only schemas) without prescribing Vercel/ClickHouse query
behavior. The Vercel-specific details (observability pipeline backing,
bounded-window scan speed, plan lookback via observability-upgrade-required
and pageInfo) move to the Vercel World Observability section.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… halves

whats-new.mdx now covers only what changes for application code, plus the
newly shipped runtime work (retained VM, parallel hook resume, deadline-derived
inline budget, hook token retention, QuickJS engine, WS events transport).

The World spec delta moves to /worlds/upgrading-to-v5: the interface changes,
the contract changes that compile cleanly and fail at runtime, the new optional
surface, and slot-numbered event ID allocation.
Comment thread docs/content/docs/v5/whats-new.mdx Outdated
Signed-off-by: Peter Wielander <mittgfu@gmail.com>
Comment thread docs/content/docs/v5/whats-new.mdx Outdated
Comment thread docs/content/docs/v5/whats-new.mdx Outdated
VaguelySerious and others added 4 commits August 10, 2026 19:07
Rewrite the event ID section of the World upgrade guide as a requirement
rather than an opt-in, and fix the spec-version guidance: world-local and
world-postgres advertise slotEventIds while staying on spec version 5, so
SPEC_VERSION_SUPPORTS_SLOT_IDENTITY is only for a World whose IDs come
from a remote allocator.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Peter Wielander <mittgfu@gmail.com>
Signed-off-by: Peter Wielander <mittgfu@gmail.com>
# Conflicts:
#	docs/content/worlds/v5/building-a-world.mdx
#	docs/content/worlds/v5/postgres.mdx
#	packages/docs-typecheck/src/type-checker.ts
@github-actions

github-actions Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Sim World

Simulated world deterministic testing for races. Traces

🟠 world-sim scenario book — 1 fail of 41 total

fence=per-spec

scenario outcome events virt replay violations
smoke-no-steps completed 3 0ms ok 0
smoke-one-step completed 6 0ms ok 0
hook-at-step-started completed 12 0ms ok 0
hook-at-step-completed completed 12 0ms ok 0
hook-at-hook-created completed 12 0ms ok 0
deadline-hook-wins completed 7 1.0h ok 0
deadline-expires completed 7 1.0h ok 0
long-sleep completed 11 30.0d ok 0
hook-never-arrives stalled 3 0ms skipped 0
step-retries-twice completed 10 2.0s ok 0
parallel-steps completed 9 0ms ok 0
hook-on-execution-state completed 12 0ms ok 0
peek-hook-before-branch completed 12 0ms ok 0
peek-hook-after-branch completed 12 0ms ok 0
peek-hook-at-registration completed 12 0ms ok 0
race-hook-before-probe completed 12 0ms ok 0
race-hook-after-probe completed 12 0ms ok 0
race-duplicate-delivery completed 13 0ms ok 0
attr-hook-before-step completed 11 0ms ok 0
attr-hook-after-step completed 11 0ms ok 0
attr-from-step-body completed 13 0ms ok 0
fork-hook-after-timeout completed 14 1.0m ok 0
fork-hook-before-timeout completed 14 1.0m ok 0
count-hook-after-timeout completed 17 1.0m ok 0
count-hook-before-timeout completed 20 1.0m ok 0
stale-read-step-count-fork completed 20 1.0m ok 0
stale-read-equal-step-counts completed 14 1.0m ok 0
step-vs-step-fork completed 12 0ms ok 0
step-vs-step-fork-fenced completed 12 0ms ok 0
fence-catches-benign-direction completed 12 5ms ok 0
in-flight-before-decision completed 17 1.0m ok 0
in-flight-before-decision-counted completed 17 1.0m ok 0
in-flight-after-decision completed 19 2.0m ok 0
stale-read-step-count-fork-fenced completed 20 1.0m ok 0
fork-hook-wins completed 13 1.0m ok 0
fork-timeout-wins completed 13 1.0m ok 0
unclaimed-payload-under-fork completed 17 1.0m ok 0
claimed-payload-under-fork completed 17 1.0m ok 0
writers-independent-step-bodies completed 12 0ms ok 0
writers-scripted-tempo completed 12 0ms ok 0
cancel-mid-step cancelled 7 0ms skipped 0

Full trace: world-sim.txt

VaguelySerious and others added 5 commits August 11, 2026 11:16
Found by migrating the workflow-examples repo from 4.3.1 to a 5.0.0-beta
tarball. Both breaks hit application code, but neither was in the
app-facing breaking-changes table:

- getWorld()/getWorldHandlers()/createWorld() became async. This broke
  the instrumentation.ts bootstrap in two examples. TypeScript catches
  it; plain JavaScript does not, and the World worker silently never
  starts.
- The standalone bundles were renamed and the step route was removed,
  which breaks any server that wires `workflow build` output by hand.
  The mechanics were already correct in how-it-works/framework-
  integrations; this only adds the pointer from the summary table and
  names the filenames in the build-integration table.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…guard

Catches the World docs up with #3519, which removed two
capability flags. Both changes matter to whoever is porting a World, and one of
them invalidates advice this guide currently gives.

**Slot-numbered event IDs are a requirement, not a capability.** `slotEventIds`
is gone. The runtime reads a position out of every ID it loads and fails the run
when it cannot, so there is no flag to set and no fallback: a World whose IDs
are not positions type-checks, starts runs, and fails on the first replay with
`Event id is not slot-numbered`. Both pages say so where it comes up, and the
allocation section leads with it.

**Existing runs cannot come along.** The upgrade guide said a World switching
slots on "keeps replaying its existing ULID-numbered runs unchanged". That is
now false and it is the sentence most likely to cost someone a production
incident, so it is replaced with a warning: drain those runs on 4.x first, or
accept that in-flight ones fail. Platforms that pin a run to its creating
deployment resolve this for free; a single deployment serving every run does
not.

**A stale replay does not have to be refused.** `preconditionGuard` is gone,
and nothing replaced it — allocating positions at the commit makes a reader's
log a prefix rather than a prefix with a hole, replay is deterministic on a
prefix, and a write reports the events it was pushed past. A World that
implemented the guard can delete it. The section survives for the one case that
still wants it, a store that allocates positions away from the commit, and now
says that up front instead of presenting the guard as a normal option.

Also adds the requirement that was implicit: allocate the position *at the
commit*, since that is what the prefix property rests on. And points the
verification section at `@workflow/world-testing`'s new `numbers events by
position`, which turns a first-replay failure into one line of test output.

Not touched: `runtime-tuning.mdx` still documents a `WORKFLOW_PRECONDITION_GUARD`
env var that no longer exists. That section is inherited from the older main
this branch is based on, not authored here, and rebasing picks up the rewrite
rather than conflicting with it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Same two stale claims the guide carried, in the skill that agents actually
follow: it told them to declare `capabilities.slotEventIds`, which no longer
exists, and that existing ULID-numbered runs keep replaying, which is now the
opposite of true. Both replaced, plus the allocate-at-the-commit requirement,
a pointer at `world-testing`'s new conformance case, and a note that a World
which implemented `preconditionGuard` can delete it.

Version bumped to 0.2.8 per the repo convention for skill edits.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
# Conflicts:
#	docs/content/docs/v5/api-reference/workflow-errors/precondition-failed-error.mdx
#	docs/content/worlds/v5/building-a-world.mdx
…n skill

Two pieces, both for a World implementer rather than an application.

- The Spec versions section is rewritten for #3542. The accepted range
  narrows to a single version, because slot-numbered event ids stopped
  being optional, so a World declaring 5 is now refused before its first
  run rather than partway through one. The guidance that mattered stays
  the same and gets a reason: declare SPEC_VERSION_CURRENT, not a
  literal, and not SPEC_VERSION_SUPPORTS_SLOT_IDENTITY, which is a
  literal by another name.
- skills/migrating-world-v4-to-v5 is a second migration skill, for the
  World rather than the app. The two jobs share almost no surface, and
  the World half is the one where a type-clean port still fails on the
  first replay, so it leads with event id allocation and treats the
  signature changes as the small half. The application skill now hands
  that work off instead of summarizing it.

Also drops what the main merge left stale: the precondition guard row in
whats-new and the guard bullet in the application skill (the capability,
the env var and the stateUpdatedAt triple are all gone), and the
eventCount-supersedes-the-triple paragraph in the upgrade guide.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@pranaygp

Copy link
Copy Markdown
Contributor

Heads-up for the v5 docs flip / what's-new page: #3025 (in review) adds a new optional World API — events.createBatch — plus the WORKFLOW_BATCH_TRANSITIONS runtime flag, with two doc changes that will land under content/docs/v5:

  • a new changelog entry changelog/batched-event-writes (World spec addition: createBatch signature + contract, and the batched suspension fan-out), registered in the changelog meta.json;
  • a WORKFLOW_BATCH_TRANSITIONS entry in configuration/worlds.

Once #3025 lands, this PR (and the what's-new page) should be updated to mention the batched event writes — and if this PR merges first, #3025's docs will need the unprefixed-path treatment applied here (its changelog link currently uses /docs/changelog/batched-event-writes). Flagging now so neither lands stale.

🤖 Generated with Claude Code

VaguelySerious and others added 2 commits August 21, 2026 11:47
# Conflicts:
#	docs/content/docs/v5/configuration/worlds.mdx
#	docs/content/worlds/v5/building-a-world.mdx
This PR now adds the two new pages and the sidebar drill-in change, and
leaves v5 as the pre-release version. Making v5 the default is a separate
PR stacked on this one, so the routing move can be reviewed on its own
rather than as the smaller half of a content diff.

Reverted here, and restored there: the version tables and prefixes
(`lib/geistdocs/versions.ts` and everything reading it), the redirect
rules, the `app/[lang]/v5/**` → `app/[lang]/v4/**` move, the
pre-release/maintenance banner rename, the worlds version plumbing, the
link lint's two URL spaces, the smoke checks asserting the flipped
semantics, and `whats-new` in the v5 sidebar.

Three things had to be re-authored rather than simply reverted, because
they are the new pages' machinery and not the flip's:

- `worlds-guide-page.tsx` is the generalization of `building-a-world-page.tsx`
  from one hard-coded slug to a slug parameter, which is what lets
  `upgrading-to-v5` reuse it. It keeps the pre-release version mapping.
- The `upgrading-to-v5` route moves under `app/[lang]/v5/worlds/`, since
  that is where v5 world pages are served from until the flip.
- The docs layout moves into `[[...slug]]` in both trees, not just the
  unprefixed one, so the sidebar change applies to v5 as well.

Two cross-version links are dropped and come back with the flip: v5
pages cannot link to v4 pages while v4 is the unprefixed version, because
an unprefixed href on a v5 page is rewritten into the /v5 view. The prose
now points at the version picker instead.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

Backport to stable failed for b3dbc6d due to a workflow error (backport job run).

This is usually an infrastructure problem (e.g. the configured AI model could not be found, an AI Gateway error, or an opencode crash) rather than a merge conflict. Check the job logs linked above for details.

Once the underlying issue is fixed, re-run the Backport to stable workflow manually via workflow_dispatch and paste this commit SHA into the ref input:

b3dbc6d2643bb3020c1099c3efc611c9292f69f3

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.

4 participants