# ApprovalStep

## Description

ApprovalStep is the runtime instance of one ordered step in an `ApprovalRequest`'s routing chain. Each step carries a `stepOrder` (unique within the parent request), a human-readable `name`, a `minimumApprovals` threshold, and a status of `PENDING` / `IN_PROGRESS` / `APPROVED` / `REJECTED`.

## Domain Model Definitions

### Model type

Stateful

#### State Transitions

```mermaid
stateDiagram-v2
    [*] --> Pending: createApprovalRequest (trailing steps)
    [*] --> InProgress: createApprovalRequest (lowest-stepOrder step)
    Pending --> InProgress: approveApprovalStep
    InProgress --> Approved: approveApprovalStep
    InProgress --> Rejected: rejectApprovalStep
    InProgress --> Pending: resubmitApprovalRequest
    Approved --> Pending: resubmitApprovalRequest
```

`Pending --> InProgress: approveApprovalStep` represents next-step activation: when an `approveApprovalStep` command on the prior step satisfies its voting threshold, the next step's row transitions PENDING → IN_PROGRESS as a side effect within the same transaction. The transition table below disambiguates this from the `IN_PROGRESS → APPROVED` self-transition with the `activate-next` vs. `approve` rows.

| Operation | From | To | Command |
|-----------|------|----|---------|
| activate | PENDING | IN_PROGRESS | [createApprovalRequest](../command/CreateApprovalRequest.md) (first step) / [approveApprovalStep](../command/ApproveApprovalStep.md) (next step) / [resubmitApprovalRequest](../command/ResubmitApprovalRequest.md) (first step on restart) |
| approve | IN_PROGRESS | APPROVED | [approveApprovalStep](../command/ApproveApprovalStep.md) |
| reject | IN_PROGRESS | REJECTED | [rejectApprovalStep](../command/RejectApprovalStep.md) |
| reset | IN_PROGRESS, APPROVED | PENDING | [resubmitApprovalRequest](../command/ResubmitApprovalRequest.md) |

### Command Definitions

- [createApprovalRequest](../command/CreateApprovalRequest.md)
- [approveApprovalStep](../command/ApproveApprovalStep.md)
- [rejectApprovalStep](../command/RejectApprovalStep.md)
- [resubmitApprovalRequest](../command/ResubmitApprovalRequest.md)

### Query Definitions

### Models

- ApprovalStep

### Invariants

- `stepOrder` is unique within a single `ApprovalRequest`
- `stepOrder` is a positive integer and represents a sequential position in the chain
- `name` is required and non-empty
- `minimumApprovals` is a positive integer no greater than the number of `ApprovalStepAssignee` rows resolved at the moment the step transitions to `IN_PROGRESS`
- PENDING: not yet activated; the lowest-`stepOrder` step never has this status because creation activates it directly
- IN_PROGRESS: exactly one step per request can hold this status at any time, and that step is the lowest-`stepOrder` step that has not yet reached APPROVED
- `resolvedAt` is null while status is `PENDING` or `IN_PROGRESS` and is set when status reaches `APPROVED` or `REJECTED`
- Once the parent `ApprovalRequest.status` becomes terminal via a request-level command (`WITHDRAWN` or `CANCELLED`), or non-terminal `REVISION_REQUESTED` via `sendBackApprovalStep`, step rows are not updated and the status may stay `IN_PROGRESS` or `PENDING` indefinitely; consumers must join to `ApprovalRequest.status` to determine actionability

### Relationships

- **Belongs To ApprovalRequest**: Each step references exactly one parent request
- **Has Many ApprovalStepAssignees**: A step holds one or more assignee rows that vote on it; for role-based assignees the rows are materialized into per-user rows at the `PENDING → IN_PROGRESS` transition
- **Created From ApprovalPolicyStep (optional)**: In policy mode the row is copied from an `ApprovalPolicyStep` at request creation; in direct mode there is no source policy step. Either way the row tracks runtime status independently of the source
- **References ApprovalDecisions**: Step-level `ApprovalDecision` rows (decision = APPROVE / REJECT / SEND_BACK / DELEGATE) carry `approvalStepId` pointing to this step
