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
// ❌ 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 resourcesWith Compensation:
// ✅ 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 neededWhen 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:
{
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):
{
onChildFailure: {
action: 'ROLLBACK_ALL',
rollbackCompletedChildren: true
}
}What happens:
OutboundOrder - Child PICK-001 failed
├── InternalOrder (PICK-001) - FAILED (trigger)
├── InternalOrder (PACK-001) - COMPLETED → CANCELLED ✅
├── InternalOrder (LABEL-001) - IN_PROGRESS → CANCELLED ✅
└── ShippingOrder (SHIP-001) - PENDING → CANCELLED ✅
// All children cancelled, even completed ones
// Full reset to pre-execution stateUse 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):
{
onChildFailure: {
action: 'ROLLBACK_SIBLINGS',
rollbackCompletedChildren: false
}
}What happens:
OutboundOrder - Child PACK-001 failed
├── InternalOrder (PICK-001) - COMPLETED → Keep ✅
├── InternalOrder (PACK-001) - FAILED (trigger)
├── InternalOrder (LABEL-001) - PENDING → CANCELLED ✅
└── ShippingOrder (SHIP-001) - PENDING → CANCELLED ✅
// Only cancel active siblings
// Keep completed work (picking done)
// Cancel pending/in-progress workUse 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:
{
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:
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 correctlyUse 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:
{
onChildFailure: {
action: 'MARK_FAILED'
}
}What happens:
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 reviewUse 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:
{
onChildFailure: {
action: 'MANUAL_INTERVENTION'
}
}What happens:
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 requiredUse 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:
{
onChildFailure: {
action: 'IGNORE'
}
}What happens:
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 ignoredUse Case: Optional children don't impact parent
- Quality audits (nice-to-have)
- Photo documentation
- Optional gift wrapping
- Analytics tasks
Built-in Compensation Strategies
OutboundOrder
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
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
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
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
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:
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:
// 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:
import { compensateOnChildFailure } from '#lib/order/helpers'
const compensation = await compensateOnChildFailure(
parentOrder,
{ reference: 'PICK-001', type: 'InternalOrder' },
'OutboundOrder',
db,
context
)
// Returns: Same as compensateOnParentFailureExample Usage:
// 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:
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:
// 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:
{
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:
{
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:
// 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
}
}
}
}
breakResult: Pipeline failures automatically trigger order compensation.
Best Practices
Choose Appropriate Strategies
High-value orders:
onChildFailure: {
action: 'MANUAL_INTERVENTION' // Human review
}Time-sensitive operations:
onChildFailure: {
action: 'ROLLBACK_ALL' // Fast fail, clean slate
}Preserve work when possible:
onChildFailure: {
action: 'ROLLBACK_SIBLINGS',
rollbackCompletedChildren: false // Keep completed work
}Audit Compensation
Always log compensation actions:
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:
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:
// 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 correctUse Cases
E-Commerce Order Cancellation
Scenario: Customer cancels after picking started
Strategy:
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:
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:
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:
const strategy = LSP_COMPENSATION_STRATEGIES[orderType]
if (!strategy) {
console.error(`No compensation strategy for ${orderType}`)
}Partial Compensation Failures
Review failed compensations:
compensation.failedCompensations.forEach(f => {
console.error(`Failed to compensate ${f.orderReference}: ${f.error}`)
})Unexpected Behavior
Verify strategy matches intent:
// 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:

