# Exercise 2: Build a Saga with Step Functions

Model the order-fulfilment saga from `distributed-systems/fundamentals/08-distributed_transactions` as a **Standard** Step Functions workflow:

1. `ValidateOrder` (Lambda) — fails with `InvalidOrder` for bad input.
2. `ReserveStock` (DynamoDB conditional `UpdateItem` called directly — no Lambda) — fails with `ConditionalCheckFailedException` when out of stock.
3. `ChargeCard` (Lambda calling the PSP) — transient errors retried 3× with exponential backoff and full jitter; `PaymentDeclined` is permanent.
4. If the amount > 1,000 EUR: `ManualApproval` — wait up to 24 h for an approver using a **task token** (an email/Slack link calls an API that sends `SendTaskSuccess`/`SendTaskFailure`).
5. `CreateShipment` (Lambda) — on failure, compensate.
6. Publish `OrderConfirmed` to EventBridge (direct integration) at the end, or `OrderFailed` after compensations.

Compensations: refund the charge, release the stock — in reverse order, each retried until success.

## Deliverables

1. The complete **ASL definition** (JSON or YAML) with Retry/Catch, the Choice for the approval threshold, the task-token state with `TimeoutSeconds` and `HeartbeatSeconds` as appropriate, and `ResultPath`s that keep the original input alongside results.
2. **IAM**: the state machine role's policy (least privilege: specific Lambda ARNs, the DynamoDB table, `events:PutEvents` on the bus).
3. **Idempotency**: which steps must be idempotent and how (PSP idempotency key = execution name or order id; conditional writes).
4. **Execution naming** to prevent duplicate sagas for the same order (Standard workflows reject duplicate names).
5. **Standard vs Express**: justify Standard here, and name one adjacent piece of the system where Express fits.
6. **Observability**: what you'd alarm on (ExecutionsFailed, ExecutionsTimedOut, approval timeouts) and how to find a stuck order.

## Requirements Checklist

- [ ] Uses `arn:aws:states:::dynamodb:updateItem` with a `ConditionExpression`, and `arn:aws:states:::events:putEvents` for publishing.
- [ ] `ChargeCard` has `Retry` with `BackoffRate`, `MaxAttempts: 3`, `JitterStrategy: "FULL"` for transient errors, and `Catch` for `PaymentDeclined` → release stock → fail.
- [ ] Approval uses `.waitForTaskToken` passing `$$.Task.Token`, with `TimeoutSeconds: 86400`; timeout leads to compensation.
- [ ] Compensation states run in reverse order with their own retries; failure path publishes `OrderFailed`.
- [ ] `ResultPath` (e.g. `"$.charge"`) preserves input; payloads stay small.
- [ ] Execution name = order id for deduplication; PSP idempotency key derived from it.
- [ ] Least-privilege role and alarms are specified.
