# Knowledge: Approval Chain Workflow

State-machine model for `ApprovalRequest` + `ApprovalStep`.

## States

```
DRAFT  ──submit──▶  SUBMITTED  ──pickup──▶  IN_REVIEW
                                                │
                              ┌─────────────────┼─────────────────┐
                              ▼                 ▼                 ▼
                          APPROVED          REJECTED          CANCELLED
                          (terminal)        (terminal)         (terminal)
```

Each `ApprovalRequest` has 1..N ordered `ApprovalStep`s. The first step with `decision=PENDING` is the **active** step.

## Transitions

| From                            | Action               | To                                                | Who can do it             | Side effects                       |
| ------------------------------- | -------------------- | ------------------------------------------------- | ------------------------- | ---------------------------------- |
| `DRAFT`                         | submit               | `SUBMITTED`                                       | submitter                 | notify first approver              |
| `SUBMITTED`                     | first approver opens | `IN_REVIEW`                                       | first step approver       | no notification                    |
| `IN_REVIEW`                     | approve current step | `IN_REVIEW` (next step) or `APPROVED` (last step) | current step approver     | notify next approver / submitter   |
| `IN_REVIEW`                     | reject               | `REJECTED`                                        | current step approver     | notify submitter; comment required |
| `DRAFT`/`SUBMITTED`/`IN_REVIEW` | cancel               | `CANCELLED`                                       | submitter or system-admin | notify approvers                   |

## Rules

1. The active step's approver is the **only** principal who can decide that step. A non-approver decision is a 403, not a 422.
2. A `Reject` requires a non-empty `comment`.
3. Step `order` is unique per request (`@@unique([requestId, order])`).
4. `decidedAt` is the time we recorded the decision, in UTC (not the user's local time).
5. Re-opening a terminal request is **not** allowed — create a new one and link via metadata if needed.

## Notifications

- On `submit` → email + push to the first approver.
- On `approve` (intermediate) → email + push to the next approver.
- On final `APPROVED` or `REJECTED` → email + push to the submitter.
- On `CANCELLED` → email to remaining approvers.

All sent via `@loma-portal/em-notifications` (queued).

## Audit

Every transition emits an audit event: `approval.submit | approval.approve_step | approval.reject | approval.cancel | approval.advance`.

## Related

- `.claude/blueprints/workflow-feature/BLUEPRINT.md`
- `prisma/schema.prisma` (models `ApprovalRequest`, `ApprovalStep`, enum `ApprovalStatus`, `ApprovalDecision`)
