Guards & Conditions
Guards are conditions that control whether actions execute. They're marked with the ? prefix.
Basic Guards
A guard is a condition that must be true for the indented actions to run:
on> :checkout from @customer
? cart is not empty
order moves to #awaiting_payment
emit :payment_request to @paymentIf cart is not empty is false, the indented actions are skipped.
Multiple Guards (AND)
Multiple ? lines are combined with AND logic - all must pass:
on> :checkout from @customer
? cart is not empty
? user is logged in
? payment method is valid
order moves to #awaiting_paymentAll three conditions must be true for the action to execute.
OR Guards
Use ?? for OR conditions - any one passing is enough:
on> :access_admin from @user
? user is admin
?? user is super_admin
?? user has admin_override
show admin panelAny one of these conditions passing will allow access.
Combined Logic
You can combine AND and OR:
on> :checkout from @customer
? cart is not empty // AND
? total is greater than 0 // AND
?? user has override // OR
process checkoutReads as: (cart is not empty AND total > 0) OR user has override
Complex Guard Example
on> :process_refund from @customer
? order exists
? order belongs to @customer
? days since purchase < 7
full refund
$refund_amount becomes $order_total
order moves to #fully_refunded
? days since purchase < 30
partial refund (50%)
$refund_amount becomes $order_total * 0.5
order moves to #partially_refunded
? days since purchase < 90
?? customer is premium
partial refund (25%)
$refund_amount becomes $order_total * 0.25
order moves to #partially_refunded
otherwise
refund denied
emit :refund_rejected to @customerDefault Case (Otherwise)
When no guards pass, use otherwise for the default case:
on> :categorize_order from @system
? $total > 1000
$priority becomes "high"
emit :alert to @vip_team
? $total > 100
$priority becomes "medium"
otherwise
$priority becomes "low"You can also use an empty ?:
? $total > 1000
$priority becomes "high"
? $total > 100
$priority becomes "medium"
?
$priority becomes "low"Both are equivalent - otherwise is more readable, empty ? is more concise.
Guard Patterns
State Guards
? order is in #pending
process order
? order is not in #cancelled
allow modificationsContext Guards
? $total is greater than 100
apply free shipping
? $items is not empty
show cart summary
? $retry_count is less than 3
retry operationActor Guards
? @customer is logged in
show account details
? @customer has verified email
allow password resetComparison Guards
? $total equals 0
? $total is 0
? $total is equal to 0
show empty cart
? $count is greater than 10
? $count is less than 5
? $count is at least 3
? $count is at most 100Collection Guards
? $items contains "Gift Card"
apply gift card rules
? $items is empty
show empty message
? $tags is not empty
process tagsTime-Based Guards
? created more than 24 hours ago
mark as expired
? updated within last 1 hour
skip refresh
? days since purchase < 7
allow full refundNested Guards
Guards can be nested for complex logic:
on> :process_order from @system
? order is valid
? payment is authorized
? inventory is available
order moves to #confirmed
emit :prepare_shipment to @warehouse
otherwise
order moves to #backordered
emit :backorder_notice to @customer
otherwise
order moves to #payment_failed
emit :payment_retry to @customer
otherwise
order moves to #invalid
emit :validation_error to @customerGuard Execution Order
Guards are evaluated top-to-bottom. The first matching guard block executes:
on> :apply_discount from @customer
? $total > 500
$discount becomes 50 // First check
? $total > 100
$discount becomes 20 // Second check
otherwise
$discount becomes 0 // DefaultIf $total is 600:
- First guard passes →
$discount becomes 50 - Subsequent guards are not checked
Guards Without Actions
Sometimes you just want to validate:
on> :checkout from @customer
? cart is empty
emit :error to @customer
// Implicit: don't continue
// If we get here, cart is not empty
order moves to #processingGuards in Given Blocks
Guards in given: blocks set up test conditions:
scenario: premium checkout
given:
@customer is logged in
@customer is premium member
cart has items
$total: number is 500
on> :checkout from @customer
? @customer is premium member
$discount becomes 10%
$total decreases by $discount
expect:
= $total equals 450Natural Language Flexibility
EventFlow accepts natural variations:
// All equivalent
? cart is not empty
? cart is non-empty
? cart has items
// All equivalent
? $total is greater than 100
? $total > 100
? $total exceeds 100Use what reads most naturally for your context.
Best Practices
Be Explicit
// Good - clear conditions
? order is in #pending
? payment is authorized
process order
// Avoid - unclear
? everything is ok
do stuffUse Otherwise for Completeness
// Good - all cases handled
? status is active
process active
? status is pending
process pending
otherwise
handle unknown status
// Risky - what if status is something else?
? status is active
process active
? status is pending
process pendingGroup Related Guards
// Related conditions together
? payment is authorized
? payment amount matches order total
? payment currency is valid
complete payment