cancel method
Idempotent; the FIRST reason wins (a step_timeout that lands after a user cancel must not relabel the outcome).
Implementation
void cancel([String? reason]) {
if (_cancelled) return;
_cancelled = true;
_reason = reason ?? 'cancelled';
}