Conversation
Co-Authored-By: shalabhc <shalabh.chaturvedi@vercel.com>
🦋 Changeset detectedLatest commit: ba49f37 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✅ All tests passed
|
| Passed | Failed | Skipped | Total | |
|---|---|---|---|---|
| ✅ ▲ Vercel Production | 3662 | 0 | 685 | 4347 |
| ✅ 💻 Local Development | 3998 | 0 | 510 | 4508 |
| ✅ 📦 Local Production | 3998 | 0 | 510 | 4508 |
| ✅ 🐘 Local Postgres | 3998 | 0 | 510 | 4508 |
| ✅ 🪟 Windows | 160 | 0 | 1 | 161 |
| ✅ 🌐 Cross-language Conformance | 68 | 0 | 74 | 142 |
| ✅ vercel-http-transport | 823 | 0 | 143 | 966 |
| ✅ vercel-multi-region | 27 | 0 | 0 | 27 |
| ✅ vercel-ws-transport | 557 | 0 | 87 | 644 |
| Total | 17291 | 0 | 2520 | 19811 |
Details by Category
✅ ▲ Vercel Production
| App | Passed | Failed | Skipped |
|---|---|---|---|
| ✅ astro-node | 133 | 0 | 28 |
| ✅ astro-quickjs | 133 | 0 | 28 |
| ✅ example-node | 133 | 0 | 28 |
| ✅ example-quickjs | 133 | 0 | 28 |
| ✅ express-node | 133 | 0 | 28 |
| ✅ express-quickjs | 133 | 0 | 28 |
| ✅ fastify-node | 133 | 0 | 28 |
| ✅ fastify-quickjs | 133 | 0 | 28 |
| ✅ hono-node | 133 | 0 | 28 |
| ✅ hono-quickjs | 133 | 0 | 28 |
| ✅ nest-node | 133 | 0 | 28 |
| ✅ nest-quickjs | 133 | 0 | 28 |
| ✅ nextjs-turbopack-node | 158 | 0 | 3 |
| ✅ nextjs-turbopack-quickjs | 158 | 0 | 3 |
| ✅ nextjs-webpack-node | 158 | 0 | 3 |
| ✅ nextjs-webpack-quickjs | 158 | 0 | 3 |
| ✅ nitro-node | 133 | 0 | 28 |
| ✅ nitro-quickjs | 133 | 0 | 28 |
| ✅ nuxt-node | 133 | 0 | 28 |
| ✅ nuxt-quickjs | 133 | 0 | 28 |
| ✅ python-node | 66 | 0 | 95 |
| ✅ sveltekit-node | 152 | 0 | 9 |
| ✅ sveltekit-quickjs | 152 | 0 | 9 |
| ✅ tanstack-start-node | 133 | 0 | 28 |
| ✅ tanstack-start-quickjs | 133 | 0 | 28 |
| ✅ vite-node | 133 | 0 | 28 |
| ✅ vite-quickjs | 133 | 0 | 28 |
✅ 💻 Local Development
| App | Passed | Failed | Skipped |
|---|---|---|---|
| ✅ astro-stable-node | 134 | 0 | 27 |
| ✅ astro-stable-quickjs | 134 | 0 | 27 |
| ✅ express-stable-node | 134 | 0 | 27 |
| ✅ express-stable-quickjs | 134 | 0 | 27 |
| ✅ fastify-stable-node | 134 | 0 | 27 |
| ✅ fastify-stable-quickjs | 134 | 0 | 27 |
| ✅ hono-stable-node | 134 | 0 | 27 |
| ✅ hono-stable-quickjs | 134 | 0 | 27 |
| ✅ nest-stable-node | 134 | 0 | 27 |
| ✅ nest-stable-quickjs | 134 | 0 | 27 |
| ✅ nextjs-turbopack-canary-node | 160 | 0 | 1 |
| ✅ nextjs-turbopack-canary-quickjs | 160 | 0 | 1 |
| ✅ nextjs-turbopack-stable-node | 160 | 0 | 1 |
| ✅ nextjs-turbopack-stable-quickjs | 160 | 0 | 1 |
| ✅ nextjs-webpack-canary-node | 160 | 0 | 1 |
| ✅ nextjs-webpack-canary-quickjs | 160 | 0 | 1 |
| ✅ nextjs-webpack-stable-node | 160 | 0 | 1 |
| ✅ nextjs-webpack-stable-quickjs | 160 | 0 | 1 |
| ✅ nitro-stable-node | 134 | 0 | 27 |
| ✅ nitro-stable-quickjs | 134 | 0 | 27 |
| ✅ nuxt-stable-node | 134 | 0 | 27 |
| ✅ nuxt-stable-quickjs | 134 | 0 | 27 |
| ✅ sveltekit-stable-node | 153 | 0 | 8 |
| ✅ sveltekit-stable-quickjs | 153 | 0 | 8 |
| ✅ tanstack-start-node | 134 | 0 | 27 |
| ✅ tanstack-start-quickjs | 134 | 0 | 27 |
| ✅ vite-stable-node | 134 | 0 | 27 |
| ✅ vite-stable-quickjs | 134 | 0 | 27 |
✅ 📦 Local Production
| App | Passed | Failed | Skipped |
|---|---|---|---|
| ✅ astro-stable-node | 134 | 0 | 27 |
| ✅ astro-stable-quickjs | 134 | 0 | 27 |
| ✅ express-stable-node | 134 | 0 | 27 |
| ✅ express-stable-quickjs | 134 | 0 | 27 |
| ✅ fastify-stable-node | 134 | 0 | 27 |
| ✅ fastify-stable-quickjs | 134 | 0 | 27 |
| ✅ hono-stable-node | 134 | 0 | 27 |
| ✅ hono-stable-quickjs | 134 | 0 | 27 |
| ✅ nest-stable-node | 134 | 0 | 27 |
| ✅ nest-stable-quickjs | 134 | 0 | 27 |
| ✅ nextjs-turbopack-canary-node | 160 | 0 | 1 |
| ✅ nextjs-turbopack-canary-quickjs | 160 | 0 | 1 |
| ✅ nextjs-turbopack-stable-node | 160 | 0 | 1 |
| ✅ nextjs-turbopack-stable-quickjs | 160 | 0 | 1 |
| ✅ nextjs-webpack-canary-node | 160 | 0 | 1 |
| ✅ nextjs-webpack-canary-quickjs | 160 | 0 | 1 |
| ✅ nextjs-webpack-stable-node | 160 | 0 | 1 |
| ✅ nextjs-webpack-stable-quickjs | 160 | 0 | 1 |
| ✅ nitro-stable-node | 134 | 0 | 27 |
| ✅ nitro-stable-quickjs | 134 | 0 | 27 |
| ✅ nuxt-stable-node | 134 | 0 | 27 |
| ✅ nuxt-stable-quickjs | 134 | 0 | 27 |
| ✅ sveltekit-stable-node | 153 | 0 | 8 |
| ✅ sveltekit-stable-quickjs | 153 | 0 | 8 |
| ✅ tanstack-start-node | 134 | 0 | 27 |
| ✅ tanstack-start-quickjs | 134 | 0 | 27 |
| ✅ vite-stable-node | 134 | 0 | 27 |
| ✅ vite-stable-quickjs | 134 | 0 | 27 |
✅ 🐘 Local Postgres
| App | Passed | Failed | Skipped |
|---|---|---|---|
| ✅ astro-stable-node | 134 | 0 | 27 |
| ✅ astro-stable-quickjs | 134 | 0 | 27 |
| ✅ express-stable-node | 134 | 0 | 27 |
| ✅ express-stable-quickjs | 134 | 0 | 27 |
| ✅ fastify-stable-node | 134 | 0 | 27 |
| ✅ fastify-stable-quickjs | 134 | 0 | 27 |
| ✅ hono-stable-node | 134 | 0 | 27 |
| ✅ hono-stable-quickjs | 134 | 0 | 27 |
| ✅ nest-stable-node | 134 | 0 | 27 |
| ✅ nest-stable-quickjs | 134 | 0 | 27 |
| ✅ nextjs-turbopack-canary-node | 160 | 0 | 1 |
| ✅ nextjs-turbopack-canary-quickjs | 160 | 0 | 1 |
| ✅ nextjs-turbopack-stable-node | 160 | 0 | 1 |
| ✅ nextjs-turbopack-stable-quickjs | 160 | 0 | 1 |
| ✅ nextjs-webpack-canary-node | 160 | 0 | 1 |
| ✅ nextjs-webpack-canary-quickjs | 160 | 0 | 1 |
| ✅ nextjs-webpack-stable-node | 160 | 0 | 1 |
| ✅ nextjs-webpack-stable-quickjs | 160 | 0 | 1 |
| ✅ nitro-stable-node | 134 | 0 | 27 |
| ✅ nitro-stable-quickjs | 134 | 0 | 27 |
| ✅ nuxt-stable-node | 134 | 0 | 27 |
| ✅ nuxt-stable-quickjs | 134 | 0 | 27 |
| ✅ sveltekit-stable-node | 153 | 0 | 8 |
| ✅ sveltekit-stable-quickjs | 153 | 0 | 8 |
| ✅ tanstack-start-node | 134 | 0 | 27 |
| ✅ tanstack-start-quickjs | 134 | 0 | 27 |
| ✅ vite-stable-node | 134 | 0 | 27 |
| ✅ vite-stable-quickjs | 134 | 0 | 27 |
✅ 🪟 Windows
| App | Passed | Failed | Skipped |
|---|---|---|---|
| ✅ nextjs-turbopack-node | 160 | 0 | 1 |
✅ 🌐 Cross-language Conformance
| App | Passed | Failed | Skipped |
|---|---|---|---|
| ✅ python | 68 | 0 | 74 |
✅ vercel-http-transport
| App | Passed | Failed | Skipped |
|---|---|---|---|
| ✅ example | 133 | 0 | 28 |
| ✅ express | 133 | 0 | 28 |
| ✅ hono | 133 | 0 | 28 |
| ✅ nextjs-turbopack | 158 | 0 | 3 |
| ✅ nitro | 133 | 0 | 28 |
| ✅ vite | 133 | 0 | 28 |
✅ vercel-multi-region
| App | Passed | Failed | Skipped |
|---|---|---|---|
| ✅ nextjs-turbopack | 27 | 0 | 0 |
✅ vercel-ws-transport
| App | Passed | Failed | Skipped |
|---|---|---|---|
| ✅ example | 133 | 0 | 28 |
| ✅ express | 133 | 0 | 28 |
| ✅ nextjs-turbopack | 158 | 0 | 3 |
| ✅ vite | 133 | 0 | 28 |
📊 Workflow Benchmarkscommit Backend:
Streams
📈 STSO distribution vs main (inline / queue-hop histograms)1020 steps (inline) Cumulative STSO time: main 158739ms → this run 147427ms (Δ -11312ms, -7%) 📈 CRTT drill-down vs main (RTT distributions & profiles)RTT over stream progress (avg per tenth of stream, bars scaled min→max): RTT by chunk size (avg per log size bin, ~160B → ~12KB serialized, bars scaled min→max): Delivery jitter over stream progress (avg positive CDV per tenth of stream, bars scaled min→max): 📜 Previous results (2)51cff62Mon, 14 Sep 2026 23:12:00 GMT · run logs
Streams
b9a614cMon, 14 Sep 2026 21:32:00 GMT · run logs
Streams
ℹ️ Metric definitions & methodologyStreams: first-chunk RTT (the stream-open path, before any buffering/backpressure), CRTT percentiles, and worst delivery stall (CDV max). Cells are medians across iterations; per-run values in the artifacts. No 🔴/🟢 marks until targets attach. The collapsed STSO distribution section above buckets every step gap, split inline (same warm process — pure framework overhead) vs queue-hop (fresh process — dispatch, reinit, replay). The collapsed CRTT drill-down: per-variant RTT histograms (fixed log bins, Best/P75/P90/P99 deltas compare against the most recent benchmark run on 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) · CRTT: chunk round-trip time (per-chunk write → read latency, one clock domain: deployment → stream backend → same deployment) · CDV: chunk delay variation / delivery jitter (inter-arrival gap minus inter-write gap per seq-adjacent pair; skew-free; the row is each run's MAX positive value, so one stall moves it) 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 · paced control (100/s, 60B): the control: 300 tiny (~60B) deltas metronome-paced at 100/s — zero workload structure, so it reads the transport floor and flush cadence, and disambiguates transport-wide vs workload-specific when a replay row moves · size sweep (100/s, 160B-12KB): same pacing as the control with deltas padded in rotation across seven log-spaced sizes (~160B–12KB) — rotation decouples size from stream position, so it isolates whether chunk size causes latency · replay gateway-gpt-5.4-nano-2000t (1x): raw provider SSE cadence captured at the AI gateway boundary (gpt-5.4-nano, the most popular gateway model; per-token deltas p50 208B = the modal production chunk size), replayed exactly as measured — the typical customer's workload; its CDV is the typical customer's real delivery jitter · replay eve-gpt-5.6-sol-2000t (1x): a captured eve turn (gpt-5.6-sol, the most-used demanding eve model; ~2000 output tokens = production p50 turn length) replayed exactly as measured — eve's envelope protocol re-ships the cumulative message so sizes ramp 142B→13KB; the demanding outlier tenant's reality · replay eve-gpt-5.6-sol-2000t (2x): the same eve capture at 2x — the headroom/stress row; real fast-tier models emit the same chunk sizes at proportionally higher rate, so time compression is a faithful speed model · first chunk (pooled): every run's seq-0 RTT pooled across all stream scenarios — the first chunk precedes any workload differentiation, so pooling samples one shared stream-open path with exact percentiles Replay cadences (semantic sha256) — eve-gpt-5.6-sol-2000t 🔴 marks a percentile over its target (within target is left unmarked). Targets (p75/p90/p99, ms) — TTFS 200/300/600 All timestamps are deployment-side; runs are triggered in-deployment, so the CI runner and api.vercel.com sit outside every measured window. TTFS = Cold starts stay in the numbers (real bursty-workload latency, inflates P75+); Best is the warm floor. |
Sim WorldSimulated world deterministic testing for races. Traces 🟠 world-sim scenario book — 1 fail of 41 total
Full trace: |
About these numbersSizes are gzip; parentheses show the change against
|
Co-Authored-By: shalabhc <shalabh.chaturvedi@vercel.com>
VaguelySerious
left a comment
There was a problem hiding this comment.
AI review: blocking issues found
| if (isTerminalWorkflowRunStatus(run.status)) | ||
| throw new HookNotFoundError(input.token); | ||
| const v1Compat = isLegacySpecVersion(hook.specVersion); | ||
| await world.events.create( |
There was a problem hiding this comment.
AI Review: Blocking
Redelivery after events.create() succeeds but respond() fails appends the same hook input again because invocation.id is not used to deduplicate the event. I added a crash-boundary test; it observed two hook_received events for one invocation ID. This can execute a single resume twice. Persist an invocation identity with the event or otherwise make redelivery idempotent.
There was a problem hiding this comment.
Fixed in e3b9a6b using the existing resumeId/resumePayloadDigest API, without adding an atomic World commit method. Postgres validates the digest and resolves a prior event identity before lifecycle rejection. Core also recognizes prior acceptance after hook disposal/run completion. The real-DB test simulates the missing-response boundary, retries the same ID, verifies exactly one hook_received, and completes the actual workflow. Separate IDs with identical payloads remain distinct; changed contents under one ID are rejected.
| "sequence" bigserial NOT NULL, | ||
| "run_id" varchar NOT NULL, | ||
| "request_id" varchar NOT NULL, | ||
| "payload" bytea NOT NULL, |
There was a problem hiding this comment.
AI Review: Blocking
Invocation payloads remain intact when a $retention: 0 run finishes. The existing purge clears all other user-data columns, but this new table is neither included nor lifecycle-linked. This violates the run’s retention contract and retains potentially sensitive hook payloads indefinitely. Add invocation data to the transactional purge and cover it with a retention test.
There was a problem hiding this comment.
Fixed in e3b9a6b. Zero-retention purge now clears invocation payloads/results/fingerprints and event resume digests, retaining an expiry tombstone so callers receive INVOCATION_DATA_EXPIRED (410). Mailbox inserts and response writes lock/recheck run lifecycle, preventing late writes from restoring purged data. Migration 0021 also backfills already-expired or terminal zero-retention runs from earlier previews. Tests cover the existing retention fixture, pending callers, late writes racing the purge lock, and migration backfill.
| const proof = delivery.data; | ||
| // Use Graphile's public jobs view, not private tables. This verifies the | ||
| // actual active task/queue/attempt rather than trusting a boolean header. | ||
| // It is an admission check, NOT a fence on later journal writes. |
There was a problem hiding this comment.
AI Review: Note
This admission check happens only before the HTTP handler runs. If Graphile aborts or reclaims the job while that handler continues, a replacement executor can be admitted while the old handler still writes events, so the claimed per-run serialization no longer holds at the journal boundary. The limitation is documented, but there is no test modeling an admitted handler continuing through reclaim; please add one or fence subsequent writes.
There was a problem hiding this comment.
Added the requested characterization coverage in e3b9a6b: a real Graphile job is admitted, its claim is revoked through forceUnlockWorkers, and the held HTTP handler is then allowed to write an event. The test explicitly confirms the remaining unfenced-write behavior rather than claiming it is safe. Continuous journal fencing remains deferred under the agreed scope and is still called out in the PR description; this change addresses the missing test, not the ownership protocol.
| `LISTEN ${INVOCATION_INPUT_TOPIC}; LISTEN ${INVOCATION_RESULT_TOPIC}` | ||
| ); | ||
| if (!closed && client === connection) wakeAll(); | ||
| } catch { |
There was a problem hiding this comment.
AI Review: Note
Listener connection and LISTEN failures are silently swallowed. Falling back is correct, but operators cannot distinguish healthy notification delivery from continuous one-second polling. Consider logging or telemetry for disconnects, reconnects, and fallback operation.
There was a problem hiding this comment.
Addressed in e3b9a6b. Invocation notification degradation and restoration now emit bounded state-transition diagnostics to stderr. Repeated failed reconnects within an outage do not repeat the warning. Diagnostics use fixed failure categories and omit raw connection errors/options and payloads. Tests verify the degradation/restoration pair, suppression across repeated failures, and omission of sensitive error text.
| try { | ||
| await client.query('BEGIN'); | ||
| await client.query( | ||
| `INSERT INTO workflow.workflow_invocations(run_id, request_id, payload, fingerprint) |
There was a problem hiding this comment.
AI Review: Note
There is no load or connection-footprint test for the extra dedicated connection per World, per-invocation Graphile wake, and repeated authoritative reads. A modest concurrency soak would help quantify the opt-in mode’s database cost and catch mailbox/index growth regressions.
There was a problem hiding this comment.
Added a bounded real-Postgres/Graphile concurrency fixture in e3b9a6b: 72 inputs across 36 two-hook runs, in three parallel waves. It checks one shared producer LISTEN connection, the expected completed mailbox-row count, drained run executor queues, and zero listener connections after close; it also records caller result-read counts. This quantifies the test configuration and catches growth/lifecycle regressions, rather than claiming production throughput or a multi-host soak.
Co-Authored-By: shalabhc <shalabh.chaturvedi@vercel.com>
Co-Authored-By: shalabhc <shalabh.chaturvedi@vercel.com>
1. Changes to the World API
Add optional request/response delivery for run inputs:
The existing
createQueueHandlercallback now returnsPromise<unknown>. Worlddelivers invocation-mode messages through that same callback:
Core inspects the input, awaits its event write, and returns a value. World
owns delivering/storing that value for the original caller. There is no exported
Invocationtype,metadata.invocations, public mailbox API orrespond()callback. The input loop is private to Postgres World. Core shares a run's
execution/admission activity between normal wake and input calls, so input
delivery does not start a competing replay while a step is waiting.
Only normal wake returns interpret
{ timeoutSeconds }as queue control. Ininvocation mode it is response data. In-tree adapters have the corresponding
return-value guards; only Postgres advertises invocation sending in this PR.
Hooks are the first caller: supported
resumeHook()payloads use invoke when thecapability is enabled. Legacy payloads and other Worlds retain the existing path.
The ordering is event-log write → handler return → World response storage;
there is no new acquisition, exchange or atomic-commit API. Invoke failures do
not fall back to direct producer-side event writes.
2. Postgres implementation and Graphile concurrency
Opt-in: after migrations, set
WORKFLOW_POSTGRES_INVOKE=1or passenableInvoke: truetocreateWorld(). Default is off. Apply migrations beforerunning the upgraded Postgres World even with invoke disabled: hook deduplication
and zero-retention cleanup also use the new columns.
The mailbox stores an input/fingerprint and eventual result under
(runId, requestId). Input insertion and Graphile wake enqueue share a backend-privatetransaction. Every eligible invoke enqueues a wake, including result retries;
redundant wakes still check durable run state rather than treating an empty
mailbox as proof that all committed events were replayed.
With the default job prefix:
workflow_flows_executorworkflow_flows:<runId>:executorworkflow_flowsThe overall
queueConcurrencyremains 50 per process by default, not 1.Different runs use different named queues. The executor's World wrapper services
pending inputs while the normal SDK handler is awaiting work, and stores each
input handler's returned value. The Graphile job stays unacknowledged for that
executor lifetime. A later executor may use another process and replay.
Executor delivery checks
The executor task checks its actual Graphile queue and forwards job/worker/attempt
metadata in private headers. The HTTP receiver checks the active task, exact run
queue, worker and attempt against
graphile_worker.jobsbefore starting mailboxservice. Caller-supplied headers cannot override this metadata.
Updated workers/receivers durably transfer legacy unmarked orchestration into the
executor lane before acknowledging it. Steps and health checks never drain the
mailbox. These are entry checks, not continuous journal fencing of stale handlers.
Notification-driven delivery and response waiting
Both waiting points use LISTEN/NOTIFY with one lazy dedicated listener per World.
Input notifications commit with input/wake insertion; result notifications commit
with the separate response update. Notifications carry hashed identifiers and
always lead to authoritative table reads. Read revisions and subscription/reconnect
invalidation close the read-then-wait race. A 1-second fallback and reconnect
backoff cover missed signals/unavailable LISTEN. Degradation/restoration is logged
once per transition to stderr without connection details or payloads.
Inputs are read in pages of 32. Default response timeout is 30 seconds; encoded
inputs/results are limited to 1 MiB each. Closing the World cancels waits and
closes the listener. LISTEN needs a session-capable connection; otherwise fallback
reads preserve progress. Graphile still gets every invocation wake.
Example: one hook
The caller waits for H1's result, not for E1 or the whole workflow to finish.
Example: two hooks queued for the same run
If H2 arrives after E1 exits, E2 processes it instead. An already-active executor
can also consume both inputs before their extra wakes run. Different runs and
step jobs remain concurrent.
Review fixes
hookResumeDedupcapability. A unique
(runId, resumeId)event identity and validated payloaddigest make the event write idempotent. Retry converges even after disposal or
normal run completion, and changed contents under one identity are rejected.
Event and response writes remain separate; the fix does not introduce a public
transaction API. Separate logical calls with identical payloads stay distinct.
and event resume digests. Expiry tombstones settle callers with
INVOCATION_DATA_EXPIRED(410). Mailbox writers lock/recheck run lifecycle solate writes cannot restore purged data. Migration backfills already-expired or
terminal zero-retention runs from earlier previews.
ensuring repeated failed reconnects do not spam or expose raw error details.
test confirms the entry guard does not fence a previously admitted writer.
Full journal fencing remains the explicitly deferred limitation below.
check one shared producer listener, expected mailbox row growth, drained
executor queues, and listener release after close. The fixture records result
read counts; it is not a production throughput benchmark.
Validation
queue tests, local/Vercel queue compatibility and module-state checks.
Postgres/Graphile invocation cases, storage, retention, run creation and status
waiting. Includes Node and QuickJS, inline self-hook delivery, response-loss
retry, post-disposal/completion dedup, zero-retention/late-write races,
migration backfill, listener reconnect, reclaim and concurrency fixtures,
including the updated HTTP deadline/error-metadata and stream-EOF regressions.
after building their dependencies. Changeset and diff validation passed;
Biome reports warnings, no errors.
main, preserving its native HTTP delivery/deadline controls,queue error metadata, status-list handling and stream-EOF fixes.
these are not multi-host process-kill or production load measurements.
Remaining limitations
running after claim revocation. No new ownership protocol is introduced.
replay boundaries. Long bodies can delay serialized wakes and occupy slots.
do not route incompatible code versions; old binaries do not enforce new checks.
zero retention. Expiry can occur before a caller reads its response, even if
the hook event already committed; the caller then receives the expiry error.
Docs Preview
Preview base URL from the
vercel[bot]project row; team authentication isrequired. Section anchors match the documentation headings.