# ApprovalStepAssignee

## Description

ApprovalStepAssignee represents one named approver on a runtime `ApprovalStep`.

## Domain Model Definitions

### Model type

Stateful

#### State Transitions

```mermaid
stateDiagram-v2
    [*] --> Pending: createApprovalRequest
    [*] --> Pending: delegateApprovalStep
    Pending --> Approved: approveApprovalStep
    Pending --> Rejected: rejectApprovalStep
    Pending --> Delegated: delegateApprovalStep
    Approved --> Pending: resubmitApprovalRequest
```

`[*] --> Pending: delegateApprovalStep` refers to the delegate's newly inserted row (the substituted approver), not the delegator's seat. The delegator's row follows the `Pending --> Delegated` arrow in the same command, and the new delegate row is created with `required` carried over verbatim from the originating row. `DELEGATED` rows are deliberately not reset by `resubmitApprovalRequest` — the delegatee keeps the seat across resubmit rounds.

| Operation | From | To | Command |
|-----------|------|----|---------|
| approve | PENDING | APPROVED | [approveApprovalStep](../command/ApproveApprovalStep.md) |
| reject | PENDING | REJECTED | [rejectApprovalStep](../command/RejectApprovalStep.md) |
| delegate | PENDING | DELEGATED | [delegateApprovalStep](../command/DelegateApprovalStep.md) |
| reset | APPROVED | PENDING | [resubmitApprovalRequest](../command/ResubmitApprovalRequest.md) |

### Command Definitions

- [approveApprovalStep](../command/ApproveApprovalStep.md)
- [rejectApprovalStep](../command/RejectApprovalStep.md)
- [delegateApprovalStep](../command/DelegateApprovalStep.md)
- [resubmitApprovalRequest](../command/ResubmitApprovalRequest.md)

### Query Definitions

- [listApprovalRequestsForApprover](../query/ListApprovalRequestsForApprover.md)

### Models

- ApprovalStepAssignee

### Invariants

- Exactly one of `userId` or `roleId` is set per row (XOR); after the parent step transitions to `IN_PROGRESS`, every row has `userId` set (role-expanded rows additionally retain `roleId` for group identity)
- `required` is meaningful only on user-direct rows (`roleId` is null)
- `roleQuorum` is meaningful only on role-expanded rows (`roleId` is set); on user-direct rows it must be null
- Once the parent step transitions to `IN_PROGRESS`, the assignee set is frozen — later role-membership changes do not alter the materialized rows of a step already in progress
- `resolvedAt` is null while status is `PENDING` and is set when status reaches `APPROVED`, `REJECTED`, or `DELEGATED`
- A user can hold at most one PENDING row per step at a time
- Once the parent `ApprovalRequest.status` becomes terminal via a request-level command (`WITHDRAWN` or `CANCELLED`), or non-terminal `REVISION_REQUESTED` via `sendBackApprovalStep`, assignee rows are not updated and the status may stay `PENDING` indefinitely; inbox consumers must join to `ApprovalRequest.status` to determine actionability

### Relationships

- **Belongs To ApprovalStep**: Each assignee row references exactly one parent step
- **References User (cross-module)**: When `userId` is set, references a `User` row in `user-management` whose row is never hard-deleted, so the FK target outlives the assignee row
- **References Role (cross-module)**: When `roleId` is set, references a `Role` row in `user-management`; the `roleId` is also retained for traceability on user-materialized rows after role expansion
- **Created From ApprovalPolicyStepAssignee (optional)**: In policy mode the row is copied from an `ApprovalPolicyStepAssignee` at request creation; in direct mode there is no source policy assignee. Either way the row tracks runtime status independently of the source
- **Originates ApprovalDecision**: Step-level `ApprovalDecision` rows reference the assignee implicitly through `approvalStepId` and `decidedByUserId`
