v0.8.0
The matchers learn the version column: under versioning: a transition
is no longer proven by the state move alone.
The block matcher folds in a third fact. A matching transition must
also have incremented the version, checked from the same before/after
snapshot it already takes for the state and the log:
expect { order.fire!(:pay, seen: order[:state_version]) }
.to transition(order).from(:pending).to(:paid).via_event(:pay)
A version that did not move with the state fails with both numbers named:
the state_version column went from 0 to 7, expected 1. On an unversioned
mounting nothing changes and no new chain appears — the matcher reads
version_column off the mounting and stays silent when there is none.
Every failure message carries the version. The shared standing line
becomes Order in state :pending (state_version 3), so a red spec about
any matcher shows which revision of the row it was looking at.
Staleness stays an exception, exactly as in production. The prediction
matchers were deliberately left alone: allow_event and refuse_event
answer about guards, and a stale seen: token is a conflict, which the
question surface has never predicted. Assert it where it lives:
expect { order.cancel!(seen: stale_token) }
.to raise_error(Statecraft::StaleTransition)
Teaching can_fire? to take a token would have widened the introspection
contract to cover a race it cannot see; the boundary "prediction equals
guards" is worth more than the shorthand.