Check software that fails some of the time.

Haze is a small language for systems where things go wrong: lost messages, flaky services, rates you only roughly know. Write a model, state what it must guarantee, and Haze checks it with exact probabilities. When a guarantee can fail, it shows you the run where it does.

v0.1 · experimental · built in Lean 4 · runs entirely in your browser

Chance a customer is charged twice, with up to three attempts:
243/2500

Open this model in the playground →

Playground

Try it. No install, no server.

Haze runs in your browser, compiled to WebAssembly from the same Lean code as the command-line tool. Pick an example, change it, and press Run, or turn on live checking.

What it does

Answers you can check, from models you can read

Exact

Exact answers, even for runs of any length

Fractions, not simulations. A game that can last forever still gets its exact odds, and the expected number of rolls.

-- Craps: the shooter keeps rolling until the point comes up again or a 7.
-- That can take any number of rolls, and Haze still gets the exact answer.
enum Phase = ComeOut | OnPoint | Won | Lost

system Craps {
  var phase : Phase := ComeOut
  var point : Num := 0
  step {
    let roll := uniform(1, 6) + uniform(1, 6)
    match phase {
      ComeOut => {
        if roll == 7 or roll == 11 { phase := Won }
        else if roll == 2 or roll == 3 or roll == 12 { phase := Lost }
        else { point := roll; phase := OnPoint }
      }
      OnPoint => { if roll == point { phase := Won } else if roll == 7 { phase := Lost } }
      _ => skip
    }
  }
}

claim shooter_wins : Craps |= eventually phase == Won          -- 244/495
#expect Craps |= steps until phase == Won or phase == Lost      -- rolls on average
Rough numbers

Uncertain rates are welcome

Write "loss is about 2%, maybe 10%" and get a range: how strongly what you know guarantees the property, and how strongly it allows it.

-- You rarely know a failure rate exactly. Write what you do know:
-- "loss is about 2%, could be 5%, possibly 10%".
-- Each value comes with how plausible it is (1 = fully plausible).
param loss : Num ~= {0.01: 0.6, 0.02: 1, 0.05: 0.5, 0.1: 0.2}

-- A request is retried up to 3 times; it fails only if all 3 are lost.
input lost1 : Bool ~ flip(loss)
input lost2 : Bool ~ flip(loss)
input lost3 : Bool ~ flip(loss)

-- The answer is a range: how strongly what you know guarantees it
-- (belief) and how strongly it allows it (plausibility). This bound holds
-- at the likely rates but not at the pessimistic 10%, so it fails.
claim delivered : not (lost1 and lost2 and lost3) with prob >= 0.9999
#prob lost1 and lost2 and lost3
Counterexamples

When a guarantee fails, see the run

Theorems must hold on every run. When one doesn't, Haze shows the state and step where it breaks.

-- A payment client retries when a request or its reply is lost.
-- The server charges the card every time a request reaches it.
-- Is a customer ever charged twice?
enum Phase = Sending | Waiting | Done | GaveUp

def loss : Num := 0.05            -- each message is lost 5% of the time

system Client {
  var phase : Phase := Sending
  var attempts : Num := 0
  var charges : Num := 0
  step {
    match phase {
      Sending => {
        attempts := attempts + 1
        let request_lost := flip(loss)
        if request_lost { phase := Waiting }
        else {
          charges := charges + 1         -- the server charges the card
          let reply_lost := flip(loss)
          if reply_lost { phase := Waiting } else { phase := Done }
        }
      }
      Waiting => { if attempts < 3 { phase := Sending } else { phase := GaveUp } }
      _ => skip
    }
  }
}

claim delivered : Client |= eventually phase == Done with prob >= 0.99
theorem never_charged_twice : Client |= always charges <= 1
-- Fix: charge once per idempotency key, `if charges == 0 { charges := 1 }`
Testing

Tests ranked by probability

Generate test cases from the most likely runs, measure coverage by probability, and get a statistical test with a known flake rate.

-- Tests ranked by probability: Haze lists the runs of the model from the
-- most likely to the least, so a handful of tests covers almost everything
-- that actually happens, plus the rare runs where the property fails.
enum Phase = Sending | Waiting | Done | GaveUp
def loss : Num := 0.1

system Client {
  var phase : Phase := Sending
  var attempts : Num := 0
  var acked : Bool := false
  step {
    match phase {
      Sending => {
        attempts := attempts + 1
        let request_lost := flip(loss)
        let reply_lost := flip(loss)
        if request_lost or reply_lost { phase := Waiting }
        else { acked := true; phase := Done }
      }
      Waiting => { if attempts < 3 { phase := Sending } else { phase := GaveUp } }
      _ => skip
    }
  }
}

-- The 4 most likely runs, the rare failing runs, and a statistical test
-- with a known flake rate for checking the real code.
#tests[4] Client |= after[6] acked
How it works

Built like Lean, for systems with chance

Haze is written in Lean 4 and follows its design: everything you write is elaborated by untrusted code and then re-checked by a small kernel before it is accepted.

Parser

Reads models, claims and queries.

Elaborator

Infers types and translates to a small core language. Not trusted.

TRUSTED

Kernel

Re-checks every declaration. Types record what a value depends on:

crispprobfuzzypartial

Checker

Exact rational arithmetic, bounded and unbounded properties, and bounds certified in exact arithmetic for large state spaces.

Where it fits

How Haze compares

Haze is young and small. Use PRISM or Storm for large models and nondeterminism, and Lean for properties of every size. Reach for Haze for exact answers, rough rates and tests from a small model of real software.

HazePRISM, StormLeanProbabilistic programming (Stan, PyMC)
AnswersExact probabilities and expectations, proofs over every run of a finite model, counterexamples, generated testsProbabilities, expectations and rewards for temporal logics (PCTL, CSL)Proofs that hold for every input and sizePosterior distributions by inference
Rates you only roughly knowFuzzy parameters give a belief–plausibility rangeParametric and interval modelsAnything you can state and provePriors over parameters
ArithmeticExact fractions; floating point only proposes bounds that are then checked exactlyFloating point by default, with exact modesExactSampling or approximation
ModelsDiscrete-time Markov chains; no nondeterminism or continuous time yetDTMCs, MDPs, CTMCs, games and moreAny mathematicsStatistical models, often continuous
ScaleExplicit states, up to about a million per stepMuch larger, with symbolic enginesUnbounded, with effortLarge data sets
EffortWrite a model; checking is automaticWrite a model; checking is automaticWrite the proofs, or have an agent write themWrite a model; tune inference
Install

Get Haze

Prebuilt for Linux, macOS and Windows, checked against published checksums. Or run it in Docker, in GitHub Actions, or build it from source with Lean's toolchain.

curl -sSfL https://raw.githubusercontent.com/ritabratamaiti/haze-lang/main/install.sh | sh