Commit EnvelopesLifecycle & Timeout

Lifecycle & Timeout

Every commit envelope moves through a strict four-phase lifecycle. The runtime drives transitions by emitting the events you declare in lifecycle:.


State diagram

                    start_action submitted


                    ┌─────────────┐
                    │   PENDING   │
                    └──────┬──────┘
                           │ prepare_event emitted

                    ┌─────────────┐
             ┌─────►│  PREPARING  │◄──── (waiting for all
             │      └──────┬──────┘       participants to ACK)
             │             │
             │    all ACK  │   any NACK or timeout
             │             ▼             ▼
             │      ┌────────────┐  ┌──────────┐
             │      │ COMMITTING │  │ ABORTING │
             │      └──────┬─────┘  └────┬─────┘
             │             │             │
             │    commit   │    abort    │
             │    event    │    event    │
             │    emitted  │    emitted  │
             │             ▼             ▼
             │      ┌────────────┐  ┌──────────┐
             │      │ COMMITTED  │  │ ABORTED  │
             │      └────────────┘  └──────────┘
             │             (terminal)   (terminal)

             └─── (timeout fires during PREPARING → ABORTING)

Lifecycle events

lifecycle:
  prepare_event:   TRANSFER_PREPARED
  prepared_event:  TRANSFER_PREPARE_CONFIRMED
  commit_event:    TRANSFER_COMMITTED
  abort_event:     TRANSFER_ABORTED
FieldEmitted byDescription
prepare_eventstart_action core ruleSignals all participants to stage their changes
prepared_eventRuntime (when all participants ACK)Triggers the commit phase
commit_eventRuntime (after prepared_event)Tells all participants to apply staged changes
abort_eventRuntime (on NACK or timeout)Tells all participants to roll back

All four events must be declared in your events: section targeting the envelope_state entity type.


Timeout

If not all participants ACK within abort_after_seconds, the runtime emits the abort_event automatically.

timeout:
  abort_after_seconds: 30
  tick_event: CLOCK_TICK
FieldRequiredDescription
abort_after_secondsyesMax seconds to wait for all participants to ACK
tick_eventyesA kind: system event that fires periodically (the runtime checks elapsed time on each tick)

Defining the tick event

events:
  CLOCK_TICK:
    kind: system
    state: scheduler
    entity_expr: event.tick_id
    payload:
      tick_id: string
      instant_time: integer

The tick event is usually emitted by an external scheduler (cron job, timer service) at a fixed interval such as every 5–10 seconds.

⚠️

Set your tick interval shorter than abort_after_seconds. If the tick fires every 30 seconds and your timeout is 30 seconds, envelopes may run up to 2× the timeout before aborting.

Recommended: tick every 5–10 seconds, timeout 30–60 seconds.


What happens on abort

When abort_event fires:

  1. Each participant executes its abort_cleanup rules (reverting any staged state changes)
  2. The envelope entity’s status is set to ABORTED
  3. A projection update records the aborted transfer for audit

The abort path is deterministic and replay-safe. If the runtime crashes mid-abort, replaying the abort_event will re-run the cleanup rules idempotently.

Your abort_cleanup rules must be idempotent. If the abort_event is delivered twice (e.g., after a crash and replay), running cleanup twice should produce the same final state as running it once.

Use cap: { min: 0 } on balance subtractions and filter to de-duplicate list reversals.