Commit Envelope Examples
Wallet transfer
Atomically debit a sender and credit a receiver. Neither balance changes unless both sides succeed.
# events
events:
TRANSFER_PREPARED:
state: transfer
entity_expr: event.transfer_id
payload:
transfer_id: string
sender_id: string
receiver_id: string
amount: number
TRANSFER_PREPARE_CONFIRMED:
state: transfer
entity_expr: event.transfer_id
payload:
transfer_id: string
TRANSFER_COMMITTED:
state: transfer
entity_expr: event.transfer_id
payload:
transfer_id: string
TRANSFER_ABORTED:
state: transfer
entity_expr: event.transfer_id
payload:
transfer_id: string
reason: string
# state
state:
transfer:
entity_key: transfer_id
fields:
- name: status
type: string
default: ""
- name: amount
type: number
default: 0
wallet:
entity_key: wallet_id
fields:
- name: available_balance
type: number
default: 0
- name: held_balance
type: number
default: 0
- name: total_balance
type: number
default: 0
# action
actions:
INITIATE_TRANSFER:
state: transfer
entity_id_expr: intent.transfer_id
input:
transfer_id: { type: string, required: true }
sender_id: { type: string, required: true }
receiver_id: { type: string, required: true }
amount: { type: number, required: true }
preflight:
rules:
- name: reject_self_transfer
when:
expr: "event.sender_id == event.receiver_id"
then:
- op: reject
code: SELF_TRANSFER
- name: reject_zero_amount
when:
expr: "event.amount <= 0"
then:
- op: reject
code: INVALID_AMOUNT
core:
rules:
- name: create_transfer_record
when: {}
then:
- op: set
path: /status
value: preparing
- op: set
path: /amount
value: event.amount
- op: emit
event_type: TRANSFER_PREPARED
payload:
transfer_id: event.transfer_id
sender_id: event.sender_id
receiver_id: event.receiver_id
amount: event.amount
# commit envelope
commit_envelopes:
wallet_transfer:
start_action: INITIATE_TRANSFER
envelope_state: transfer
envelope_state_path: /transfer_state
lifecycle:
prepare_event: TRANSFER_PREPARED
prepared_event: TRANSFER_PREPARE_CONFIRMED
commit_event: TRANSFER_COMMITTED
abort_event: TRANSFER_ABORTED
timeout:
abort_after_seconds: 30
tick_event: CLOCK_TICK
participants:
- name: sender
state: wallet
entity_expr: event.sender_id
delta_expr: "-event.amount"
staging:
rules:
- name: hold_sender_funds
when: {}
then:
- op: sub
path: /available_balance
value: event.amount
cap:
min: 0
- op: add
path: /held_balance
value: event.amount
apply:
rules:
- name: debit_sender
when: {}
then:
- op: sub
path: /held_balance
value: event.amount
- op: sub
path: /total_balance
value: event.amount
abort_cleanup:
rules:
- name: release_sender_hold
when: {}
then:
- op: sub
path: /held_balance
value: event.amount
cap:
min: 0
- op: add
path: /available_balance
value: event.amount
- name: receiver
state: wallet
entity_expr: event.receiver_id
delta_expr: "event.amount"
apply:
rules:
- name: credit_receiver
when: {}
then:
- op: add
path: /available_balance
value: event.amount
- op: add
path: /total_balance
value: event.amountInventory reserve + payment capture
Reserve stock and capture payment atomically. If payment fails, stock is released automatically.
commit_envelopes:
order_fulfillment:
start_action: PLACE_ORDER
envelope_state: order
envelope_state_path: /fulfillment_state
lifecycle:
prepare_event: ORDER_FULFILLMENT_PREPARED
prepared_event: ORDER_FULFILLMENT_READY
commit_event: ORDER_FULFILLMENT_COMMITTED
abort_event: ORDER_FULFILLMENT_ABORTED
timeout:
abort_after_seconds: 60
tick_event: CLOCK_TICK
participants:
- name: inventory
state: product
entity_expr: event.product_id
staging:
rules:
- name: reserve_stock
when: {}
then:
- op: sub
path: /available_stock
value: event.quantity
cap:
min: 0
- op: add
path: /reserved_stock
value: event.quantity
apply:
rules:
- name: confirm_reservation
when: {}
then:
- op: sub
path: /reserved_stock
value: event.quantity
abort_cleanup:
rules:
- name: release_reservation
when: {}
then:
- op: sub
path: /reserved_stock
value: event.quantity
cap:
min: 0
- op: add
path: /available_stock
value: event.quantity
- name: payment
state: customer
entity_expr: event.customer_id
staging:
rules:
- name: authorize_payment
when: {}
then:
- op: sub
path: /wallet/available_balance
value: event.total_price
cap:
min: 0
- op: add
path: /wallet/held_balance
value: event.total_price
apply:
rules:
- name: capture_payment
when: {}
then:
- op: sub
path: /wallet/held_balance
value: event.total_price
abort_cleanup:
rules:
- name: void_authorization
when: {}
then:
- op: sub
path: /wallet/held_balance
value: event.total_price
cap:
min: 0
- op: add
path: /wallet/available_balance
value: event.total_price