Commit Graph

17 Commits

Author SHA1 Message Date
Peter Wielander 1e6b1fdea2 Attributes MVP (experimental and write-only) and CI hardening (#2134)
* fix(core): scan inline sourcemaps during error remapping

* Attributes MVP (experimental and write-only) (#2088)
2026-05-28 18:06:46 +00:00
Pranay Prakash 9d2a9261fd Expose conflicting run id on hook conflicts (#2012)
* Expose conflicting run id on hook conflicts

* Mark hook conflict run id as future required

* Address hook conflict docs review

* Address hook conflict review comments

* Fix hook conflict docs typecheck
2026-05-18 17:31:20 -07:00
Nathan Rajlich 5f22832675 Serialize run_failed/step_failed errors through serialization pipeline (#1851)
* Serialize run_failed/step_failed errors through serialization pipeline

Switch run_failed, step_failed, and step_retrying events to persist
the full thrown value via the workflow serialization pipeline (as
SerializedData / Uint8Array) instead of a lossy { message, stack, code }
StructuredError shape. Consumers hydrate via hydrateRunError /
hydrateStepError to reconstruct the original thrown value, preserving
Error subclass identity, cause chains, and custom properties.

- WorkflowRun.error and Step.error are now SerializedData
- WorkflowRun gains a top-level errorCode plaintext field
- WorkflowRunFailedError.cause is now the hydrated thrown value
- Adds world-postgres migration 0010_add_error_code.sql
- Legacy pre-pipeline errorJson records surface as undefined on read

* Update Next.js workbenches for new WorkflowRunFailedError.cause type

cause is now `unknown` (the hydrated thrown value) rather than
`Error & { code }`. Defensively extract Error-shaped fields when the
hydrated value is an Error, otherwise round-trip the raw value, and
expose the new `errorCode` classification field.

* Update docs for WorkflowRunFailedError.cause: unknown

The hydrated `cause` is now `unknown` (the original thrown value
through the serialization pipeline) and the error classification has
moved to the top-level `errorCode` property. Update the two affected
docs pages and the `TSDoc` interface to reflect the new shape, and
narrow `cause` with `instanceof Error` before accessing fields.

* Expand test coverage for the run/step error serialization pipeline

Unit tests:
- 19 new dehydrate/hydrate{Step,Run}Error round-trip tests covering
  FatalError, plain Error, built-in Error subclasses, non-Error thrown
  values (string, plain object), cause chains, encryption round-trip,
  the binary format prefix contract, and the unserializable / unknown-
  format error paths.
- 5 new tests for Run.returnValue when the run is failed: hydrated
  FatalError + cause as cause, plain Error preservation, non-Error
  thrown values surfaced verbatim, cross-class cause chains, and the
  hydration-failure fallback that still surfaces errorCode.

E2E tests (new, in 99_e2e.ts + e2e.test.ts):
- Step throw → workflow catch round-trips a FatalError with a TypeError
  cause chain, asserting class identity, fatal marker, and cause name +
  message all survive the step_failed event pipeline.
- Workflow throw → run_failed reaches  status with the new
  top-level errorCode metadata exposed (cause-shape coverage lives at
  the unit level, since the SWC plugin's class registration is not
  invoked in the plain-Node e2e runner).
- Workflow throw of a non-Error value round-trips that value verbatim
  as WorkflowRunFailedError.cause.

Adjustments to existing assertions:
- error.cause is now ; tests narrow with
  and use the new top-level  field instead of .
- step.error / run.error from CLI --withData are now hydrated payloads:
  unregistered class instances surface as Instance refs whose
  carries the original message + stack.

Observability hydration:
- hydrateStepIO / hydrateWorkflowIO in serialization-format.ts now
  hydrate the  field via hydrateData, so the CLI and web UI
  continue to surface readable run/step error messages and stacks.

* Tighten error serialization changeset description

* Trim error serialization changeset to a single sentence

* Resolve FatalError/RetryableError revivers via cross-realm registry

When a workflow runs in a Node `vm` context, its bundled
`@workflow/errors` is a different module instance than the host's
import (separate prototype chains, separate class identity). Calling
`new FatalError(...)` from the host-side reviver produces a
host-realm instance that fails `err instanceof FatalError` checks
in the workflow code — even when the serialized payload was correctly
tagged via the dedicated `FatalError` reducer.

Surfaced by the local-prod e2e "step throw round-trips FatalError"
test on Next.js Turbopack: each route gets its own bundled chunk, so
the flow handler's `@workflow/errors` and the workflow VM bundle's
`@workflow/errors` are two distinct copies of the same module.

Fix:

- Each bundled copy of `@workflow/errors` self-registers its
  `FatalError` and `RetryableError` classes on `globalThis` via
  `Symbol.for("@workflow/errors//FatalError")` /
  `Symbol.for("@workflow/errors//RetryableError")`. First load wins
  per realm; the descriptor is non-writable / non-configurable to make
  accidental clobbering loud.

- The revivers in `@workflow/core`'s common reducers module read the
  consumer's `globalThis` (passed in as `global`) to pick up the
  realm-local class, falling back to the host-imported class when no
  registration is present (e.g. in the CLI / test runner).

* Use `types.isNativeError` to remap workflow stacks across VM realms

The runtime's run-failure path computes a source-map-remapped stack
and then assigns it back onto the thrown value via `if (err
instanceof Error) err.stack = errorStack`. Workflows run inside a
Node `vm` context, so a workflow-thrown error is an instance of the
VM realm's `Error` — `instanceof` against the host realm's
`Error` returns `false`, the assignment is skipped, and the
serialized `run_failed` event carries the un-remapped (bundled-line-
number) stack instead of the source-mapped one.

Switch the gate to `types.isNativeError`, which uses V8's internal
type tag and works across realms — same approach already in place
for the serialization reducers.

Caught by the local-prod e2e "nested function calls preserve message
and stack trace" and "cross-file imports preserve message and stack
trace" tests, which assert that the persisted run-error stack
contains `99_e2e.ts` / `helpers.ts`.

* Sync CLI revivers with core + add toJSON shim for Error subclasses

Two issues with the CLI's hand-rolled reviver list:

1. It hadn't been updated for the new first-class Error subclass
   reducers (`TypeError`, `RangeError`, `FatalError`, `RetryableError`,
   etc.). devalue throws "Unknown type X" when it encounters a
   reduced value with no matching reviver, and `hydrateResourceIO`
   swallows that error and surfaces the raw `Uint8Array` payload —
   so `step.error` / `run.error` showed up as raw byte dumps in
   `workflow inspect` output.

2. Even with all the right revivers, `Error.prototype`'s `message`
   / `stack` / `cause` are non-enumerable, so `JSON.stringify`
   (used by `workflow inspect --json`) drops them — leaving the
   subclass-specific enumerable fields (e.g. `FatalError.fatal`)
   visible but the actual error data missing.

Fix:

- Build the CLI reviver set on top of `getCommonRevivers()` from
  `@workflow/core` so the CLI stays in sync with the runtime's
  reducer set automatically. New core reducers/revivers will Just
  Work without any CLI-side change.

- Wrap each Error reviver from the common set with a thin shim that
  attaches a non-enumerable `toJSON` method to the produced
  `Error` instance. `JSON.stringify` calls `toJSON` and gets a
  full object (`name` + `message` + `stack` + `cause` + any
  enumerable subclass fields like `fatal` / `retryAfter` /
  `errors`); `util.inspect` ignores `toJSON` and renders the
  canonical `Error: msg\\n at ...` format. Best of both worlds for
  CLI output without compromising the runtime hydration path.

Caught by the local-prod e2e "basic step error preserves" and
"cross-file step error preserves" tests, which read
`failedStep.error.message` / `.stack` from the CLI's JSON output.

* Clarify parseErrorJson JSDoc to match its always-null return

The previous JSDoc described preserving legacy values "for best-effort
hydration" which contradicted the implementation, where legacy errors
are intentionally surfaced as absent (the pre-pipeline shapes can't be
hydrated by the new error revivers). Rewrite the comment so the contract
matches behavior. Also rename the now-unused parameter to `_errorJson`
to reflect that the function ignores it.

Caught by a code review on #1851.

* Refine error-handler ergonomics on the step / run hot paths

Three review-driven adjustments that all touch the queue handlers and
their interaction with the error serialization pipeline:

1. Memoize the per-run encryption key fetch. The step handler used to
   eagerly fetch + import the key at the top of every step delivery so
   the value would be in scope for every potential dehydrateStepError
   path. That pessimized step-started early-return cases (the fetch
   happens unconditionally even when the step never reaches user code)
   and required duplicating the same boilerplate at four call sites in
   runtime.ts. Introduce `memoizeEncryptionKey(world, run)` in
   runtime/helpers.ts that returns a lazy, single-fetch accessor;
   step-handler / runtime call sites use `await getEncryptionKey()`
   instead. The first caller pays the fetch cost, subsequent callers
   await the cached promise, and steps that fail before any
   encryption-aware work happens skip the fetch entirely.

2. Preserve the prior attempt's serialized error as the cause on the
   defensive max-retries-exceeded `step_failed` re-invocation guard.
   The existing comment explicitly opted out of cause attachment, but
   the symmetric post-failure path below already does this and the
   reviewer is right that consumers shouldn't have to walk the
   step_retrying event history to recover the underlying error. Best-
   effort: if hydration of the prior `step.error` throws, fall back
   to a FatalError without cause rather than letting the event write
   itself fail.

3. Document the intentional `unflatten` throw in
   `hydrateStepError` / `hydrateRunError` for non-Uint8Array input.
   SDK version is pinned per workflow run via skew protection so the
   non-binary branch is dead in production; if a misshapen value
   reaches it, surfacing the throw via the surrounding o11y try/catch
   is more debuggable than masking it. Add a comment so future
   reviewers don't reach for a defensive fallback.

A standalone `falls back to plaintext` suggestion on the run_failed
key fetch was rejected: when encryption is configured we should fail
loudly rather than silently emit plaintext error data. The queue's
redelivery semantics will retry the key fetch; persistent KMS outages
get logged with the existing "persistent error preventing the run from
being terminated" message rather than a security regression.

* Hydrate `event.eventData.error` in event listings

`hydrateEventData` enumerated the per-event fields that need
hydration (`result`, `input`, `output`, `metadata`, `payload`)
but omitted the new `error` field on `step_failed`,
`step_retrying`, and `run_failed` events. Without this branch,
o11y tools that list events (e.g. `workflow inspect events`) surface
the raw `Uint8Array` payload instead of a hydrated
`{ name, message, stack, … }` object even though the entity-level
`Run.error` / `Step.error` paths already hydrate.

Mirrors the existing per-field branches; the `try/catch` leaves the
field un-hydrated on parse failure rather than failing the whole
event view. Adds a unit test.

* Use `.is()` static checks in `classifyRunError` for cross-realm safety

Workflows execute inside a separate `vm` realm: the
`WorkflowRuntimeError` class bundled into the workflow code and the
host-imported one are distinct constructors, so an
`err instanceof WorkflowRuntimeError` check on a VM-thrown error
returns `false` and we'd misclassify genuine runtime errors (corrupted
event log, missing timestamps, workflow/step not registered) as user
errors.

Switch to each subclass's `.is()` static (a name-based duck check that
works across realms). Since `WorkflowRuntimeError.is` only matches its
own concrete name, enumerate every concrete subclass we want to
recognize (`StepNotRegisteredError`, `WorkflowNotRegisteredError`)
in a `RUNTIME_ERROR_CHECKS` table; keep that table in sync with the
class hierarchy in `@workflow/errors`.

Existing `classify-error.test.ts` already covers `WorkflowRuntimeError`
and `WorkflowNotRegisteredError` cases — both still pass.

* Add e2e coverage for step throws of non-Error values

We had `errorWorkflowThrowNonErrorValue` (workflow body throws a plain
object — round-trips verbatim as `WorkflowRunFailedError.cause`) but
no symmetric coverage for the step-throw side. Step-throw goes through
a different code path: non-Error values aren't recognized as
`FatalError` (no `name === 'FatalError'`) nor `RetryableError`,
so they take the transient retry path. After max retries the runtime
wraps the original thrown value as `cause` on a fresh `FatalError`
which the workflow's catch block then sees.

Add a workflow that throws a recognizable plain object from a step
with `maxRetries = 0` (so we exhaust on first attempt and avoid a
long test wait) and a workflow that asserts the wrapped FatalError
shape: `isFatal`, `instanceof FatalError`, message includes the
original object's serialized form, `cause` is the original non-Error
object verbatim with structure preserved.

Documents the current retry-then-wrap behavior so any future change
to "non-Error throws skip retries" semantics has to update the test.

* Note legacy postgres error-data loss in the run/step error changeset

Pre-upgrade failed runs that wrote into world-postgres's deprecated
`error` text column can't be hydrated through the new pipeline (the
shape is incompatible with the new revivers). The new runtime
intentionally surfaces them as `error: undefined` on read; the
original payload is still readable directly from the `errorJson`
column for manual inspection. Add a one-sentence note to the
changeset's migration text so consumers upgrading don't get blindsided
by suddenly-empty error fields on historical runs.
2026-05-04 15:18:46 -07:00
Nathan Rajlich 7c45e9e213 Enforce per-(run, correlation) uniqueness for entity-creating events in world-postgres (#1878)
Adds a unique partial index on workflow_events(run_id, correlation_id, type)
filtered to step_created/hook_created/wait_created, and translates the
resulting unique-violation (pg code 23505, surfaced via DrizzleQueryError.cause)
into EntityConflictError. The steps table already deduped via
onConflictDoNothing, but the event row still inserted, leaving duplicate
events in the log. Now both rows are kept consistent and the runtime's
existing dedup catch path handles concurrent writers cleanly.
2026-05-04 19:36:32 +00:00
Peter Wielander 66d49c0db6 [world] Restructure stream interface, require run ID for all step and stream operations (#1293) 2026-04-09 13:25:16 -07:00
Peter Wielander 5f138f2cee [core] [world] Gate CBOR queue transport on specVersion (#1627) 2026-04-07 09:36:51 -07:00
James Berry 5502438bac [world-postgres] Migrate client from postgres.js to pg (#1484) 2026-03-23 16:30:27 -07:00
Pranay Prakash aee035f944 refactor: replace HTTP status code checks with semantic error types (#1342)
* feat: classify run failure error codes and improve error logging

- Add RUN_ERROR_CODES (USER_ERROR, RUNTIME_ERROR) to @workflow/errors
- Populate errorCode in run_failed events via classifyRunError()
- Update web UI StatusBadge to show amber dot for infrastructure errors
- Improve world-local queue error logging (concise, no body dump)
- Improve schema validation error messages (concise, verbose behind DEBUG)
- Add e2e tests for error code flow and infrastructure error retry

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

* feat: add semantic error types to replace HTTP status code checks in runtime

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

* feat: classify run failure error codes and improve error logging

- Add RUN_ERROR_CODES (USER_ERROR, RUNTIME_ERROR) to @workflow/errors
- Populate errorCode in run_failed events via classifyRunError()
- Update web UI StatusBadge to show amber dot for infrastructure errors
- Improve world-local queue error logging (concise, no body dump)
- Improve schema validation error messages (concise, verbose behind DEBUG)
- Add e2e tests for error code flow and infrastructure error retry

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

* feat: classify run failure error codes and improve error logging

- Add RUN_ERROR_CODES (USER_ERROR, RUNTIME_ERROR) to @workflow/errors
- Populate errorCode in run_failed events via classifyRunError()
- Update web UI StatusBadge to show amber dot for infrastructure errors
- Improve world-local queue error logging (concise, no body dump)
- Improve schema validation error messages (concise, verbose behind DEBUG)
- Add e2e tests for error code flow and infrastructure error retry

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

* address PR review comments

- Remove dead `meta` option from TooEarlyError constructor (TooTallNate)
- Extract `throwWithTrace` helper to deduplicate span recording in
  world-vercel makeRequest (TooTallNate)
- Restore `maxAttempts` const for stable retry count logging (TooTallNate)
- Fix behavioral regression: add WorkflowAPIError 404 fallback in
  suspension-handler hook disposal to handle world-vercel path where
  makeRequest doesn't map 404 to HookNotFoundError (TooTallNate)

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

* fix: translate 404 to HookNotFoundError at the world-vercel boundary

Move the 404 → HookNotFoundError translation into world-vercel's
createWorkflowRunEvent, where we know the event type context. For
hook-related events (hook_created, hook_disposed, hook_received,
hook_conflict), a 404 from the server means the hook was not found.

This removes the WorkflowAPIError 404 fallback from the runtime's
suspension-handler, keeping the runtime fully decoupled from HTTP
status codes.

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

* fix: parse Retry-After for 425 responses and narrow hook event set

- Parse Retry-After header unconditionally so TooEarlyError gets
  the server-provided delay instead of always falling back to ~1s
- Narrow hookEventsRequiringExistence to only hook_disposed and
  hook_received (matching world-local's set), since hook_created
  and hook_conflict don't imply the hook must already exist

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

* rename WorkflowAPIError to WorkflowWorldError

Breaking change: rename WorkflowAPIError → WorkflowWorldError to
better reflect that this error represents world (storage backend)
failures, not HTTP API errors specifically. Updated across all
packages: errors, core, world-local, world-vercel, world-postgres,
workflow, and web.

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

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 15:07:22 -07:00
Nathan Rajlich 1060f9d04a Change user input/output to be binary data at the World interface (#853) 2026-01-28 10:36:28 -08:00
Pranay Prakash 4966b728a8 implement event-sourced architecture (#621)
* perf: implement event-sourced architecture

* Apply suggestions from code review

* Improve invalid event log handling in step/hook/wait

* Handle serialized workflow run errors correctly

* log error in failing test

* Handle queue idempotency in vercel world

* hotfix for error propogation

* Fix: Incorrect HTTP status code 409 should be 410 for terminal run state rejections in postgres storage

* Fix: The code attempts to pass an unsupported `fatal` property when creating a `step_failed` event. The TypeScript schema for `step_failed` events only allows `error` and `stack` properties, so the `fatal` property causes a compilation error.

This commit fixes the issue reported at packages/core/src/runtime/step-handler.ts:133-139

## TypeScript error: Invalid property 'fatal' in step_failed event

**What fails:** TypeScript compilation fails in `packages/core` due to an invalid property in the `step_failed` event creation.

**How to reproduce:**
```bash
cd /vercel/sandbox/primary
pnpm run -F @workflow/core build
```

**Result:**
```
src/runtime/step-handler.ts(133,32): error TS2769: No overload matches this call.
  Overload 1 of 2, '(runId: null, data: { eventType: "run_created"; ... }, gave the following error.
    Argument of type 'string' is not assignable to parameter of type 'null'.
  Overload 2 of 2, '(runId: string, data: CreateEventRequest, params?: CreateEventParams | undefined): Promise<EventResult>', gave the following error.
    Object literal may only specify known properties, and 'fatal' does not exist in type '{ error: any; stack?: string | undefined; }'.
```

**Issue:** The code attempted to pass a `fatal: true` property in the `eventData` object when creating a `step_failed` event. However, the event schema defined in `packages/world/src/events.ts` for `StepFailedEventSchema` only allows `error` and `stack` properties - the `fatal` property is not part of the schema.

Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>

* Fix: Code silently skips updating workflowRun if result.run is undefined, causing workflows to be incorrectly skipped instead of throwing an error

* Add hook_conflict event type for duplicate token detection

Implements hook_conflict events across all world implementations to handle
cases where a workflow attempts to use a hook token already claimed by another
workflow. Instead of throwing errors, the system now records hook_conflict
events in the event log, enabling deterministic replay.

- Add HookConflictEvent schema to @workflow/world
- Implement hook_conflict in world-local, world-postgres, and suspension-handler
- Update hook consumer to reject promises with WorkflowRuntimeError on conflict
- Add HOOK_CONFLICT error slug with documentation
- Add e2e and unit tests for conflict scenarios

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

* Add changeset for hook_conflict events

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

* Add unit tests for hook_conflict handling

- Add tests for hook_conflict event in workflow.test.ts
- Fix world-postgres test to not expect removed message field

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

* Improve hook-conflict.mdx error guide

- Remove redundant third point in 'Why This Happens' section
- Add example showing how to handle WorkflowRuntimeError for hook conflicts

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

* Fix docs validation: add hook-conflict to errors index

- Fix broken link in hook-conflict.mdx (/docs/foundations/webhooks -> /docs/api-reference/workflow/create-webhook)
- Add hook-conflict to errors index page so it's discoverable by the docs link validator

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

* Fix world-local tests for hook_conflict event behavior

Update tests to expect hook_conflict events instead of thrown errors when
duplicate hook tokens are used. This aligns with the new event-sourced
approach where conflicts are recorded as events rather than thrown.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

* Add specVersion property to World interface for backwards compatibility

- Add specVersion property to World interface to track world package version
- Add specVersion to WorkflowRun schema and run_created event data
- World implementations (vercel, local, postgres) set specVersion from npm version
- Server can use specVersion to route operations based on world version
- Add specVersion display to observability UI attribute panel
- Add spec_version column to postgres runs schema

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

* Add migration for spec_version column in postgres schema

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

* Add drizzle migration journal and snapshot for spec_version column

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

* Regenerate postgres migration using drizzle-kit

- Properly generates migration with drizzle-kit CLI
- Removes deprecated 'paused' status from enum
- Adds spec_version column

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

* Add backwards compatibility for event-sourced runs

- Add RunNotSupportedError for runs requiring newer world versions
- Add semver-based version utilities (isLegacyVersion) to @workflow/world
- World implementations check specVersion and route to legacy handlers
- Legacy runs (< 4.1.0): run_cancelled skips event storage, wait_completed stores event only
- New runs always get current world version (4.1.0-beta.0)
- Make EventResult.event optional for legacy compatibility

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

* Refactor spec version from semver strings to integers

Replace semver-based version compatibility with explicit integer spec versions:
- SPEC_VERSION_LEGACY (1): pre-event-sourcing runs
- SPEC_VERSION_CURRENT (2): event-sourced architecture

Use branded SpecVersion type to enforce importing from @workflow/world.
Remove semver dependency from world, world-local, and world-postgres.

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

* Store error as CBOR in postgres world for consistency with input/output

- Add error_cbor bytea columns to workflow_runs and workflow_steps tables
- Deprecate text error column, rename to errorJson with fallback parsing
- Remove JSON.stringify from error writes (run_failed, step_failed, step_retrying)
- Add parseErrorJson helper for backwards compatibility with legacy data

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

* Use WorkflowRuntimeError and improve run entity handling in core runtime

- Replace generic Error with WorkflowRuntimeError for runtime assertions
- Add explicit check for run entity in run_created response
- Use run.runId instead of event.runId for consistency
- Use actual run status instead of hardcoded 'pending' in attributes

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

* Add specVersion to Step, Hook, and Event entities

- Add specVersion field to Step, Hook, and Event interfaces in @workflow/world
- Add spec_version column to steps, hooks, events tables in postgres schema
- Set specVersion to SPEC_VERSION_CURRENT when creating entities in all worlds
- Update migration to include spec_version columns for all entity tables

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

* Refactor world-local storage into modular files

Split storage.ts (1041 lines) into smaller, focused modules:
- storage/filters.ts: Data filtering helpers
- storage/helpers.ts: ULID and date utilities
- storage/hooks-storage.ts: Hook CRUD operations
- storage/legacy.ts: Legacy event handling
- storage/runs-storage.ts: Run get/list operations
- storage/steps-storage.ts: Step get/list operations
- storage/events-storage.ts: Event create/list operations
- storage/index.ts: Main composition

Also extracted test helpers to test-helpers.ts for reusability.

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

* Remove genversion and World.specVersion property

The World.specVersion string property was never actually read - only
the numeric SPEC_VERSION_CURRENT is used for backwards compatibility.

- Remove genversion dependency and generated version.ts from @workflow/world
- Remove specVersion property from World interface and all implementations
- Minor fix: correct error message to reference 'workflow' package
- Minor fix: correct error source priority in world-vercel events.ts
- Minor fix: update comment in runtime.ts

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

* Remove genversion from world-local, world-postgres, and world-vercel

These packages no longer need genversion since we removed the
World.specVersion property in the previous commit.

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

* Remove version.ts from .gitignore files

No longer needed after removing genversion.

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

* Add legacy/backwards compatibility tests

Tests for world-local and world-postgres covering:
- Legacy runs (specVersion < 2 or null/undefined)
  - run_cancelled handling (updates run, no event stored)
  - wait_completed handling (stores event only)
  - Rejection of unsupported events
  - Hook cleanup on cancellation
- Future runs (specVersion > current)
  - Rejection with RunNotSupportedError
- Current version runs (normal processing)
- Legacy error parsing (errorJson field parsing)

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

* Add hook_received support for legacy runs

When resumeHook() is called on a legacy run (specVersion < 2), the
hook_received event was previously rejected. This adds support for
storing hook_received events on legacy runs without entity mutation,
matching the behavior of wait_completed.

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

* Fix missing genversion in world-vercel

Signed-off-by: Peter Wielander <mittgfu@gmail.com>

* Remove deprecated workflow_completed, workflow_failed, and workflow_started events

Replace with run_completed, run_failed, and run_started equivalents.

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

* Add specVersion to EventWithRefsSchema in world-vercel

The manually-created EventWithRefsSchema was missing the specVersion field,
which caused specVersion to be stripped when using lazy (refs) mode for events.

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

* Wire specVersion from client through world backends

- Core runtime now sends specVersion in run_created eventData
- world-local accepts specVersion from eventData (defaults to current)
- world-postgres accepts specVersion from eventData (defaults to current)

This matches workflow-server behavior where v2 endpoints accept
specVersion from the client, while v1 endpoints default to legacy.

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

* Move specVersion to event object level, propagate to entities

- Add specVersion to BaseEventSchema (event level, not eventData)
- Remove specVersion from RunCreatedEventSchema.eventData
- Core runtime sends specVersion on event object
- world-local reads specVersion from event, propagates to run/step/hook entities
- world-postgres reads specVersion from event, propagates to run/step/hook entities

This ensures specVersion flows from client through event to all created entities.

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

* Update specVersion to be optional in types for backwards compatibility

- specVersion is optional in all entity schemas (runs, steps, hooks, events)
  for backwards compatibility with legacy data in storage
- Runtime always sends specVersion on event requests
- world-local and world-postgres provide fallback to SPEC_VERSION_CURRENT
- Test helpers include specVersion in all event creation calls
- EventWithRefsSchema in world-vercel defaults specVersion to 1 for legacy

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

* Fix world-vercel queue tests missing VERCEL_DEPLOYMENT_ID setup

Two tests were calling queue.queue() without setting up
VERCEL_DEPLOYMENT_ID, causing them to fail with "No deploymentId
provided" error before reaching the code they were testing.

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

* Add specVersion to all event creation calls in core package

The server's CreateEventSchemaV2 requires specVersion on all events,
but only run_created was sending it. This caused 400 Bad Request errors
for all other event types (run_started, run_completed, run_failed,
run_cancelled, step_created, step_started, step_completed, step_failed,
step_retrying, hook_created, hook_received, wait_created, wait_completed).

Now all event creation calls include specVersion: SPEC_VERSION_CURRENT.

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

---------

Signed-off-by: Peter Wielander <mittgfu@gmail.com>
Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Peter Wielander <mittgfu@gmail.com>
2026-01-22 17:24:52 -08:00
Peter Wielander dd3db13d54 [world] Remove pause and resume events, actions and states (#751) 2026-01-08 19:29:17 +01:00
Gal Schlezinger 10ce313d56 postgres: fix tests (#394)
* postgres: use non-deprecated drizzle signatures

Signed-off-by: Gal Schlezinger <gal@spitfire.co.il>

* postgres: store metadata in the hook

Signed-off-by: Gal Schlezinger <gal@spitfire.co.il>

* core: do not rely on module cache for world config. instead, use a global and a symbol.

this makes sure that streamers can use in-memory event emitters and that it won't be compiled away into the different flow.js and step.js files.

this was figured out when i was adding a hooks tests to world-testing.

Signed-off-by: Gal Schlezinger <gal@spitfire.co.il>

* add postgres world to all workbench packages

we try to run them with the postgres world but it's not installed

Signed-off-by: Gal Schlezinger <gal@spitfire.co.il>

* Replace jsonb with cbor because zero byte does not work in jsonb :(

Signed-off-by: Gal Schlezinger <gal@spitfire.co.il>

* fix error handling: attempts start at 0 now, and not 1 like when we released. so initial attempt in postgres should reflect that.

Signed-off-by: Gal Schlezinger <gal@spitfire.co.il>

* drain stuff

Signed-off-by: Gal Schlezinger <gal@spitfire.co.il>

* fallback metadata to metadataJson

Signed-off-by: Gal Schlezinger <gal@spitfire.co.il>

* Make code more readable

Signed-off-by: Gal Schlezinger <gal@spitfire.co.il>

* apply Vade fix

Signed-off-by: Gal Schlezinger <gal@spitfire.co.il>

---------

Signed-off-by: Gal Schlezinger <gal@spitfire.co.il>
2025-11-23 11:05:20 -08:00
Adrian 281292cdd8 fix: world-postgres tests (#314)
* fix: world-postgres db:push script

* fix: test truncating wrong table names
2025-11-12 10:05:11 -08:00
Pranay Prakash 00b0bb9346 Proper error stack propogating (#280)
* Proper stacktrace propogation in world

Proper stacktrace propogation in world

* Merge Reconciliation

* Standardize the error type in the world spec

* Deduplicate vercel world utils

* fix undefined type issue

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2025-11-11 15:34:04 -08:00
Peter Wielander 0ce3197d02 Disable postgres world tests running in win32, fix container setup for ubuntu (#215) 2025-11-04 18:27:08 -08:00
Copilot 4a821fce7c Fix Windows support by normalizing path separators in workflow IDs (#150)
---------

Signed-off-by: Nathan Rajlich <n@n8.io>
Co-authored-by: Nathan Rajlich <n@n8.io>
2025-11-04 12:13:45 -08:00
Gal Schlezinger 4ca9a3edbd Introducing Workflow DevKit
build durable, resilient, and observable workflows.

Co-authored-by: Nathan Rajlich <n@n8.io>
Co-authored-by: Pranay Prakash <pranay.gp@gmail.com>
Co-authored-by: Adrian <me@adriandlam.com>
Co-authored-by: JJ Kasper <jj@jjsweb.site>
Co-authored-by: Vercel Release Bot <88769842+vercel-release-bot@users.noreply.github.com>
Co-authored-by: Peter Wielander <mittgfu@gmail.com>
Co-authored-by: Hayden Bleasel <hello@haydenbleasel.com>
Co-authored-by: Gal Schlezinger <gal@spitfire.co.il>
Co-authored-by: Manuel Muñoz Solera <mamuso@mamuso.net>
Co-authored-by: Garrett <garrett.tolbert@vercel.com>
Co-authored-by: Lars Grammel <lars.grammel@gmail.com>
Co-authored-by: Pooya Parsa <pyapar@gmail.com>
Co-authored-by: Tom Dale <tom@tomdale.net>
Co-authored-by: Vishal Yathish <135551666+visyat@users.noreply.github.com>
Co-authored-by: josh <144584931+dancer@users.noreply.github.com>
2025-10-23 12:07:52 +03:00