Assertions
Assertions specify properties the model checker should verify.
Must hold in all reachable states:
always assertion Invariant:
return counter >= 0
Use cases: Invariants, safety properties
Must hold in at least one reachable state:
exists assertion Reachability:
return counter == 10
Use cases: Reachability, possibility
Must hold for all state transitions:
transition assertion MonotonicIncrease(before, after):
if after.counter != before.counter:
return after.counter > before.counter
return True # Stutter step
Stutter Invariance (critical requirement):
Transition assertions must allow stutter steps (transitions where state doesn’t change):
# ✅ CORRECT: Allows stuttering
transition assertion NoDecrease(before, after):
if after.value != before.value:
return after.value >= before.value
return True # Stutter allowed
# ❌ WRONG: Requires change every step
transition assertion AlwaysIncreases(before, after):
return after.value > before.value
# Fails on stutter steps!
Why stutter invariance matters:
- Model checker may have steps where only thread-local variables change
- Some actions may execute without changing state variables
- FizzBee automatically passes assertion if no state variables changed
Automatic handling: If no state variables changed, assertion assumed satisfied (even if thread-local variables changed)
Pattern: Always check before != after before asserting a property about the change
Inside an always assertion, next_states() returns the list of successor
transitions from the state under check — one entry per enabled transition,
each executed to its first yield point. This expresses TLA+ ENABLED-style
safety properties with arbitrary quantifier nesting, e.g. “every item in the
trash must be restorable right now”:
always assertion EveryTrashItemRestorable:
for k in items:
if items[k] == "trash":
if len([n for n in next_states() if n.state.items[k] == "active"]) == 0:
return False
return True
Each transition is a struct:
| Field | Type | Example | Notes |
|---|---|---|---|
name |
string | "Customer#0.PlaceOrder" |
Flat label; also "thread-0", "channel-0-message-1" |
kind |
string | "action" |
"action", "thread", or "channel" |
role_ref |
string / None | "Customer#0" |
None for non-role transitions |
role_id |
id value / None | Customer0 |
Same value as the role’s __id__; compares by value |
action_name |
string / None | "PlaceOrder" |
Bare action name; None for thread/channel |
state |
struct | n.state.items |
Successor state vars — same shape as before/after in transition assertions |
(action_name/role_ref — not action/role — because action and role
are fizz keywords and cannot appear as attribute names.)
To scope a check to a specific role instance, compare ids — never role objects (roles compare by pointer and never match across states; see GOTCHAS):
always assertion EveryIdleWorkerCanWork:
for w in workers:
if w.status == "idle":
if len([n for n in next_states() if n.action_name == "Work" and n.role_id == w.__id__]) == 0:
return False
return True
Id values also expose their components: w.__id__.name → "Worker",
w.__id__.index → 0.
Semantics:
- Successors ignore exploration bounds (
max_actionsdepth,max_concurrent_actions, per-action caps). Enabledness is a property of the state, not the scheduler — so the check does not false-fail at the depth frontier or when in-flight actions exhaust the concurrency cap. - Crash variants are excluded.
- For non-atomic (serial) actions, the successor is the action’s first
yield point — effects later in the body are not yet visible. Use
effect predicates (
n.state.x == ...) with atomic witness actions; use name filtering (n.action_name == ...) to check a serial action can start. - Computed lazily, once per checked state. Works in model checking (BFS/DFS), simulation, and no-graph modes. Not available in composition specs.
- Reference examples:
09-06through09-09.
Eventually reaches a stable state:
eventually always assertion EventuallyStable:
return done == True
Requires: Fairness on relevant actions
Always makes progress:
always eventually assertion AlwaysProgresses:
return processed_count > 0
Requires: Strong fairness typically
An assertion without a prefix returns a temporal formula. The body may
compute locals first, as any assertion can, and must end with a single
return:
assertion Answered:
waiting = [c for c in range(NUM_CLIENTS) if pending[c]]
return len(waiting) > 0 leadsto len(waiting) == 0
assertion NoRequestsBeforeStart:
return no_requests() until started
assertion StaysStarted:
return always (started implies always started)
| FizzBee | LTL | TLA+ | Holds on a run when |
|---|---|---|---|
always P |
□P or G P | []P |
P holds in every state |
eventually P |
◇P or F P | <>P |
P holds in some state, now or later |
P until Q |
P U Q | none built in | Q holds in some state, and P in every state before it |
P unless Q |
P W Q | none built in | P holds in every state before the first where Q holds; if Q never holds, in every state |
P leadsto Q |
□(P → ◇Q) | P ~> Q |
every state where P holds is followed, now or later, by one where Q holds |
P implies Q |
P → Q | P => Q |
P is false or Q holds |
not, and, or |
¬, ∧, ∨ | ~, /\, \/ |
as usual |
possibly P |
none; CTL’s EF P | none built in | some path from the current state reaches a state where P holds (see Possibility below) |
There is no next operator (LTL X, TLA+ primes): a formula can’t tell a state from a repeat of it.
Operator precedence, from tightest to loosest binding:
| Level | Operators | Meaning |
|---|---|---|
| 1 | any expression | evaluated in a state |
| 2 | not P, always P, eventually P, possibly P |
prefix |
| 3 | P and Q |
|
| 4 | P or Q |
|
| 5 | P implies Q, P until Q, P unless Q, P leadsto Q |
need parentheses to combine |
and and or chain (P and Q and R). The level 5 operators don’t: write
(P until Q) until R or P until (Q until R); P until Q until R is an
error, and so is mixing two of them without parentheses.
P until Q: Q eventually holds, and P holds in every state before it (strong until).P unless Q: weak until.P until Qrequires Q to happen.P unless Qdoes not: if Q never happens, P must hold forever. Once Q happens, neither operator constrains P afterward. It is not “P if not Q”: inx == 0 unless done, a state withx == 1beforedonefails it.P leadsto Q:always (P implies eventually Q).implies,until,unless,leadstoandpossiblyare keywords only in an assertion’s return, so they can still be used as names elsewhere.
Atoms. Each largest part of the formula with no temporal operator is an
ordinary expression (it may call funcs), evaluated in every state.
Locals are recomputed in every state: they are definitions, not
snapshots. So v = x then return eventually x > v compares x with
itself and is always false.
Parentheses. Prefix operators bind tightest, so
always x >= 0 and y >= 0 would mean (always x >= 0) and y >= 0. That is
an error; write always (x >= 0 and y >= 0). The same holds for
eventually, possibly and not always on the left of any binary
operator.
One formula per value. A for clause on the header checks one instance
per value, named like Answered[0]. Iterables and filters may use only
constants and builtins, and are evaluated once, before Init. The targets
are names; with several, like for a, b in PAIRS, each value must be a
tuple or list of that length. Several clauses nest in order, and a later
clause can use an earlier one’s names. Inside the assertion (its filters,
later clauses and body), a header name shadows a constant with the same
name, as a loop variable would:
assertion Answered for c in range(NUM_CLIENTS):
return pending[c] leadsto not pending[c]
assertion Replicated for k in KEYS for r in REPLICAS if r != PRIMARY:
return written[k] leadsto r in copies[k]
A for clause also works on prefixed assertions:
always eventually assertion Copied for k in KEYS for r in REPLICAS if r != PRIMARY:
return r in copies[k]
return always P, return always eventually P and
return eventually always P (with P free of temporal operators) are the
same checks as the prefixed always, always eventually and
eventually always assertions.
One return. The body computes locals, then returns one formula. A
formula can’t be chosen with if:
assertion Progress:
if MODE == 1: # error: a single return, as the last statement
return always x >= 0
return eventually x == 2
Instead, use one of these:
# Separate assertions, when the cases are separate properties.
assertion Bounded:
return always x >= 0
assertion Reaches:
return eventually x == 2
# A local for the non-temporal part.
assertion Progress:
ready = MODE == 1 and x >= 0
return ready leadsto x == 2
# Connectives. An atom outside every temporal operator, such as MODE == 1
# here, is evaluated in the first state only.
assertion ByMode:
return (MODE == 1 and always x >= 0) or (MODE != 1 and eventually x == 2)
Common patterns. For a request that is made and then served:
| Pattern | Formula | Meaning |
|---|---|---|
| Invariance | always (served implies requested) |
never served without being requested |
| Absence | always not (served and not requested) |
the bad state never occurs |
| Existence | eventually served |
served at some point |
| Response | requested leadsto served |
every request is eventually served |
| Precedence | (not served) unless requested |
not served before it’s requested; it may never be requested |
| Strict precedence | (not served) until requested |
requested at some point, and not served before then |
| Stays true | always (served implies always served) |
once served, served forever |
| Stability | eventually always served |
eventually settles with served true |
| Recurrence | always eventually served |
served infinitely often |
| Constrained response | always (requested implies (requested until served)) |
once requested, stays requested until served |
| Held until released | always (held implies (held unless released)) |
once held, stays held until released, or forever |
| Conditional | (always eventually requested) implies (always eventually served) |
if requested infinitely often, served infinitely often |
| Absence after | always (basic implies always not refunded) |
once basic, never refunded, whatever happens next |
Properties that start from a condition. always (P implies F) checks
the formula F from every state where P holds: a precondition for the
property. It replaces a history variable in the model. For example, a basic
ticket can’t be refunded, but it can be upgraded to premium, which can. The
invariant not (cabin == "basic" and refunded) misses the
upgrade-then-cancel loophole, because the ticket is premium by the time it
is refunded. The formula talks about every basic ticket’s future instead:
assertion BasicNeverRefunded:
return always (cabin == "basic" implies always not refunded)
It fails, and the counterexample is the loophole: buy basic, Upgrade,
Cancel. F can be any formula: always Q (safety after P),
eventually Q (that is P leadsto Q), possibly Q, or Q unless R.
See 09-20.
until is strong and unless is weak: P unless Q means
(P until Q) or always P. Like any formula, a bare P unless Q is judged
from the first state only; to check it from every state where P starts,
write always (start implies (P unless Q)), as in the held-until-released
pattern.
Possibility. The other operators are linear-time, as in LTL: they
describe each fair run. possibly is the one branching-time operator,
CTL’s EF: possibly P holds in a state when some path from it, of zero or
more steps, reaches a state where P holds. It says P can happen,
never that it will: the path may take any action, fair or not. The
operators around it keep their meaning over fair runs:
# From every booked state, cancelling is still possible.
assertion CanCancel:
return always (booked implies possibly cancelled)
# The system can always get back to idle.
assertion CanReset:
return always possibly status == "idle"
action Init:
x = 0
atomic action Leave: # not fair
x = 1
atomic fair action Stay:
x = 0
assertion CanLeave:
return always possibly (x == 1) # passes: Leave is a path to x == 1
assertion WillLeave:
return eventually (x == 1) # fails: a fair run can repeat Stay forever
- What follows
possiblyis a condition on a state: expressions combined withand,or,not,impliesand nestedpossibly. Temporal operators can’t appear inside it, sopossibly (always P)is an error, andpossibly (eventually P)is writtenpossibly P. - The path never takes a crash: recovery that needs a fault isn’t something
a user can count on. States reached after a crash are still checked by an
enclosing
always. exists eventually Pis another spelling ofpossibly P. Anexists assertionis close topossiblychecked in the initial state, except that it also counts states reached by a crash.next_states()looks one step ahead;possiblylooks any number of steps ahead.- When a formula with
possiblyfails, a note under the assertion’s name gives the step of the counterexample after which thatpossiblyis false. - An assertion has at most 64 atoms. Each distinct condition counts once,
and each use of
possiblyadds one, soalways (possibly ready and possibly recovered)uses four.
Limitations.
- Formulas are checked by the default (strict) liveness checker only, not
with
liveness: eventual, simulation or--experimental_no_graph. - Only one
return, at the end (see below). - Temporal operators and
impliescan’t be stored in locals. forclauses can’t iterate over symmetric domains yet.- There is no release operator (LTL R).
P R QisQ unless (P and Q). - Reference examples:
09-14(passing) and09-15(a violation); forpossibly,09-16(passing) and09-17(a violation); forunless,09-18; for a property that starts from a condition,09-20; forforclauses,09-21.
By default, liveness assertions and formulas are checked once the whole
state space is explored. With --experimental_incremental_liveness, the
checker also checks the part explored so far each time the explored node
count doubles, and stops at the first violation:
./fizz --experimental_incremental_liveness spec.fizz
- It never changes a verdict. A violation found early is a real one, and a run with no violation still ends with the usual full check. Only when a failure is reported changes.
- It helps most on large models where the bad run sits close to
Init. - Formulas with
possiblyare checked at the end only. - It needs the default BFS exploration and strict liveness. It is refused
with simulation, an explicit
--experimental_processed_queueand--experimental_no_graph, and has no effect withliveness: eventual. It turns off the default processed queue. - Reference example:
09-19(the flag is in itsflags.txt).
Safety invariants that must hold in every state
Demonstrates: always assertion, safety properties
|
|
Expected result (from fizz):
StateSpaceOptions: options:{max_actions:100 max_concurrent_actions:2}
Nodes: 6, queued: 0
Valid Nodes: 6 Unique states: 6
IsLive: true
PASSED: Model checker completed successfully
Existential properties - at least one state must satisfy
Demonstrates: exists assertion
|
|
Expected result (from fizz):
StateSpaceOptions: options:{max_actions:100 max_concurrent_actions:2}
Nodes: 11, queued: 0
Valid Nodes: 11 Unique states: 11
IsLive: true
PASSED: Model checker completed successfully
Properties about state transitions
Demonstrates: transition assertion with before/after states
|
|
Expected result (from fizz):
StateSpaceOptions: options:{max_actions:100 max_concurrent_actions:2}
Nodes: 8, queued: 0
Valid Nodes: 8 Unique states: 8
IsLive: true
PASSED: Model checker completed successfully
Eventually the system reaches a stable state
Demonstrates: eventually always assertion (liveness property)
|
|
Expected result (from fizz):
StateSpaceOptions: options:{max_actions:100 max_concurrent_actions:2}
Nodes: 9, queued: 0
Valid Nodes: 9 Unique states: 9
Checking eventually always BecomesStable
IsLive: true
PASSED: Model checker completed successfully
System always makes progress eventually
Demonstrates: always eventually assertion (strong fairness)
|
|
Expected result (from fizz):
StateSpaceOptions: options:{max_actions:100 max_concurrent_actions:2}
Nodes: 4, queued: 0
Valid Nodes: 4 Unique states: 4
Checking always eventually AlwaysMakesProgress
IsLive: true
PASSED: Model checker completed successfully
Assert that every trashed item can be restored in one step using next_states()
Demonstrates: next_states(), one-step reachability, always assertion
|
|
Expected result (from fizz):
StateSpaceOptions: options:{max_actions:8 max_concurrent_actions:2} deadlock_detection:false
Nodes: 37, queued: 0
Valid Nodes: 37 Unique states: 15
IsLive: true
PASSED: Model checker completed successfully
A stack-based trash means only the most recent item is restorable, so the next_states() assertion fails with a counterexample
Demonstrates: next_states(), assertion failure, counterexample
|
|
Expected result (from fizz):
StateSpaceOptions: options:{max_actions:8 max_concurrent_actions:2} deadlock_detection:false
FAILED: Model checker failed. Invariant: EveryTrashItemRestorable
------
Init
--
state: {"items":{},"next_id":0,"trash_stack":[]}
------
Create
--
state: {"items":{"0":"active"},"next_id":1,"trash_stack":[]}
------
Create
--
state: {"items":{"0":"active","1":"active"},"next_id":2,"trash_stack":[]}
------
Delete
--
state: {"items":{"0":"active","1":"active"},"next_id":2,"trash_stack":[]}
------
Any:id=0
--
state: {"items":{"0":"trash","1":"active"},"next_id":2,"trash_stack":[0]}
------
Delete
--
state: {"items":{"0":"trash","1":"active"},"next_id":2,"trash_stack":[0]}
------
Any:id=1
--
state: {"items":{"0":"trash","1":"trash"},"next_id":2,"trash_stack":[0,1]}
------
The next_states() probe sees enabled transitions even when max_concurrent_actions is exhausted
Demonstrates: next_states(), max_concurrent_actions, serial actions
|
|
Expected result (from fizz):
StateSpaceOptions: options:{max_actions:6 max_concurrent_actions:1} deadlock_detection:false
Nodes: 55, queued: 0
Valid Nodes: 55 Unique states: 43
IsLive: true
PASSED: Model checker completed successfully
Use role_id and action_name to match next_states() transitions to a specific role instance
Demonstrates: next_states(), role_id, symmetric roles
|
|
Expected result (from fizz):
StateSpaceOptions: options:{max_actions:6 max_concurrent_actions:2} deadlock_detection:false
Nodes: 3, queued: 0
Valid Nodes: 3 Unique states: 3
IsLive: true
PASSED: Model checker completed successfully
An unsatisfiable exists assertion must fail under –experimental_no_graph, matching full-graph mode
Demonstrates: exists assertion, –experimental_no_graph
Needs command-line flags (
--experimental_no_graph); run locally withfizz.
# Example 0100: Exists Assertion in No-Graph Mode (Unsatisfiable)
# An unsatisfiable exists assertion must fail under --experimental_no_graph, matching full-graph mode
# Demonstrates: exists assertion, --experimental_no_graph
---
deadlock_detection: false
options:
max_actions: 6
crash_on_yield: false
---
# Regression pin for the no_graph exists bug: an unsatisfiable `exists`
# must FAIL under --experimental_no_graph (flags.txt) exactly as it does
# with the full graph. Before the fix, exists assertions were silently
# skipped in no_graph mode and this spec reported PASSED.
action Init:
counter = 0
atomic action Inc:
require counter < 3
counter = counter + 1
exists assertion ImpossibleProbe:
return counter > 999
exists assertion ReachableProbe:
return counter == 2
Expected result (from fizz):
StateSpaceOptions: options:{max_actions:6 max_concurrent_actions:2 crash_on_yield:false} deadlock_detection:false
Nodes: 4, queued: 0
FAILED: Expected states never reached
Invariant 0: ImpossibleProbe
A satisfiable exists assertion passes under –experimental_no_graph by detecting a witness
Demonstrates: exists assertion, –experimental_no_graph, witness detection
Needs command-line flags (
--experimental_no_graph); run locally withfizz.
# Example 0101: Exists Assertion in No-Graph Mode (Satisfiable)
# A satisfiable exists assertion passes under --experimental_no_graph by detecting a witness
# Demonstrates: exists assertion, --experimental_no_graph, witness detection
---
deadlock_detection: false
options:
max_actions: 6
crash_on_yield: false
---
# Companion to 09-10: a satisfiable `exists` must PASS under
# --experimental_no_graph (flags.txt) — the witness accumulator must
# actually detect witnesses, not just fail everything.
action Init:
counter = 0
atomic action Inc:
require counter < 3
counter = counter + 1
exists assertion ReachableProbe:
return counter == 2
Expected result (from fizz):
StateSpaceOptions: options:{max_actions:6 max_concurrent_actions:2 crash_on_yield:false} deadlock_detection:false
Nodes: 4, queued: 0
PASSED: Model checker completed successfully
In simulation mode an unwitnessed exists assertion produces a warning instead of a failure
Demonstrates: exists assertion, simulation mode, coverage warning
Needs command-line flags (
-x --seed 42); run locally withfizz.
# Example 0102: Exists Coverage Warning in Simulation Mode
# In simulation mode an unwitnessed exists assertion produces a warning instead of a failure
# Demonstrates: exists assertion, simulation mode, coverage warning
---
deadlock_detection: false
options:
max_actions: 6
---
# Simulation-mode exists coverage warning: with a fixed seed (flags.txt,
# forces a single deterministic run), the unsatisfiable exists must
# produce a named WARNING — not a failure, since simulation is not
# exhaustive. The reachable probe must not be warned about if this
# seed's trace witnesses it; the impossible one always warns.
action Init:
counter = 0
atomic action Inc:
require counter < 3
counter = counter + 1
exists assertion ImpossibleProbe:
return counter > 999
Expected result (from fizz):
StateSpaceOptions: options:{max_actions:6 max_concurrent_actions:2} deadlock_detection:false
WARNING: 1 exists assertion(s) never satisfied in 1 simulation run(s):
exists assertion ImpossibleProbe
Simulation is not exhaustive: this may be a coverage gap rather than unreachability. Model check to verify.
Fizz funcs can be called anywhere in an assertion expression, each on its own copy of the state
Demonstrates: role funcs and top-level funcs called inside expressions
|
|
Expected result (from fizz):
StateSpaceOptions: options:{max_actions:100 max_concurrent_actions:2} deadlock_detection:false
Nodes: 9, queued: 0
Valid Nodes: 9 Unique states: 9
Checking eventually always AllDone
IsLive: true
PASSED: Model checker completed successfully
An assertion without a prefix returns a temporal formula built from always, eventually, until, leadsto and implies
Demonstrates: temporal formulas, until, leadsto, for clauses on an assertion header
|
|
Expected result (from fizz):
StateSpaceOptions: options:{max_actions:100 max_concurrent_actions:2} deadlock_detection:false
Nodes: 58, queued: 0
Valid Nodes: 58 Unique states: 26
Checking formula NoRequestsBeforeStart
Checking formula Answered[0]
Checking formula Answered[1]
Checking formula StaysStarted
Checking always eventually EventuallyIdle
IsLive: true
PASSED: Model checker completed successfully
A temporal formula that fails, with a counterexample run that violates it
Demonstrates: temporal formulas, assertion failure, counterexample
|
|
Expected result (from fizz):
StateSpaceOptions: options:{max_actions:100 max_concurrent_actions:2} deadlock_detection:false
Nodes: 58, queued: 0
Valid Nodes: 58 Unique states: 26
Checking formula NoRequestsBeforeStart
Checking formula Answered[0]
IsLive: false
FAILED: Liveness check failed
Invariant: Answered[0]
------
Init
--
state: {"answered":[0,0],"pending":[false,false],"started":false}
------
Start
--
state: {"answered":[0,0],"pending":[false,false],"started":true}
------
Request
--
state: {"answered":[0,0],"pending":[false,false],"started":true}
------
Any:c=0
--
state: {"answered":[0,0],"pending":[true,false],"started":true}
------
stutter
--
state: {"answered":[0,0],"pending":[true,false],"started":true}
------
possibly P: some path from this state reaches a state where P holds
Demonstrates: possibly, can happen versus will happen, nested possibly, exists eventually
|
|
Expected result (from fizz):
StateSpaceOptions: options:{max_actions:100 max_concurrent_actions:2} deadlock_detection:false
Nodes: 8, queued: 0
Valid Nodes: 8 Unique states: 8
Checking formula CanCancel
Checking formula CanUpgrade
Checking formula CanBookThenFly
Checking formula FlyingIsFinal
Checking formula EventuallyDone
Checking formula CanFly
IsLive: true
PASSED: Model checker completed successfully
A possibly property that fails: from some state, no path reaches the condition
Demonstrates: possibly, recoverability, counterexample note
|
|
Expected result (from fizz):
StateSpaceOptions: options:{max_actions:100 max_concurrent_actions:2} deadlock_detection:false
Nodes: 3, queued: 0
Valid Nodes: 3 Unique states: 3
Checking formula Restorable
Checking formula Recoverable
IsLive: false
FAILED: Liveness check failed
Invariant: Recoverable
Note: the possibly at line 37, column 19 is false in the state after Purge (step 2; Init is step 0): no path from there, without a crash, reaches its condition
------
Init
--
state: {"status":"active"}
------
Delete
--
state: {"status":"trash"}
------
Purge
--
state: {"status":"purged"}
------
stutter
--
state: {"status":"purged"}
------
P unless Q: P holds until Q happens, or forever if Q never happens
Demonstrates: unless, unless versus until, unless under always
|
|
Expected result (from fizz):
StateSpaceOptions: options:{max_actions:100 max_concurrent_actions:2} deadlock_detection:false
Nodes: 7, queued: 0
Valid Nodes: 7 Unique states: 7
Checking formula HeldUntilReturned
Checking formula NotReturnedBeforeTaken
IsLive: true
PASSED: Model checker completed successfully
Find a liveness violation without exploring every state first
Demonstrates: –experimental_incremental_liveness (see flags.txt)
Needs command-line flags (
--experimental_incremental_liveness); run locally withfizz.
# Example 0121: Incremental liveness checking
# Find a liveness violation without exploring every state first
# Demonstrates: --experimental_incremental_liveness (see flags.txt)
---
deadlock_detection: false
options:
max_actions: 200
---
# A producer may send up to 60 messages and a consumer acknowledges them.
# The consumer is weakly fair, so every message sent is acknowledged. The
# producer isn't fair, so nothing makes it send them all: a run may only
# ever exchange heartbeats. AllSent is false on that run.
#
# Normally liveness is checked once the whole state space is explored.
# With --experimental_incremental_liveness the checker also checks the part
# explored so far, each time the node count doubles, and stops at the
# first violation. Here the run of heartbeats sits next to Init, so the
# first partial check finds it, long before all 3,782 states are
# explored. A run with no violation still ends with the full check, so the
# option never changes a verdict, only when a failure is reported.
action Init:
sent = 0
acked = 0
beat = False
atomic action Send:
require sent < 60
sent += 1
atomic fair action Ack:
require acked < sent
acked += 1
atomic fair action Heartbeat:
beat = not beat
always eventually assertion AllAcked:
return acked == sent
always eventually assertion AllSent:
return sent == 60
Expected result (from fizz):
StateSpaceOptions: options:{max_actions:200 max_concurrent_actions:2} deadlock_detection:false
Checking liveness on 953 closed nodes of 1000
FAILED: Liveness check failed
Invariant: AllSent
------
Init
--
state: {"acked":0,"beat":false,"sent":0}
------
Heartbeat
--
state: {"acked":0,"beat":true,"sent":0}
------
Heartbeat
--
state: {"acked":0,"beat":false,"sent":0}
------
A property about what happens after a condition, which a state invariant can’t express
Demonstrates: always (P implies always Q), why an invariant misses it, counterexample
|
|
Expected result (from fizz):
StateSpaceOptions: options:{max_actions:100 max_concurrent_actions:2} deadlock_detection:false
Nodes: 7, queued: 0
Valid Nodes: 7 Unique states: 6
Checking formula BasicNeverRefunded
IsLive: false
FAILED: Liveness check failed
Invariant: BasicNeverRefunded
------
Init
--
state: {}
------
Any:cabin="basic"
--
state: {"cabin":"basic","refunded":false,"status":"booked"}
------
Upgrade
--
state: {"cabin":"premium","refunded":false,"status":"booked"}
------
Cancel
--
state: {"cabin":"premium","refunded":true,"status":"cancelled"}
------
stutter
--
state: {"cabin":"premium","refunded":true,"status":"cancelled"}
------
One assertion header checks one property per value, with filters, nesting and tuples
Demonstrates: for clauses on prefixed and formula assertions, if filters, several clauses, tuple targets
|
|
Expected result (from fizz):
StateSpaceOptions: options:{max_actions:100 max_concurrent_actions:2} deadlock_detection:false
Nodes: 80, queued: 0
Valid Nodes: 76 Unique states: 25
Checking always eventually Copied[a, 1]
Checking always eventually Copied[a, 2]
Checking always eventually Copied[b, 1]
Checking always eventually Copied[b, 2]
Checking formula StaysCopied[a, 1]
Checking formula StaysCopied[b, 2]
IsLive: true
PASSED: Model checker completed successfully
- Mutex Pattern
- Read-Write Lock Pattern
- Distributed Lock Pattern
- Optimistic Locking Pattern
- Saga Pattern
- Snapshot Isolation Pattern
- Leader Election (Simplified Raft)
- Compare-And-Swap Pattern
- Producer-Consumer Pattern
- Message Queue Pattern
- Cache Coherence Pattern
- Eventual Consistency Pattern
- Idempotent Operations Pattern
- Circuit Breaker Pattern
- Rate Limiting Pattern
- Write-Ahead Log Pattern
- Event Sourcing Pattern