Backport #2055 + #2415 to stable: [world-vercel] v4 event wire format + lazy metadata-only listings#2414
Conversation
🦋 Changeset detectedLatest commit: 10dd0d4 The changes in this PR will be included in the next version bump. This PR includes changesets to release 20 packages
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 |
🧪 E2E Test Results❌ Some tests failed Summary
❌ Failed Tests🌍 Community Worlds (99 failed)redis (19 failed):
turso (80 failed):
Details by Category✅ ▲ Vercel Production
✅ 💻 Local Development
✅ 📦 Local Production
✅ 🐘 Local Postgres
✅ 🪟 Windows
❌ 🌍 Community Worlds
✅ 📋 Other
|
|
(AI) Updated this backport to actually work on The auto-backport was a clean git merge, but #2055's v4 wire format has a hard dependency on a runtime refactor that landed on newer lines but is not on
As-is, the v4 split's This update carries stable's structured errors in the frame meta and decodes them back on read — no breaking change, no |
Signed-off-by: Peter Wielander <mittgfu@gmail.com>
The v4 wire format assumes the runtime hands `events.create` a dehydrated
`Uint8Array` for every payload field. On this line run/step errors are NOT
dehydrated — the runtime emits them as a plain string (step_failed /
step_retrying) or a `{ message, stack }` object (run_failed). The split's
Uint8Array guard therefore threw on every failure event.
Route a non-Uint8Array `error` field (plus the sibling `stack`) into the
frame meta instead, so the backend rebuilds the same StructuredError the
pre-v4 wire produced. On read, the backend returns that StructuredError as
the ref's CBOR bytes in the frame body, so decode the structured-error
event types back into `eventData.error` — the core step-event reducer
reads `.message` / `.stack` off it directly and has no hydrate step for
errors on this line.
Also drops the native-run-attributes and webhook/system-hook meta fields
from the wire allowlist: those eventData fields do not exist on this
line's @workflow/world schema, and the schema-derived exhaustiveness guard
flags them as stale. Requires the matching backend change to parse
`error` / `stack` from the v4 frame meta.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The runtime emits `isWebhook` on hook_created (the suspension handler) and the backend reads it to mark webhook hooks, which must not be resumable via the public webhook endpoint. The schema-derived wire-allowlist guard had flagged `isWebhook` as stale because this line's @workflow/world HookCreatedEventSchema did not declare it — so it was dropped, breaking webhook hooks on the v4 wire. Declare `isWebhook` on HookCreatedEventSchema (matching the field the runtime already sends and the backend already consumes) and route it through the v4 frame meta. Unlike isWebhook, isSystem / native run attributes / attr_set are genuinely absent from this line's runtime and schema, so they stay off the wire. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
getWorkflowRunEvents applied resolveData only client-side: it always downloaded the resolved payload bytes for every event, then discarded them when resolveData was 'none'. The v4 list endpoints accept remoteRefBehavior (resolve|lazy, default resolve), so map resolveData 'none' → lazy and send it on the runId and correlationId list queries — the backend then emits empty-body frames and skips the per-event blob read. Safe against a backend that predates the flag: it ignores the param and streams full bodies, and buildEventFromV4 still strips them for resolveData 'none', so this is a pure bandwidth optimization either way. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Peter Wielander <mittgfu@gmail.com>
The structured-error decode test (stable-only, from this backport) lists events with resolveData 'all', which the lazy-refs change maps to remoteRefBehavior=resolve on the wire. Match the new query param in the interceptor so the request resolves against it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
668c968 to
6b696e9
Compare
decodeFrames never cancelled response.body when a consumer stopped reading before EOF — getEventV4 returns after the first frame and consumeListFrameStream breaks at the sentinel — so the undici connection stayed checked out of the pool (8 per origin) instead of being released, causing stalls/timeouts on the event-read path. Cancel the source in a try/finally (and cancel the reader in readerToIterator) via a shared closeQuietly helper. Add regression tests for both decode branches and a getEventV4 HTTP round-trip through undici MockAgent. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
| "@workflow/world": patch | ||
| --- | ||
|
|
||
| New internal API format: separately encode event metadata from user payloads. Eliminates the need for calling separate endpoints for ref resolution, which improves performance especially on longer runs. |
There was a problem hiding this comment.
AI review: Checked that PR #2414 is a stable backport of #2055, #2415, and #2547; all three source PRs are closed and merged into main, and each had review/approval activity before merge. I also checked that the stable-only adaptations called out in this PR body are limited to the v4 event wire-format/schema compatibility needed for the backport.
Combined backport to
stableof two main-line changes to the v4 event wire format:getWorkflowRunEvents({ resolveData: 'none' })now sendsremoteRefBehavior=lazyso the backend emits empty-body frames instead of streaming payload bytes the caller would discard.Unlike the clean cherry-pick the backport bot assumed, #2055's wire format was co-designed with a runtime change that only exists on newer lines (there, the runtime serializes run/step errors into opaque bytes before
events.create). On this line errors are still plain values, so this PR adapts the adapter — plus one small, additive@workflow/worldschema fix — to carry them correctly. No breaking change:WorkflowRun.error/Step.errorkeep theirStructuredErrorshape.What this does
v4 wire format (#2055)
[u32_be meta_len][cbor meta][u32_be body_len][bytes]. Metadata rides in the CBOR meta; the user payload is the opaque body./refsround-trip.EventResult/Event/PaginatedResponse<Event>shapes the runtime consumes are unchanged.Lazy refs on metadata-only listings (#2415)
getWorkflowRunEventsmapsresolveData: 'none'→remoteRefBehavior: 'lazy'and sends it on both the runId and correlationId list queries, so the backend skips the per-event blob read and emits empty-body frames.resolveData: 'all'sendsresolve(the default), unchanged.remoteRefBehaviorparam (same one v2/v3 use); no new wire concept.buildEventFromV4still strips them forresolveData: 'none'— so it's purely a bandwidth optimization that kicks in once the backend honors the flag.Stable-specific adaptations (vs. #2055)
step_failed/step_retrying) or a{ message, stack }object (run_failed), not dehydrated bytes. They are carried in the frame meta and decoded back on read, so the Vercel backend (world-vercel) materializes the sameStructuredErrorthe previous wire produced — the replay reducer reads.message/.stackdirectly and there is no hydrate step for errors on this line. Depends on the matchingworld-vercelbackend change (parseerror/stackfrom the v4 frame meta) being deployed first; until then the error-path E2E lanes report "Unknown error".hook_created.isWebhookis kept on the wire and declared onHookCreatedEventSchema— the runtime emits it and the backend consumes it to reject public-webhook-endpoint resumption.attr_set/isSystem). A schema-derived exhaustiveness guard keeps the wire allowlist in sync with the event schema in both directions.resolveData: 'all', which [world-vercel] Send remoteRefBehavior=lazy on v4 metadata-only event listings #2415 now maps toremoteRefBehavior=resolve; its mock interceptor was updated to match the new query param.Test plan
hook_created.isWebhookroutingremoteRefBehaviormapping —resolveData: 'none'→ lazy (strips any returned body), default → resolve (splices body bytes), on both runId and correlationId queriesworld-vercelunit suite (157 tests) + typecheck green; exhaustiveness guard compiles against this line's schemaworld-vercelbackend change is deployed, then re-run🤖 Generated with Claude Code