As a state blob that I pass around. I do exactly this in the languages I’ve implemented in Elixir, just passing around an env struct, threading it through every function.
Well in purely functional you cannot get user input other than before your code runs, so I’d do it via the repl with them calling the function with the current state along with their action as a kind of ‘event input’.
In reality you need some way to get user actions at runtime, a Haskell IO monad can do that functionally (by blackboxing using input and pretending it has been there since the start even if it has not) or via event passing between purely functional processes or so.
Depends entirely on what you want to validate, but you’d do it in that above process function.
This is not actually that hard. Given a language with Sum types (or tagged tuples on the BEAM) I’d just have each action be part of that sum type that they pass in with the state, and via simple pattern matching you only handle what is valid for a given state/event mix. With matchers it is trivial to just work on the valid actions and crash on the invalid (with perhaps a single wrapper to give pretty errors).
Likewise, I just implemented an FSM on gen_server anytime I needed a FSM, just by pattern matching states and such.


















