Some product rules are too important to live as scattered if checks. Account restrictions — cooling-off, self-exclusion, reactivation — are one of them: get the transitions wrong and you either trap a user or fail to honor a limit they set. A finite state machine makes the rules auditable.
Enumerate states and legal transitions
from enum import Enum
class State(Enum):
ACTIVE = "active"
COOLING_OFF = "cooling_off" # short, timed, auto-reverts
EXCLUDED = "excluded" # long, cannot self-reverse
CLOSED = "closed"
TRANSITIONS = {
State.ACTIVE: {State.COOLING_OFF, State.EXCLUDED, State.CLOSED},
State.COOLING_OFF: {State.ACTIVE}, # only after the timer
State.EXCLUDED: {State.CLOSED}, # no path back to ACTIVE
State.CLOSED: set(),
}
def transition(current, target, *, timer_elapsed=False):
if target not in TRANSITIONS[current]:
raise ValueError(f"illegal {current}->{target}")
if current is State.COOLING_OFF and not timer_elapsed:
raise ValueError("cooling-off still active")
return target
The value is in the transitions you forbid
Making EXCLUDED -> ACTIVE unreachable in code means no endpoint, feature flag, or support tool can accidentally re-enable a self-excluded account. The illegal transition throws instead of silently succeeding. That's a guarantee a pile of booleans can't give you.
Log every transition
Persist an append-only history of (from, to, actor, reason, ts). When someone asks "why is this account restricted," the answer is a query, not an archaeology dig — and it's the record you'll want during an audit.
Reference
Responsible-play controls are a good real-world example because the transitions are legally load-bearing. Seeing how a site like here presents its deposit-limit and self-exclusion options maps neatly onto states a user can enter freely but not always exit on demand.
Takeaway
Name the states, whitelist the legal transitions, make the dangerous ones unrepresentable, and log the rest. The state machine turns a safety-critical rule from hopeful if-checks into something you can prove.
Top comments (0)