Skip to content

Compensation & Rollback Strategies

Automatic failure handling, rollback strategies, and cleanup actions for order hierarchies - ensure system consistency when operations fail.

Compensation System

Comprehensive failure handling and cleanup:

  • Compensation Strategies - Define what happens when orders fail
  • Rollback Actions - Automatic cleanup of related orders
  • Cascading Operations - Propagate actions through hierarchy
  • Compensating Actions - Execute corrective operations
  • Audit Trail - Track all compensation activities
  • Manual Intervention - Flag operations requiring human review

Use Cases: Order cancellations, delivery failures, inventory shortages, system errors, exception handling

What is Compensation?

In distributed systems and complex workflows, failures are inevitable. When an order fails or is cancelled, you need to clean up related orders, reverse actions, and maintain system consistency. The compensation system handles this automatically based on configurable strategies.

The Problem Without Compensation

Scenario: Customer Cancels After Picking Started

typescript
// ❌ Without compensation - manual cleanup
OutboundOrder (OUT-001) - Customer cancelled
├── InternalOrder (PICK-001) - IN_PROGRESS ← Still active!
├── InternalOrder (PACK-001) - PENDING ← Still queued!
└── ShippingOrder (SHIP-001) - PENDING ← Still scheduled!

// Problems:
// - Warehouse workers keep picking items
// - Packing task still in queue
// - Shipping still scheduled
// - No automated cleanup
// - Manual intervention required
// - Wasted labor and resources

With Compensation:

typescript
// ✅ With compensation - automatic cleanup
OutboundOrder (OUT-001) - Customer cancelled
COMPENSATION STRATEGY: CASCADE_CANCEL
├── InternalOrder (PICK-001) - CANCELLED ✅ Auto-cancelled
├── InternalOrder (PACK-001) - CANCELLED ✅ Auto-cancelled
└── ShippingOrder (SHIP-001) - CANCELLED ✅ Auto-cancelled

// Benefits:
// - All children automatically cancelled
// - Workers notified to stop
// - Resources freed up
// - Audit trail maintained
// - No manual cleanup needed

When Compensation Triggers

Parent Failure:

  • Parent order fails → Compensate children
  • Parent cancelled → Cascade cancellation
  • Parent suspended → Handle children

Child Failure:

  • Child order fails → Compensate parent and siblings
  • Critical child fails → Mark parent failed
  • Optional child fails → Continue parent

Compensation Strategy Schema

Strategy Structure

Each order type has compensation strategies:

typescript
{
  orderType: 'OutboundOrder',
  
  // What happens when a child fails
  onChildFailure: {
    action: 'ROLLBACK_ALL' | 'ROLLBACK_SIBLINGS' | 'COMPENSATE' | 
            'MARK_FAILED' | 'MANUAL_INTERVENTION' | 'IGNORE',
    rollbackCompletedChildren?: boolean,
    rollbackSiblings?: boolean,
    compensatingActions?: Array<{
      orderType: string,
      action: string,
      params?: Record<string, any>
    }>
  },
  
  // What happens when parent fails  
  onParentFailure: {
    action: 'ROLLBACK_ALL' | 'COMPENSATE',
    rollbackCompletedChildren?: boolean,
    cancelActiveChildren?: boolean,
    compensatingActions?: Array<{
      orderType: string,
      action: string,
      params?: Record<string, any>
    }>
  }
}

Compensation Actions

ROLLBACK_ALL

Cancel/delete all child orders (active and completed):

typescript
{
  onChildFailure: {
    action: 'ROLLBACK_ALL',
    rollbackCompletedChildren: true
  }
}

What happens:

typescript
OutboundOrder - Child PICK-001 failed
├── InternalOrder (PICK-001) - FAILED (trigger)
├── InternalOrder (PACK-001) - COMPLETEDCANCELLED
├── InternalOrder (LABEL-001) - IN_PROGRESSCANCELLED
└── ShippingOrder (SHIP-001) - PENDINGCANCELLED

// All children cancelled, even completed ones
// Full reset to pre-execution state

Use Case: Critical failure requiring complete restart

  • Payment processing failed
  • Fraud detected
  • Inventory not actually available
  • Need to start over from scratch

ROLLBACK_SIBLINGS

Cancel only sibling orders (not completed ones):

typescript
{
  onChildFailure: {
    action: 'ROLLBACK_SIBLINGS',
    rollbackCompletedChildren: false
  }
}

What happens:

typescript
OutboundOrder - Child PACK-001 failed
├── InternalOrder (PICK-001) - COMPLETED → Keep ✅
├── InternalOrder (PACK-001) - FAILED (trigger)
├── InternalOrder (LABEL-001) - PENDINGCANCELLED
└── ShippingOrder (SHIP-001) - PENDINGCANCELLED

// Only cancel active siblings
// Keep completed work (picking done)
// Cancel pending/in-progress work

Use Case: Partial failure, preserve completed work

  • Packing failed (items picked but can't pack)
  • Keep picking complete
  • Cancel downstream work (labeling, shipping)
  • Retry packing later

COMPENSATE

Execute specific compensating actions:

typescript
{
  onChildFailure: {
    action: 'COMPENSATE',
    rollbackSiblings: true,
    compensatingActions: [
      {
        orderType: 'InternalOrder',
        action: 'REVERSE_ALLOCATION',
        params: { reason: 'CHILD_FAILURE' }
      },
      {
        orderType: 'InternalOrder',
        action: 'RETURN_TO_SHELF',
        params: { location: 'ORIGINAL' }
      }
    ]
  }
}

What happens:

typescript
OutboundOrder - Child PACK-001 failed
COMPENSATE
1. REVERSE_ALLOCATION → Return inventory to available pool
2. RETURN_TO_SHELF → Put picked items back
3. Cancel sibling orders
4. Mark parent for review

// Specific cleanup actions executed
// Business logic preserved
// Inventory updated correctly

Use Case: Business-specific cleanup needed

  • Return inventory to pool
  • Reverse financial transactions
  • Update external systems
  • Send notifications
  • Create exception orders

MARK_FAILED

Just mark parent as failed, don't modify children:

typescript
{
  onChildFailure: {
    action: 'MARK_FAILED'
  }
}

What happens:

typescript
OutboundOrder - Child SHIP-001 failed
├── InternalOrder (PICK-001) - COMPLETED (keep)
├── InternalOrder (PACK-001) - COMPLETED (keep)
└── ShippingOrder (SHIP-001) - FAILED (trigger)

OutboundOrder - FAILED ← Marked failed
// Children unchanged
// Parent marked failed
// Requires manual review

Use Case: Let children finish, mark parent failed

  • Delivery failed but items picked/packed
  • Keep work done
  • Mark order failed for customer service
  • Manual resolution needed

MANUAL_INTERVENTION

Flag for operator review, don't auto-compensate:

typescript
{
  onChildFailure: {
    action: 'MANUAL_INTERVENTION'
  }
}

What happens:

typescript
OutboundOrder - Child failed
├── InternalOrder (PICK-001) - COMPLETED
├── InternalOrder (PACK-001) - FAILED (trigger)
└── ShippingOrder (SHIP-001) - PENDING

OutboundOrder - REQUIRES_MANUAL_INTERVENTION ← Flagged
// No automatic actions
// Operator alerted
// Manual decision required

Use Case: Complex scenarios needing human judgment

  • Partial damage (some items OK)
  • Customer wants subset
  • High-value orders
  • Special handling needed

IGNORE

Do nothing, continue operation:

typescript
{
  onChildFailure: {
    action: 'IGNORE'
  }
}

What happens:

typescript
OutboundOrder - Child AUDIT-001 failed
├── InternalOrder (PICK-001) - COMPLETED
├── InternalOrder (PACK-001) - COMPLETED
├── InternalOrder (AUDIT-001) - FAILED (trigger) ← Optional task
└── ShippingOrder (SHIP-001) - PENDING

// No impact on parent
// Continue normal flow
// Failed child logged but ignored

Use Case: Optional children don't impact parent

  • Quality audits (nice-to-have)
  • Photo documentation
  • Optional gift wrapping
  • Analytics tasks

Built-in Compensation Strategies

OutboundOrder

typescript
LSP_COMPENSATION_STRATEGIES['OutboundOrder'] = {
  orderType: 'OutboundOrder',
  
  onChildFailure: {
    action: 'COMPENSATE',
    rollbackSiblings: true,
    compensatingActions: [
      {
        orderType: 'InternalOrder',
        action: 'REVERSE_ALLOCATION',
        params: { reason: 'CHILD_FAILURE' }
      }
    ]
  },
  
  onParentFailure: {
    action: 'ROLLBACK_ALL',
    rollbackCompletedChildren: true,
    cancelActiveChildren: true
  }
}

Behavior:

  • Child Fails: Execute compensating actions, cancel siblings
  • Parent Fails: Cancel everything, full cleanup

Use Case: E-commerce fulfillment - clean slate on failure

InboundOrder

typescript
LSP_COMPENSATION_STRATEGIES['InboundOrder'] = {
  orderType: 'InboundOrder',
  
  onChildFailure: {
    action: 'MARK_FAILED',
    rollbackSiblings: false
  },
  
  onParentFailure: {
    action: 'ROLLBACK_ALL',
    rollbackCompletedChildren: false,
    cancelActiveChildren: true
  }
}

Behavior:

  • Child Fails: Mark parent failed, keep other children
  • Parent Fails: Cancel active children, keep completed

Use Case: Receiving - preserve completed work

InternalOrder

typescript
LSP_COMPENSATION_STRATEGIES['InternalOrder'] = {
  orderType: 'InternalOrder',
  
  onChildFailure: {
    action: 'IGNORE'
  },
  
  onParentFailure: {
    action: 'ROLLBACK_ALL',
    rollbackCompletedChildren: false,
    cancelActiveChildren: true
  }
}

Behavior:

  • Child Fails: Ignore (tasks independent)
  • Parent Fails: Cancel all sub-tasks

Use Case: Warehouse tasks - flexible failure handling

ShippingOrder

typescript
LSP_COMPENSATION_STRATEGIES['ShippingOrder'] = {
  orderType: 'ShippingOrder',
  
  onChildFailure: {
    action: 'MANUAL_INTERVENTION'
  },
  
  onParentFailure: {
    action: 'COMPENSATE',
    cancelActiveChildren: true,
    compensatingActions: [
      {
        orderType: 'InternalOrder',
        action: 'CREATE_RETURN_SHIPMENT',
        params: { reason: 'DELIVERY_FAILED' }
      }
    ]
  }
}

Behavior:

  • Child Fails: Manual intervention (exception handling complex)
  • Parent Fails: Create return shipment, cancel children

Use Case: Last-mile delivery - careful exception handling

PassthroughOrder

typescript
LSP_COMPENSATION_STRATEGIES['PassthroughOrder'] = {
  orderType: 'PassthroughOrder',
  
  onChildFailure: {
    action: 'MARK_FAILED'
  },
  
  onParentFailure: {
    action: 'ROLLBACK_ALL',
    rollbackCompletedChildren: false,
    cancelActiveChildren: true
  }
}

Behavior:

  • Child Fails: Mark failed (time-sensitive, no retry)
  • Parent Fails: Cancel everything

Use Case: Cross-dock - fast fail, no compensation

Compensation Functions

compensateOnParentFailure

Execute compensation when parent fails:

typescript
import { compensateOnParentFailure } from '#lib/order/helpers'

const compensation = await compensateOnParentFailure(
  parentOrder,
  'OutboundOrder',
  db,
  context
)

// Returns:
{
  success: boolean,
  compensatedOrders: string[],  // Orders affected
  failedCompensations: Array<{
    orderReference: string,
    error: string
  }>,
  strategy: 'ROLLBACK_ALL' | 'COMPENSATE' | ...
}

Example Usage:

typescript
// Order failed - execute compensation
.patch('/:reference/fail', async (req, rep) => {
  const order = await db.findOne({ reference })
  
  // Mark as failed
  await db.updateOne({ reference }, { $set: { status: 'FAILED' } })
  
  // Execute compensation strategy
  const compensation = await compensateOnParentFailure(
    order,
    'OutboundOrder',
    db,
    context
  )
  
  return {
    error: false,
    message: 'Order marked as failed',
    compensation: {
      strategy: compensation.strategy,
      compensatedOrders: compensation.compensatedOrders,
      failures: compensation.failedCompensations
    }
  }
})

compensateOnChildFailure

Execute compensation when child fails:

typescript
import { compensateOnChildFailure } from '#lib/order/helpers'

const compensation = await compensateOnChildFailure(
  parentOrder,
  { reference: 'PICK-001', type: 'InternalOrder' },
  'OutboundOrder',
  db,
  context
)

// Returns: Same as compensateOnParentFailure

Example Usage:

typescript
// Child failed - compensate parent
const childOrder = await db.findOne({ reference: childRef })
const parent = await db.findOne({ reference: childOrder.hierarchy.parentOrderReference })

if (parent) {
  const compensation = await compensateOnChildFailure(
    parent,
    { reference: childRef, type: childOrder.orderType },
    parent.orderType,
    db,
    context
  )
  
  // Log compensation results
  console.log(`Compensated ${compensation.compensatedOrders.length} orders`)
}

cascadeCancellation

Execute cascading cancellation for lifecycle cancellation:

typescript
import { cascadeCancellation } from '#lib/order/helpers'

const result = await cascadeCancellation(
  order,
  'CASCADE_CANCEL',  // or 'ORPHAN', 'SUSPEND', 'COMPLETE_FIRST'
  db,
  context
)

// Returns:
{
  updated: number,       // Number of children updated
  errors: Array<{
    childReference: string,
    error: string
  }>
}

Example Usage:

typescript
// Cancel order with cascade
.patch('/:reference/cancel', async (req, rep) => {
  const order = await db.findOne({ reference })
  
  // Validate cancellation
  const validation = await canCancelOrder(order, orderType, db, context)
  
  if (!validation.allowed) {
    return { error: true, reason: validation.reason }
  }
  
  // Cancel order
  await db.updateOne({ reference }, { $set: { status: 'CANCELLED' } })
  
  // Cascade to children
  const cascade = await cascadeCancellation(
    order,
    validation.childAction,  // CASCADE_CANCEL, ORPHAN, etc.
    db,
    context
  )
  
  return {
    error: false,
    message: 'Order cancelled',
    childrenUpdated: cascade.updated,
    errors: cascade.errors
  }
})

Compensation Tracking

Compensation State

Parent orders track compensation state:

typescript
{
  hierarchy: {
    compensationState: {
      isCompensating: boolean,
      compensationStarted: ActionRecord,
      compensationCompleted?: ActionRecord,
      compensationStrategy: string,
      compensatedChildren: string[],
      failedCompensations: Array<{
        orderReference: string,
        error: string,
        timestamp: number
      }>
    }
  }
}

Child Compensation Tracking

Each child order tracks its compensation:

typescript
{
  hierarchy: {
    childOrders: [{
      reference: 'PICK-001',
      compensation: {
        compensated: true,
        recorded: { by: { type: 'SYSTEM' }, at: {...} },
        action: 'ROLLBACK_SIBLINGS',
        reason: 'Sibling order PACK-001 failed'
      }
    }]
  }
}

Pipeline Integration

Compensation on Pipeline Failure

Pipelines trigger compensation automatically:

typescript
// In transitions.ts - handleFailureTransition
case 'HALT':
  execution.status = 'HALTED'
  execution.halted = getActionRecord()
  
  // Execute compensation if order exists
  if (execution.orderId && stage.orderType) {
    const order = await fetchOrder(app, execution.orderId, context)
    
    if (order) {
      const compensation = await compensateOnParentFailure(
        order,
        stage.orderType,
        app.dbb,
        context
      )
      
      // Log compensation
      stage.metadata = {
        ...stage.metadata,
        compensation: {
          strategy: compensation.strategy,
          compensated: compensation.compensatedOrders,
          failures: compensation.failedCompensations
        }
      }
    }
  }
  break

Result: Pipeline failures automatically trigger order compensation.

Best Practices

Choose Appropriate Strategies

High-value orders:

typescript
onChildFailure: {
  action: 'MANUAL_INTERVENTION'  // Human review
}

Time-sensitive operations:

typescript
onChildFailure: {
  action: 'ROLLBACK_ALL'  // Fast fail, clean slate
}

Preserve work when possible:

typescript
onChildFailure: {
  action: 'ROLLBACK_SIBLINGS',
  rollbackCompletedChildren: false  // Keep completed work
}

Audit Compensation

Always log compensation actions:

typescript
await db.collection('compensation_log').insertOne({
  parentReference,
  strategy: compensation.strategy,
  compensatedOrders: compensation.compensatedOrders,
  failures: compensation.failedCompensations,
  timestamp: Date.now(),
  triggeredBy: actionRecord
})

Handle Partial Failures

Compensation might partially fail:

typescript
const compensation = await compensateOnParentFailure(...)

if (compensation.failedCompensations.length > 0) {
  // Some compensations failed
  await alertOperators({
    order: parentReference,
    failures: compensation.failedCompensations
  })
}

Test Compensation Flows

Regularly test compensation strategies:

typescript
// Test scenario: Child failure
1. Create parent with children
2. Fail one child
3. Verify compensation executes
4. Verify all children in expected state
5. Verify audit trail correct

Use Cases

E-Commerce Order Cancellation

Scenario: Customer cancels after picking started

Strategy:

typescript
onParentFailure: {
  action: 'COMPENSATE',
  cancelActiveChildren: true,
  compensatingActions: [
    { action: 'REVERSE_ALLOCATION' },  // Free inventory
    { action: 'RETURN_TO_SHELF' }      // Return picked items
  ]
}

Result:

  • Inventory returned to available pool
  • Picked items returned to shelf
  • All active tasks cancelled
  • Audit trail maintained

Delivery Failure

Scenario: Driver can't deliver (customer not home)

Strategy:

typescript
onParentFailure: {
  action: 'COMPENSATE',
  compensatingActions: [
    { action: 'CREATE_RETURN_SHIPMENT' },  // Schedule return
    { action: 'NOTIFY_CUSTOMER' }          // Send notification
  ]
}

Result:

  • Return shipment automatically created
  • Customer notified
  • Original order marked failed
  • Return tracked in system

Partial Inventory Shortage

Scenario: Some items not available during picking

Strategy:

typescript
onChildFailure: {
  action: 'MANUAL_INTERVENTION'  // Complex decision needed
}

Result:

  • Order flagged for review
  • Operator decides: cancel, partial fulfill, or substitute
  • No automatic actions (wrong choice could anger customer)

Troubleshooting

Compensation Not Executing

Check strategy configuration:

typescript
const strategy = LSP_COMPENSATION_STRATEGIES[orderType]
if (!strategy) {
  console.error(`No compensation strategy for ${orderType}`)
}

Partial Compensation Failures

Review failed compensations:

typescript
compensation.failedCompensations.forEach(f => {
  console.error(`Failed to compensate ${f.orderReference}: ${f.error}`)
})

Unexpected Behavior

Verify strategy matches intent:

typescript
// If children not cancelling, check:
const strategy = LSP_COMPENSATION_STRATEGIES['OutboundOrder']
console.log(strategy.onParentFailure.action)  // ROLLBACK_ALL?
console.log(strategy.onParentFailure.cancelActiveChildren)  // true?

Next Steps


Related Documentation: