# 4. Guarding transitions So far every transition fires the moment its event arrives. Real machines need conditions: a payment moves the order forward only if it was *authorized*; a declined payment goes the other way. That condition is a **guard** — a predicate on the `where` clause. ## `where` and the event's data An event carries data (`Event(kind="PaymentResult", data={...})`), and a guard tests it. Both edges below react to the *same* event, `PaymentResult`, but branch on its `status`: ```text event PlaceOrder {} event PaymentResult { status: string amount: int } event Deliver {} guard authorized = status == "authorized" machine order { initial Cart state Cart {} state AwaitingPayment {} state Paid {} final Delivered success {} final Cancelled cancelled {} from Cart to AwaitingPayment on PlaceOrder from AwaitingPayment to Paid on PaymentResult where authorized and amount <= 100 from AwaitingPayment to Cancelled on PaymentResult where status == "declined" from Paid to Delivered on Deliver } ``` ```{mermaid} stateDiagram-v2 [*] --> Cart Cart --> AwaitingPayment : PlaceOrder AwaitingPayment --> Paid : PaymentResult
[authorized and amount ≤ 100] AwaitingPayment --> Cancelled : PaymentResult
[status == declined] Paid --> Delivered : Deliver Delivered --> [*] Cancelled --> [*] ``` Two things are new here. **Named guards.** `guard authorized = status == "authorized"` names a reusable predicate. A guard reference is a predicate *atom*: you can use it alone (`where authorized`) or compose it with more conditions (`where authorized and amount <= 100`). Comparisons use `== != < <= > >= in`, and you combine them with `and` / `or` / `not`. **`event` declarations.** `event PaymentResult { status: string amount: int }` declares the event's shape. It is optional, but once you declare events, [validation](14-validation) can check that your guards reference fields that actually exist. ## Run the branches ```python from harel import definition_from_dsl, DurableRunner, DictStore, Event SOURCE = """ event PlaceOrder {} event PaymentResult { status: string amount: int } event Deliver {} guard authorized = status == "authorized" machine order { initial Cart state Cart {} state AwaitingPayment {} state Paid {} final Delivered success {} final Cancelled cancelled {} from Cart to AwaitingPayment on PlaceOrder from AwaitingPayment to Paid on PaymentResult where authorized and amount <= 100 from AwaitingPayment to Cancelled on PaymentResult where status == "declined" from Paid to Delivered on Deliver } """ defn = definition_from_dsl(SOURCE, "order") runner = DurableRunner(DictStore(), {defn.id: defn}) def after_payment(data): exe = runner.create(defn.id) exe = runner.process(exe.id, Event(kind="PlaceOrder")) exe = runner.process(exe.id, Event(kind="PaymentResult", data=data)) return exe.active_path print("authorized, 50 ->", after_payment({"status": "authorized", "amount": 50})) print("declined ->", after_payment({"status": "declined"})) print("authorized, 500 ->", after_payment({"status": "authorized", "amount": 500})) ``` ```text authorized, 50 -> Paid declined -> Cancelled authorized, 500 -> AwaitingPayment ``` The third case is the important one. The payment was authorized, but `amount <= 100` is false, so the guard fails and **no edge fires** — the order stays in `AwaitingPayment`. The event was delivered, found no enabled transition, and was simply not acted on. ```{note} A predicate on a field the event **does not carry** evaluates to **false**, not an error. So `where amount <= 100` against an event with no `amount` fails the guard — the transition won't fire. Keep that in mind when an event "mysteriously" doesn't transition: a typo'd or missing field silently fails the guard rather than blowing up. ``` ## Binding guards from the outside Just as actions can be bound programmatically with `actions=`, guards can be supplied with `guards={…}` — a map from guard name to a predicate dict (e.g. `{"status__eq": "authorized"}` or `{"all": [...]}`). It overrides the in-DSL `guard` declaration, giving tests and callers the same swap-the-implementation seam they have for actions. A guard answers yes/no. When you need to route *more than two ways* on a computed value, you reach for a **selector** — [the next step](05-selectors).