Skip to content

[core] Drop pre-slot event ID support and preconditionGuard capability - #3519

Merged
VaguelySerious merged 4 commits into
mainfrom
peter/drop-pre-slot-and-guard
Aug 13, 2026
Merged

[core] Drop pre-slot event ID support and preconditionGuard capability#3519
VaguelySerious merged 4 commits into
mainfrom
peter/drop-pre-slot-and-guard

Conversation

@VaguelySerious

@VaguelySerious VaguelySerious commented Aug 13, 2026

Copy link
Copy Markdown
Member

Three removals split out of #3512, which needed all of them but is about something else (the v5 event API). Nothing here depends on v5.

  1. Slot-numbered event ids are a requirement of the World contract, and the pre-slot code is gone
  • slotEventIds is removed along with the dead code. Nothing consumed it, and a flag describing something mandatory only invites a World to leave it off and then fail every replay. The substance moves to Storage.events: density, bump-and-report, and allocate the position at the commit, which is what makes a reader's log a prefix rather than a prefix with a hole in it
  • the building-a-world guide states it as a requirement in both places it comes up, with a warning callout naming the failure a World author would otherwise meet at replay time
  • @workflow/world-testing asserts it: numbers events by position checks every id decodes to a slot, that the run is dense from 1, and that ids are canonical (a World padding to a different width sorts its own log wrongly past ten events). Verified it can fail, not just pass
  • breaking for custom Worlds. One written against earlier guidance passed conformance and would have failed every replay with Event id is not slot-numbered. It now fails in the suite instead. The GA docs need an explicit migration note; that is not in this PR
  • maxEventSlot, findEventSlotGap and the step executor's slot observer went through a lenient decoder that answered "no position" for an id that is not a slot. That leniency was the pre-slot support: a write whose eventCount is absent is indistinguishable, to a World, from one that honestly loaded nothing
  • they now call requireEventSlot (new, in @workflow/world), which throws
  • skew protection makes this safe on Vercel: a run executes on the deployment that created it, so a build carrying this never replays a run created before slot ids
  • test fixtures across packages/core move from hand-written ULIDs to slotToEventId(n)
  1. preconditionGuard capability removed
  • the need for a precondition guard is gone rather than universal, which is why the capability could be deleted. Three properties do it: a reader's log is a prefix of the run's log rather than a prefix with a hole in it, since positions are allocated at the commit; replay is deterministic on a prefix, so a shorter log means a run that has not caught up rather than one that decides differently; and every write reports what it missed, naming the position it was derived from and getting back the events it was pushed past
  • no World in this repository returns 412 any more. The error type, its docs page and the runtime's handling of it stay for a World that allocates positions away from the commit and would rather refuse than report
  • the per-step inline event-log delta stays enabled while the run has an open hook
  • an inline step's step_started claim is awaited before the body runs (optimistic start suppressed on stale-sensitive batches)
  • resilient step dispatch no longer consults it (see 3)
  • not a no-op for world-local / world-postgres: neither declared the capability, so both now pay the await-then-run claim while a hook is open, and both get the inline delta in that same case. Only world-vercel declared it
  1. WORKFLOW_RESILIENT_STEP_DISPATCH off by default
  • =1 to opt in, where it used to be =0 to opt out
  • previously gated by capabilities.preconditionGuard, which no longer exists, and already off for world-vercel, which declared it
  • the publish races the create's verdict: a World that refuses the step_created sends the runtime back to replay while the payload-carrying message is already out, and nothing orders the refusal before the consumer's redelivery re-ensure
  1. Deletes .changeset/windows-preload-timeout.md (committed here by mistake)

@workflow/world-sim moves to slot ids alongside the runtime, since it drives the real one. Its book goes 35/6/6 to 38/3/3 mint-ordered and stays 41/0/0 append-only: four of the six reds staged a read missing an event the log already held, which under ULIDs was indistinguishable from a complete read and under slots is a gap the runtime re-reads past. Baselines updated in the workflow header, DESIGN.md and the workbench README.

#3512 is now stacked on this.

… resilient dispatch by default

Three removals that stand on their own, split out of the v5 API switch.

**Pre-slot event ids.** `maxEventSlot`, `findEventSlotGap`, and the step
executor's slot observer went through a lenient decoder that answered "no
position" for an id that is not a slot. That leniency *was* the pre-slot
support, and it is the wrong shape now: a write whose `eventCount` is absent is
indistinguishable, to a World, from one that honestly loaded nothing. They call
`requireEventSlot` and throw. Skew protection is what makes it safe on Vercel —
a run executes on the deployment that created it, so a build carrying this
never replays a run created before slot ids.

**The `preconditionGuard` capability.** Every World is now assumed to be able
to refuse a stale replay-context write, so the three behaviors that keyed on
the flag apply unconditionally: the per-step inline event-log delta stays
enabled while the run has an open hook, an inline step's `step_started` claim
is awaited before the body runs, and resilient dispatch stops consulting it.
Before this only world-vercel declared it, so for world-local and
world-postgres this is a behavior change rather than a no-op — they now pay the
await-then-run claim while a hook is open, and get the inline delta in that
same case.

**`WORKFLOW_RESILIENT_STEP_DISPATCH`.** Off by default, `=1` to opt in. The
publish races the create's verdict: a World that refuses the `step_created`
sends the runtime back to replay while the payload-carrying message is already
out, and nothing orders the refusal before the consumer's redelivery re-ensure.
It was gated on the capability that no longer exists, and was already off for
world-vercel, which declared it.

`@workflow/world-sim` moves to slot ids with the runtime, since it drives the
real one. The book's mint-ordered count goes 35/6/6 to 38/3/3 and append-only
stays 41/0/0: four of the six reds staged a read missing an event the log
already held, which under slots is a gap the runtime re-reads past.

Also deletes `.changeset/windows-preload-timeout.md`, committed here by mistake.

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

vercel Bot commented Aug 13, 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 Aug 13, 2026 10:03pm
example-nextjs-workflow-webpack Ready Ready Preview Aug 13, 2026 10:03pm
example-workflow Ready Ready Preview Aug 13, 2026 10:03pm
workbench-astro-workflow Ready Ready Preview Aug 13, 2026 10:03pm
workbench-express-workflow Ready Ready Preview Aug 13, 2026 10:03pm
workbench-fastify-workflow Ready Ready Preview Aug 13, 2026 10:03pm
workbench-hono-workflow Ready Ready Preview Aug 13, 2026 10:03pm
workbench-nestjs-workflow Ready Ready Preview Aug 13, 2026 10:03pm
workbench-nitro-workflow Ready Ready Preview Aug 13, 2026 10:03pm
workbench-nuxt-workflow Ready Ready Preview Aug 13, 2026 10:03pm
workbench-python-workflow Error Error Aug 13, 2026 10:03pm
workbench-sveltekit-workflow Ready Ready Preview Aug 13, 2026 10:03pm
workbench-tanstack-start-workflow Ready Ready Preview Aug 13, 2026 10:03pm
workbench-vite-workflow Ready Ready Preview Aug 13, 2026 10:03pm
workflow-docs Ready Ready Preview, v0 Aug 13, 2026 10:03pm
workflow-swc-playground Ready Ready Preview Aug 13, 2026 10:03pm
workflow-tarballs Ready Ready Preview Aug 13, 2026 10:03pm
workflow-web Ready Ready Preview Aug 13, 2026 10:03pm

@changeset-bot

changeset-bot Bot commented Aug 13, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 1ca4d70

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

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

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

@github-actions

github-actions Bot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

E2E Test Summary

Summary
Passed Failed Skipped Total
✅ ▲ Vercel Production 3338 0 562 3900
✅ 💻 Local Development 3517 0 539 4056
✅ 📦 Local Production 3810 0 558 4368
✅ 🐘 Local Postgres 3810 0 558 4368
✅ 🪟 Windows 312 0 0 312
✅ vercel-multi-region 27 0 0 27
Total 14814 0 2217 17031
Details by Category

✅ ▲ Vercel Production

App Passed Failed Skipped
✅ astro-node 128 0 28
✅ astro-quickjs 128 0 28
✅ example-node 128 0 28
✅ express-node 128 0 28
✅ express-quickjs 128 0 28
✅ fastify-node 128 0 28
✅ fastify-quickjs 128 0 28
✅ hono-node 128 0 28
✅ hono-quickjs 128 0 28
✅ nest-node 128 0 28
✅ nest-quickjs 128 0 28
✅ nextjs-turbopack-node 153 0 3
✅ nextjs-turbopack-quickjs 153 0 3
✅ nextjs-webpack-node 153 0 3
✅ nextjs-webpack-quickjs 153 0 3
✅ nitro-node 128 0 28
✅ nitro-quickjs 128 0 28
✅ nuxt-node 128 0 28
✅ nuxt-quickjs 128 0 28
✅ sveltekit-node 147 0 9
✅ sveltekit-quickjs 147 0 9
✅ tanstack-start-node 128 0 28
✅ tanstack-start-quickjs 128 0 28
✅ vite-node 128 0 28
✅ vite-quickjs 128 0 28

✅ 💻 Local Development

App Passed Failed Skipped
✅ astro-stable-node 130 0 26
✅ astro-stable-quickjs 130 0 26
✅ express-stable-node 130 0 26
✅ express-stable-quickjs 130 0 26
✅ fastify-stable-node 130 0 26
✅ fastify-stable-quickjs 130 0 26
✅ hono-stable-node 130 0 26
✅ hono-stable-quickjs 130 0 26
✅ nest-stable-node 130 0 26
✅ nest-stable-quickjs 130 0 26
✅ nextjs-turbopack-canary-node 137 0 19
✅ nextjs-turbopack-canary-quickjs 137 0 19
✅ nextjs-turbopack-stable-node 156 0 0
✅ nextjs-turbopack-stable-quickjs 156 0 0
✅ nextjs-webpack-canary-quickjs 137 0 19
✅ nextjs-webpack-stable-quickjs 156 0 0
✅ nitro-stable-node 130 0 26
✅ nitro-stable-quickjs 130 0 26
✅ nuxt-stable-node 130 0 26
✅ nuxt-stable-quickjs 130 0 26
✅ sveltekit-stable-node 149 0 7
✅ sveltekit-stable-quickjs 149 0 7
✅ tanstack-start-node 130 0 26
✅ tanstack-start-quickjs 130 0 26
✅ vite-stable-node 130 0 26
✅ vite-stable-quickjs 130 0 26

✅ 📦 Local Production

App Passed Failed Skipped
✅ astro-stable-node 130 0 26
✅ astro-stable-quickjs 130 0 26
✅ express-stable-node 130 0 26
✅ express-stable-quickjs 130 0 26
✅ fastify-stable-node 130 0 26
✅ fastify-stable-quickjs 130 0 26
✅ hono-stable-node 130 0 26
✅ hono-stable-quickjs 130 0 26
✅ nest-stable-node 130 0 26
✅ nest-stable-quickjs 130 0 26
✅ nextjs-turbopack-canary-node 137 0 19
✅ nextjs-turbopack-canary-quickjs 137 0 19
✅ nextjs-turbopack-stable-node 156 0 0
✅ nextjs-turbopack-stable-quickjs 156 0 0
✅ nextjs-webpack-canary-node 137 0 19
✅ nextjs-webpack-canary-quickjs 137 0 19
✅ nextjs-webpack-stable-node 156 0 0
✅ nextjs-webpack-stable-quickjs 156 0 0
✅ nitro-stable-node 130 0 26
✅ nitro-stable-quickjs 130 0 26
✅ nuxt-stable-node 130 0 26
✅ nuxt-stable-quickjs 130 0 26
✅ sveltekit-stable-node 149 0 7
✅ sveltekit-stable-quickjs 149 0 7
✅ tanstack-start-node 130 0 26
✅ tanstack-start-quickjs 130 0 26
✅ vite-stable-node 130 0 26
✅ vite-stable-quickjs 130 0 26

✅ 🐘 Local Postgres

App Passed Failed Skipped
✅ astro-stable-node 130 0 26
✅ astro-stable-quickjs 130 0 26
✅ express-stable-node 130 0 26
✅ express-stable-quickjs 130 0 26
✅ fastify-stable-node 130 0 26
✅ fastify-stable-quickjs 130 0 26
✅ hono-stable-node 130 0 26
✅ hono-stable-quickjs 130 0 26
✅ nest-stable-node 130 0 26
✅ nest-stable-quickjs 130 0 26
✅ nextjs-turbopack-canary-node 137 0 19
✅ nextjs-turbopack-canary-quickjs 137 0 19
✅ nextjs-turbopack-stable-node 156 0 0
✅ nextjs-turbopack-stable-quickjs 156 0 0
✅ nextjs-webpack-canary-node 137 0 19
✅ nextjs-webpack-canary-quickjs 137 0 19
✅ nextjs-webpack-stable-node 156 0 0
✅ nextjs-webpack-stable-quickjs 156 0 0
✅ nitro-stable-node 130 0 26
✅ nitro-stable-quickjs 130 0 26
✅ nuxt-stable-node 130 0 26
✅ nuxt-stable-quickjs 130 0 26
✅ sveltekit-stable-node 149 0 7
✅ sveltekit-stable-quickjs 149 0 7
✅ tanstack-start-node 130 0 26
✅ tanstack-start-quickjs 130 0 26
✅ vite-stable-node 130 0 26
✅ vite-stable-quickjs 130 0 26

✅ 🪟 Windows

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

✅ vercel-multi-region

App Passed Failed Skipped
✅ nextjs-turbopack 27 0 0

📋 View full workflow run

@github-actions

github-actions Bot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

📊 Workflow Benchmarks

The benchmark run for 1ca4d70 failed. See the run logs for details.

Partial results from the failed run:

commit 1ca4d70 · Thu, 13 Aug 2026 22:17:05 GMT · run logs

Backend: vercel · app: nextjs-turbopack

Metric Scenario Best (ms) P75 (ms) P90 (ms) P99 (ms) Samples
TTFS step 207 (+13%) 531 🔴 (-52%) 💚 692 🔴 (-43%) 💚 1454 🔴 (-8.6%) 30
TTFS stream 224 (-5.5%) 721 🔴 (-35%) 💚 1420 🔴 (+25%) 🔻 1532 🔴 (+27%) 🔻 30
TTFS hook + stream 371 (-3.4%) 1716 🔴 (+25%) 🔻 1812 🔴 (+30%) 🔻 1892 🔴 (+23%) 🔻 30
STSO 1020 steps (inline) 137 (-9.3%) 198 (-12%) 232 (-10%) 325 (-38%) 💚 1019
WO 1020 steps 192470 (-16%) 💚 192470 (-16%) 💚 192470 (-16%) 💚 192470 (-16%) 💚 1
SL stream latency 96 (+2.1%) 152 🔴 (+1.3%) 209 🔴 (+30%) 🔻 452 🔴 (+28%) 🔻 30
SO stream overhead (text) 126 (-20%) 💚 306 🔴 (+3.4%) 603 🔴 (+39%) 🔻 1127 🔴 (-25%) 💚 30
SO stream overhead (structured) 134 (+14%) 227 (-44%) 💚 283 (-49%) 💚 926 (-63%) 💚 30
📈 STSO distribution vs main (inline / queue-hop histograms)

1020 steps (inline)

Cumulative STSO time: main 228513ms → this run 190800ms (Δ -37713ms, -17%)

  100-150 ms  ░┃                        main   0  this  61   +61
  150-200 ms  █████████████████░░░░░░┃  main 512  this 719  +207
  200-250 ms  █████┃███████             main 385  this 184  -201
  250-300 ms  ┃█                        main  71  this  39   -32
  300-350 ms  ┃                         main  22  this  10   -12
  350-400 ms  ┃                         main  11  this   0   -11
  400-450 ms  ┃                         main   5  this   2    -3
  450-500 ms  ┃                         main   1  this   1    +0
  500-550 ms  ┃                         main   4  this   0    -4
  550-600 ms  ┃                         main   0  this   1    +1
  600-650 ms  ┃                         main   1  this   1    +0
  650-700 ms  ┃                         main   1  this   0    -1
  900-950 ms  ┃                         main   1  this   1    +0
1200-1250 ms  ┃                         main   1  this   0    -1
1600-1650 ms  ┃                         main   1  this   0    -1
2000-2050 ms  ┃                         main   1  this   0    -1
2400-2450 ms  ┃                         main   1  this   0    -1
6050-6100 ms  ┃                         main   1  this   0    -1
📜 Previous results (2)

88553f8

Thu, 13 Aug 2026 21:32:46 GMT · run logs

vercel / nextjs-turbopack

Metric Scenario Best (ms) P75 (ms) P90 (ms) P99 (ms) Samples
TTFS step 1335 (+626%) 🔻 1462 🔴 (+32%) 🔻 1494 🔴 (+23%) 🔻 1909 🔴 (+20%) 🔻 30
TTFS stream 315 (+33%) 🔻 1462 🔴 (+32%) 🔻 1494 🔴 (+32%) 🔻 1725 🔴 (+43%) 🔻 30
TTFS hook + stream 1340 (+249%) 🔻 1752 🔴 (+27%) 🔻 1779 🔴 (+28%) 🔻 2082 🔴 (+36%) 🔻 30
STSO 1020 steps (inline) 119 (-21%) 💚 176 (-21%) 💚 200 (-22%) 💚 313 (-40%) 💚 1019
WO 1020 steps 177071 (-23%) 💚 177071 (-23%) 💚 177071 (-23%) 💚 177071 (-23%) 💚 1
SL stream latency 100 (+6.4%) 141 🔴 (-6.0%) 191 🔴 (+19%) 🔻 367 🔴 (+3.7%) 30
SO stream overhead (text) 133 (-15%) 💚 199 (-33%) 💚 204 (-53%) 💚 224 (-85%) 💚 30
SO stream overhead (structured) 132 (+12%) 184 (-55%) 💚 206 (-63%) 💚 294 (-88%) 💚 30

392953f

Thu, 13 Aug 2026 02:02:54 GMT · run logs

vercel / nextjs-turbopack

Metric Scenario Best (ms) P75 (ms) P90 (ms) P99 (ms) Samples
TTFS step 1278 (+430%) 🔻 1355 🔴 (+19%) 🔻 1407 🔴 (+20%) 🔻 1481 🔴 (+17%) 🔻 30
TTFS stream 1302 (+375%) 🔻 1346 🔴 (+16%) 🔻 1372 🔴 (+13%) 1419 🔴 (-9.5%) 30
TTFS hook + stream 1594 (+299%) 🔻 1713 🔴 (+23%) 🔻 1768 🔴 (+25%) 🔻 1849 🔴 (+24%) 🔻 30
STSO 1020 steps (inline) 117 (+3.5%) 160 (-24%) 💚 182 (-28%) 💚 284 (-32%) 💚 1019
WO 1020 steps 158374 (-24%) 💚 158374 (-24%) 💚 158374 (-24%) 💚 158374 (-24%) 💚 1
SL stream latency 85 (-12%) 115 🔴 (-29%) 💚 130 🔴 (-40%) 💚 173 🔴 (-54%) 💚 30
SO stream overhead (text) 106 (-29%) 💚 166 (-42%) 💚 234 (-62%) 💚 3271 🔴 (+323%) 🔻 30
SO stream overhead (structured) 100 (-24%) 💚 146 (-45%) 💚 178 (-51%) 💚 211 (-99%) 💚 30
ℹ️ Metric definitions & methodology

The collapsed STSO distribution section above buckets every step gap of the sequential-steps run (not a sampled window), split by whether the step ending the gap ran inline — in the same warm process as the step before it, so the gap is pure framework overhead — or after a queue-hop — the first step of a fresh process, which pays queue dispatch, client reinit and event-log replay. Bars overlay the two runs: is main, marks where this run lands, bridges the gap when this run has more samples in a bucket.

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, deployment clocks) · 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 metrics are measured from deployment-side timestamps only. Runs are triggered by an in-deployment route that stamps the anchor (clientStart) right before start(), so the CI runner’s request and its path through api.vercel.com sit outside every measured window. TTFS = in-deployment start() → first step body (turbo uses the in-process fast path, non-turbo the dispatch path), and includes the VQS dispatch hop plus any /flow cold start. Fan-out TTFS/TTLS are the first and last step completions of a single Promise.all over trivial steps, from the same anchor, so the gap between the two rows is the spread the runtime adds across the fan-out. STSO/WO are measured between step bodies on the deployment. SL is measured inside the workflow (parallel reader/writer steps), so it no longer includes the api.vercel.com read path.

Cold starts are kept in the numbers on purpose — they are part of real bursty-workload latency. The workbench deployment cold-starts the /flow invocation for a large fraction of runs, inflating P75+; the Best column shows the fastest (warm-start) sample for comparison.

@github-actions

github-actions Bot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Sim World

Simulated world deterministic testing for races. Traces

🟠 Mint-ordered log — 3 fail of 41 total

log=mint-ordered · 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 failed 9 1.0m MISMATCH 1
in-flight-before-decision-counted failed 9 1.0m MISMATCH 1
in-flight-after-decision failed 9 1.0m MISMATCH 1
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-mint.txt

🟢 Append-only log — 0 fail of 41 total

log=append-only · 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-append-only.txt

@github-actions

github-actions Bot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Event Log Race Repro

1 of 14 latest repro runs hit event-log regressions.

Run History

Metric 2026-08-13 01:57 UTC #1
logs / deploy
2026-08-13 21:18 UTC #1
logs / deploy
2026-08-13 21:58 UTC #1
logs / deploy
2026-08-13 22:04 UTC #1
logs / deploy
Result no regressions no regressions no regressions 1/14 regressions
Total 14 14 14 14
completed 14 14 14 13
CORRUPTED_EVENT_LOG 0 0 0 1
USER_ERROR 0 0 0 0
RUNTIME_ERROR 0 0 0 0
stuck 0 0 0 0
other 0 0 0 0
infra 0 0 0 0
Config 14 runs / step-storm 6, hook-storm 6, hook-sleep 2 / c8 / 6x8 14 runs / step-storm 6, hook-storm 6, hook-sleep 2 / c8 / 6x8 14 runs / step-storm 6, hook-storm 6, hook-sleep 2 / c8 / 6x8 14 runs / step-storm 6, hook-storm 6, hook-sleep 2 / c8 / 6x8
Timing watchdog 2500ms / step 2200±250ms / stagger 400ms / poke 750ms / timeout 240000ms watchdog 2500ms / step 2200±250ms / stagger 400ms / poke 750ms / timeout 240000ms watchdog 2500ms / step 2200±250ms / stagger 400ms / poke 750ms / timeout 240000ms watchdog 2500ms / step 2200±250ms / stagger 400ms / poke 750ms / timeout 240000ms

Latest Scenario Breakdown

Scenario Total completed CORRUPTED_EVENT_LOG USER_ERROR RUNTIME_ERROR stuck other infra
step-storm 6 5 1 0 0 0 0 0
hook-storm 6 6 0 0 0 0 0 0
hook-sleep 2 2 0 0 0 0 0 0

Latest Non-Completed Runs

Scenario Attempt Outcome Status Error code Run
step-storm 1 CORRUPTED_EVENT_LOG failed CORRUPTED_EVENT_LOG wrun_41KZYJ92TP0GYBNDKW3FJBWQ3Y

@VaguelySerious
VaguelySerious marked this pull request as ready for review August 13, 2026 02:00
@VaguelySerious
VaguelySerious requested review from a team, fantix and msullivan as code owners August 13, 2026 02:00

@pranaygp pranaygp 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.

Reviewed the full diff, verified the sim-book baselines locally (mint-ordered 38/3/3, append-only 41/0/0, --no-fence 5), and re-ran the affected suites on the current head — all green. 88553f8 addressed every should-fix from the first review round (the #3404 doc revert, the 412-fence safety-argument framing, the latent null-runId mint in the sim facade, the stale scenario descriptions, and the world-vercel changeset). Two non-blocking threads remain open: the building-a-world capabilities paragraph should state slot allocation as a hard requirement of the v5 World contract (and the v5 GA docs PR should call out the breaking change for custom-world maintainers), and the pre-slot-run story for self-hosted worlds deserves a line in the release notes. The new world-local/world-postgres repro lanes added in 7d214db will give this PR's delta-with-open-hooks change its first storm datapoints on the worlds it actually affects.

Review feedback: `slotEventIds` could no longer read as an optional extra. The
runtime reads a position out of every event id it loads and fails the run when
it cannot, so a World that does not allocate slots cannot replay a single
workflow — the flag was the only optional part, not the allocation.

So the flag is gone, the same way `preconditionGuard` went: nothing consumed it
(three Worlds declared it and two doc comments mentioned it), and a flag
describing something mandatory only invites a World to leave it off and wonder
why replay fails. The substance moves to `Storage.events`, which is what a
World implements, and says which two properties the runtime actually relies on:
density, and bump-and-report. It also now states the part that was implicit —
allocate the position *at the commit*, because that is what makes a reader's
log a prefix rather than a prefix with a hole in it.

The guide says it as a requirement in both places it comes up: the capabilities
paragraph, which now points out what is deliberately not a capability, and the
Event ID Allocation section, which opens with a warning callout naming the
failure a World author would otherwise meet at replay time.

And `@workflow/world-testing` asserts it, which is the reviewer's point that a
World written against earlier guidance passes conformance today and fails every
replay later. The new case checks that every id decodes to a slot, that the run
is dense from 1, and that ids are in canonical form — a World padding to a
different width sorts its own log wrongly past ten events. Verified by breaking
the expectation on purpose: red on world-local, then green again, and green on
world-postgres.

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

github-actions Bot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Event Log Race Repro (world-local)

6 of 14 latest repro runs hit event-log regressions.

Run History

Metric 2026-08-13 21:58 UTC #1
logs / deploy
2026-08-13 22:09 UTC #1
logs / deploy
Result 1/1 regressions — partial (1 of 14 planned) 6/14 regressions
Total 1 14
completed 0 8
CORRUPTED_EVENT_LOG 1 1
USER_ERROR 0 0
RUNTIME_ERROR 0 0
stuck 0 5
other 0 0
infra 0 0
Config 1 runs / step-storm 6, hook-storm 6, hook-sleep 2 / c8 / 6x8 14 runs / step-storm 6, hook-storm 6, hook-sleep 2 / c8 / 6x8
Timing watchdog 2500ms / step 2200±250ms / stagger 400ms / poke 750ms / timeout 240000ms watchdog 2500ms / step 2200±250ms / stagger 400ms / poke 750ms / timeout 240000ms

Latest Scenario Breakdown

Scenario Total completed CORRUPTED_EVENT_LOG USER_ERROR RUNTIME_ERROR stuck other infra
step-storm 6 0 1 0 0 5 0 0
hook-storm 6 6 0 0 0 0 0 0
hook-sleep 2 2 0 0 0 0 0 0

Latest Non-Completed Runs

Scenario Attempt Outcome Status Error code Run
step-storm 5 CORRUPTED_EVENT_LOG failed CORRUPTED_EVENT_LOG wrun_01KZYJ91JEXV3WD1549HAYYKDA
step-storm 2 stuck running wrun_01KZYJ91HYK431CQPQR0JPQMQ3
step-storm 1 stuck running wrun_01KZYJ91JFM9T3GFEMP0XGP345
step-storm 6 stuck running wrun_01KZYJ91JCB5ABNFESGZ952JWK
step-storm 4 stuck running wrun_01KZYJ91JA3HAWEFRKRE0DWGMB
step-storm 3 stuck running wrun_01KZYJ91J87ZE0CBRGH1SM1MTH

@github-actions

Copy link
Copy Markdown
Contributor

No backport to stable for dc85865 (AI decision).

This is a deliberate contract/API change rather than a stability fix: it removes the slotEventIds and preconditionGuard World capabilities (explicitly called out as breaking for custom Worlds), makes slot-numbered event ids a hard requirement with a throwing requireEventSlot, flips the WORKFLOW_RESILIENT_STEP_DISPATCH default from on to off, and changes runtime behavior for world-local/world-postgres (they now pay the await-then-run claim and get the inline delta). It also adds new CI lanes and conformance-suite assertions that are feature work, and it is the base of a stacked v5 events-API PR. Shipping it to stable would break existing custom Worlds and change existing defaults for users who stayed behind for stability.

To override, re-run the Backport to stable workflow manually via workflow_dispatch and paste this commit SHA into the ref input:

dc85865718fdf5e4abdb5ad8edf715ec956bf07d

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

event-log-race-repro Run the event log race reproduction job

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants