Skip to content

EutaxyDeterministic simulation and formal proof for Cloudflare Workers

Find the bugs that crashes, retries and concurrency cause before production does, and prove the business logic underneath for every input.

EutaxyEutaxy

The bug an ordinary test misses ​

This queue consumer records each payment in D1 and acknowledges the message. It passes every unit test you would think to write.

ts
export default {
  async queue(batch: MessageBatch<Payment>, env: Env): Promise<void> {
    for (const message of batch.messages) {
      const { orderId, amountCents } = message.body;
      await env.DB.prepare("INSERT INTO payments (order_id, amount_cents) VALUES (?1, ?2)")
        .bind(orderId, amountCents)
        .run();
      message.ack();
    }
  },
} satisfies ExportedHandler<Env, Payment>;

A scenario beside it says what must hold: every payment recorded, none recorded twice. eutaxy check runs the Worker inside workerd, injects faults at every effect, redelivers messages the way Cloudflare Queues does, and finds the run that breaks it:

txt
VIOLATED  payments  (seed payments)

Invariant payment-recorded-once is violated: D1:DB run happened twice for key "o-2" (seq 1 and 4)

Schedule (run seed 2537518072), events kept: msg-2
  ambiguous at msg-2#1 effect #0 (D1:DB run)
Shrunk: 1 event(s) and 0 fault(s) removed in 3 run(s)

The insert committed but its answer was lost, the handler threw, the queue redelivered the message, and the order was recorded twice. The same seed replays the same run on every machine, and the regression test Eutaxy writes fails until the insert is idempotent.

Install ​

sh
pnpm add -D eutaxy vitest @cloudflare/vitest-plugin typescript@7.1.0-beta

Eutaxy needs Node 24 and TypeScript 7.1 (inside Vitest, eutaxy/cloudflare loads without the compiler). It has no runtime dependencies; the prover toolchains (Lean 4, Z3) are optional. Continue with Getting started.