workflow 5.0.0-beta.41 → 5.0.0-beta.42

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,9 +8,9 @@ related:
8
8
  - /docs/api-reference/workflow-errors/entity-conflict-error
9
9
  ---
10
10
 
11
- `PreconditionFailedError` is thrown by world implementations when an event creation is rejected because the client's event-log snapshot is stale either a newer out-of-band event (such as a received hook or a completed step) was recorded after the snapshot the client replayed from, or the snapshot is missing an event recorded at or before it. It corresponds to HTTP 412 Precondition Failed semantics.
11
+ `PreconditionFailedError` is thrown by world implementations when an event creation is rejected because the client's event-log snapshot is stale: the log already held more events than the position the creation named. It corresponds to HTTP 412 Precondition Failed semantics.
12
12
 
13
- This only occurs while the optimistic-concurrency guard is enabled (`WORKFLOW_PRECONDITION_GUARD`, on by default see [Runtime Tuning](/docs/configuration/runtime-tuning)); event creations that carry no snapshot are never rejected with this error.
13
+ No world in this repository throws it. A stale replay does not need to be refused: its log is a prefix rather than a prefix with a hole in it, replay is deterministic on a prefix, and the write it makes next comes back carrying the events it was pushed past (see [Stale reads](/docs/configuration/runtime-tuning#stale-reads-and-why-nothing-has-to-be-rejected)). The error and the runtime's handling of it remain for a world that would rather refuse than report — one that allocates positions somewhere other than the commit, and so cannot report a gap reliably. Event creations that carry no position are never rejected with it.
14
14
 
15
15
  A world rejects only on evidence and accepts the creation whenever it cannot decide, so this error always means the snapshot really was stale — but not receiving it does not prove the snapshot was current.
16
16
 
@@ -22,6 +22,10 @@ keywords:
22
22
 
23
23
  `world.analytics` is an optional, read-only namespace for observability surfaces — dashboards, CLIs, and admin tools that list large numbers of runs without touching payload data.
24
24
 
25
+ For observability and inspection listings, prefer this namespace over
26
+ [`world.runs.list()`](/docs/api-reference/workflow-runtime/world/storage#runslist).
27
+ The storage API remains available for operational and payload-bearing reads.
28
+
25
29
  It differs from [Storage](/docs/api-reference/workflow-runtime/world/storage) in two ways:
26
30
 
27
31
  - **Metadata only.** Results never include run input/output, step data, or hook tokens. There is no `resolveData` option.
@@ -133,7 +133,10 @@ const result = await world.events.listByCorrelationId({ // [!code highlight]
133
133
 
134
134
  ## world.runs
135
135
 
136
- Materialized from run events. Use it to list and inspect workflow runs.
136
+ Materialized from run events. Use it for canonical operational reads, including
137
+ reads that require workflow input or output data. For observability dashboards,
138
+ inspection tools, and historical listings, use
139
+ [`world.analytics.runs.list()`](/docs/api-reference/workflow-runtime/world/analytics#runslist).
137
140
 
138
141
  ### runs.get()
139
142
 
@@ -163,6 +166,14 @@ const result = await world.runs.list({ // [!code highlight]
163
166
 
164
167
  **Returns:** `{ data: WorkflowRun[], cursor?: string }`
165
168
 
169
+ <Callout type="warn">
170
+ Observability and inspection usage of `world.runs.list()` is deprecated. Use
171
+ [`world.analytics.runs.list()`](/docs/api-reference/workflow-runtime/world/analytics#runslist)
172
+ for metadata-only, plan-aware queries backed by the observability pipeline.
173
+ `world.runs.list()` remains supported for operational and payload-bearing
174
+ reads.
175
+ </Callout>
176
+
166
177
  ### Cancelling Runs
167
178
 
168
179
  To cancel a run, create a `run_cancelled` event via `world.events.create()` (see [world.events](#worldevents) above), or use the CLI or Web UI helpers.
@@ -71,17 +71,23 @@ For example, a workflow can run a 10-minute inline step even with `WORKFLOW_REPL
71
71
  - Only applies to Worlds with atomic, immutable deployments (the Vercel World). A run whose pinned deployment cannot be reached at all fails immediately regardless of this value.
72
72
  - Transient or unknown queue publishing failures use normal queue redelivery and do not consume this budget.
73
73
 
74
- ### `WORKFLOW_PRECONDITION_GUARD`
74
+ ### `WORKFLOW_RESILIENT_STEP_DISPATCH`
75
75
 
76
- - Default: enabled
77
- - An optimistic-concurrency guard for event creation: replay-context event creations describe the snapshot they replayed from its latest event timestamp (`stateUpdatedAt`), the number of events it contains (`stateEventCount`), and its event-log cursor (`stateCursor`) and a backend that supports the guard rejects a creation with 412 ([`PreconditionFailedError`](/docs/api-reference/workflow-errors/precondition-failed-error)) when a newer out-of-band event (a received hook or a completed step) was recorded after that snapshot, or when the snapshot is missing an event recorded at or before it.
78
- - On rejection the runtime restarts the replay in the same invocation from a corrected event log, and falls back to a re-invocation with a fresh replay once the restart budget is spent. The rejected write is never retried as-is: a replay working from a corrected log derives different events, so only a fresh replay may write again.
79
- - When enabled — and the World declares that it enforces the guard (`capabilities.preconditionGuard`; the Vercel World does) — the runtime also keeps the per-step event-log delta optimization (consuming the delta returned by a step's terminal write instead of issuing an extra `events.list` per step) active while the run has an open hook. Without an enforced guard, an open hook disables it.
80
- - While a hook is open on a guard-enforcing deployment, inline steps take the await-then-run path even when optimistic inline start is enabled: the step's `step_started` claim carries the snapshot and is awaited before the body runs, so a claim the backend rejects as stale never executes user code.
81
- - Backends that do not support the guard ignore the snapshot; they must not declare the capability, so guard-dependent optimizations stay off against them even when the flag is set.
82
- - The guard only ever rejects on evidence, and it fails open in every other case: a backend that cannot decide — because its record of recent events is incomplete, has expired, or covers only part of the run's history — must accept the write. A rejection therefore always means the snapshot really was incomplete, but the absence of one does not prove it was complete. Busy runs (wide step fan-outs, high hook volume) are the most likely to skip the check.
83
- - As a result, 412 volume describes a workload rather than the health of a deployment, and a run that never sees one is not evidence the guard is inactive.
84
- - Set `0` to disable.
76
+ - Default: disabled
77
+ - When a suspension hands newly created steps to the queue, the runtime publishes each step's execution message in parallel with its `step_created` event write instead of sequencing them, cutting a round trip per dispatched step. The message also carries the serialized step input (`stepInput`), so a transient `step_created` write failure (429 / 5xx / transport) still executes the step — the queue consumer idempotently re-ensures the event before running it, converging with the producer's write on the step's correlation ID. This mirrors resilient start (`runInput`) and the lazy hook resume (`hookInput`).
78
+ - It is off by default because the publish races the create's verdict, and a create can come back refused: as a duplicate this replay should stop pursuing, or as a [stale write](#stale-reads-and-why-nothing-has-to-be-rejected) on a World that refuses rather than reports. Either way the message carrying the payload is already out, so the consumer can materialize a step whose create was refused, and nothing orders the verdict before the consumer's redelivery re-ensure. The sequential path is the only one that gives the message a happens-after edge over it.
79
+ - Even when enabled, the runtime falls back to the sequential create-then-publish dispatch when the step input is too large to inline on the queue message, or when the run's queue transport cannot carry binary payloads (pre-CBOR spec versions).
80
+ - Producer-side recoveries are reported on the suspension span as `workflow.step.resilient_dispatch_recovered`; a consumer that materialized the event reports `workflow.step.resilient_dispatch_materialized`.
81
+ - Set `1` to enable it.
82
+
83
+ ### Stale reads, and why nothing has to be rejected
84
+
85
+ - Not a variable: this is how a replay working from an out-of-date event log stays correct, and why no World needs a precondition guard to make it so.
86
+ - Three properties do it together. A reader's log is always a **prefix** of the run's log, never a prefix with a hole in it — positions are allocated by the World at commit, so nothing lands behind a position a reader has already passed. Replay is **deterministic on a prefix**: the same prefix always yields the same decisions, so a shorter log does not mean a different run, only a run that has not caught up. And every write **reports what it missed**: a creation names the position it replayed from (`eventCount`), and the World returns the events occupying the positions it was pushed past. The replay merges those and continues, correcting itself on the write rather than on a read.
87
+ - So a stale replay costs a merge, not a rejection. None of the shipped Worlds refuses a write for being stale.
88
+ - A World *may* refuse instead, with 412 ([`PreconditionFailedError`](/docs/api-reference/workflow-errors/precondition-failed-error)) — appropriate when it allocates positions somewhere other than the commit and cannot report a gap reliably. The runtime handles that: it restarts the replay in the same invocation from a corrected event log, and falls back to a re-invocation with a fresh replay once the restart budget is spent. The rejected write is never retried as-is, because a replay working from a corrected log derives different events.
89
+ - A World that does refuse should only ever do so on evidence, and accept the write in every other case. A rejection then always means the position really was stale, while the absence of one proves nothing about currency.
90
+ - Two runtime behaviors follow from the properties above rather than from any fence. The per-step event-log delta optimization (consuming the delta returned by a step's terminal write instead of issuing an extra `events.list` per step) stays active while the run has an open hook: a `hook_received` missed by the delta window is observed one iteration later, and the next write brings it back. And while a hook is open, inline steps take the await-then-run path even when optimistic inline start is enabled — several invocations race for one step's claim there, and awaiting it means the body runs only for the writer that won.
85
91
 
86
92
  ### `WORKFLOW_SLOT_GAP_CHECK`
87
93
 
@@ -94,7 +100,7 @@ For example, a workflow can run a 10-minute inline step even with `WORKFLOW_REPL
94
100
  ### `WORKFLOW_PRECONDITION_MAX_INPROCESS_RESTARTS`
95
101
 
96
102
  - Default: `3`
97
- - How many times a single invocation restarts its replay in-process after a rejected event creation before it falls back to a re-invocation.
103
+ - How many times a single invocation restarts its replay in-process after an event creation is [rejected as stale](#stale-reads-and-why-nothing-has-to-be-rejected) before it falls back to a re-invocation. No shipped World rejects one, so this budget is reserved for a World that chooses to.
98
104
  - A restart reloads the event log and rebuilds the workflow from scratch, so it costs a replay but no queue round trip. A World may attach the missing events to its rejection, in which case the first restart needs no event-log request at all.
99
105
 
100
106
  ### `WORKFLOW_PRECONDITION_MAX_REINVOCATIONS`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "workflow",
3
- "version": "5.0.0-beta.41",
3
+ "version": "5.0.0-beta.42",
4
4
  "description": "Workflow SDK - Build durable, resilient, and observable workflows",
5
5
  "main": "dist/typescript-plugin.cjs",
6
6
  "type": "module",
@@ -59,18 +59,18 @@
59
59
  },
60
60
  "dependencies": {
61
61
  "ms": "2.1.3",
62
- "@workflow/astro": "5.0.0-beta.41",
63
- "@workflow/cli": "5.0.0-beta.41",
64
- "@workflow/core": "5.0.0-beta.41",
65
- "@workflow/errors": "5.0.0-beta.16",
62
+ "@workflow/astro": "5.0.0-beta.42",
63
+ "@workflow/cli": "5.0.0-beta.42",
64
+ "@workflow/core": "5.0.0-beta.42",
65
+ "@workflow/errors": "5.0.0-beta.17",
66
66
  "@workflow/typescript-plugin": "5.0.0-beta.5",
67
67
  "@workflow/utils": "5.0.0-beta.8",
68
- "@workflow/next": "5.0.0-beta.41",
69
- "@workflow/nest": "5.0.0-beta.41",
70
- "@workflow/nitro": "5.0.0-beta.41",
71
- "@workflow/nuxt": "5.0.0-beta.41",
72
- "@workflow/sveltekit": "5.0.0-beta.41",
73
- "@workflow/rollup": "5.0.0-beta.41"
68
+ "@workflow/next": "5.0.0-beta.42",
69
+ "@workflow/nest": "5.0.0-beta.42",
70
+ "@workflow/nitro": "5.0.0-beta.42",
71
+ "@workflow/nuxt": "5.0.0-beta.42",
72
+ "@workflow/sveltekit": "5.0.0-beta.42",
73
+ "@workflow/rollup": "5.0.0-beta.42"
74
74
  },
75
75
  "devDependencies": {
76
76
  "@types/ms": "2.1.0",