Static analysis

state/analysis reads the machine’s transition graph and reports structural defects without firing anything. It is the cheapest check in the toolbox: pure graph reasoning over the IR.
analysis.Analyze returns a Report of Findings. Each finding carries a Kind, a Severity, and the state (or transition) it concerns.
report := analysis.Analyze(m)for _, f := range report.Findings { fmt.Printf("%s [%s] %s\n", f.Severity, f.Kind, f.State)}// error [unreachable_state] lost// error [dead_transition] lost// warning [dead_end] stuck// warning [cannot_reach_final] stuckThe kinds split into what the IR proves and what it suggests:
unreachable_state/dead_transitionare exact. Reachability ignores guards (a guard can only ever remove an edge at run time, never add one), so a statically unreachable state is unreachable in every run. Severityerror.nondeterministicis exact for the guardless case: two or more guardless transitions on the same event, or competing guardless “always” transitions. Guarded overlaps are deferred to symbolic analysis.dead_end/cannot_reach_finalare heuristicwarnings: a non-final state with no exit, or a state from which no final state is reachable. A guard that is always false at run time could make these real, but the IR can’t decide that.
Scope a pass with analysis.Only(kinds...) or analysis.Without(kinds...).
These same findings power the builder’s Temper pass, an optional, non-failing diagnostics step you chain before Quench:
for _, d := range builder.Temper() { log.Printf("%s: %s", d.Severity, d.Message)}machine := builder.Quench() // panics on errors Temper merely reportedTemper hands you the findings as data; Quench is the always-call finalizer that turns the same defects into a loud panic. Lint early, freeze with confidence.