v0.9.0
Two opt-in strictnesses, both turning a silent falsehood into a loud one.
The problem input_guard: solves. A guard that reads metadata made
the question surface lie. Asked order.may_cancel? with no arguments, the
gem fed the guard an empty hash; the guard said no for want of input, and
the answer came back false — while the real call, with the form filled
in, would have passed. The button was hidden because the question was
asked without data, and the machine could not tell "no" from "I don't
know". Until now that was covered by a line of README convention.
The third nature. input_guard: declares that a guard's answer
without the input would be a false no. Its handler must take exactly
(record, metadata) — the compiler refuses any other arity, mirroring the
arity-1 promise record_guard: already makes — and the question surface
honours the declaration:
order.may_cancel? # Statecraft::MetadataRequired
order.may_cancel?(metadata: {}) # false — "my input is empty"
order.may_cancel?(metadata: { reason: "x" }) # true
The error names the guards and the fix. The default of can_fire?,
may_*?, available_events and available_transitions is now a sentinel
rather than an empty hash, so an omitted argument is distinguishable from
a deliberate one. Execution is untouched: fire! without metadata is
still a legitimate call with empty input, refused by the guard as before.
offerable_events and refusals_for ask the record layer only and never
raise — they remain the honest channel for rendering buttons before any
input exists.
strict! closes the graph. By default an unreachable state compiles
silently, and deliberately so: the column is written by more than the gem,
so a state without inbound edges may be perfectly legitimate. A machine
that claims a closed graph now says it:
class OrderFlow < ApplicationMachine
strict!
state :pending, initial: true
...
end
Compilation then requires every declared state to be reachable from the
initial one, walking edges only — guards are not consulted, exactly like
transitions_from. Dead ends stay legal in strict mode too: terminal
states are the norm.
Both are additive. Machines that declare neither behave exactly as they
did, and the whole existing suite passes unchanged.