# UpdateApprovalPolicy

## Permission Scope

policy

## Overview

UpdateApprovalPolicy modifies a `DRAFT` policy in place — the caller may change the `name`, `description`, and the entire steps-and-assignees subtree. The command rejects attempts to update an `ACTIVE` or `INACTIVE` policy because those states are immutable and any configuration change must go through a new draft. The replacement steps and assignees overwrite the prior subtree atomically.

## Business Rules

- The target policy must be in `DRAFT` status; updates on `ACTIVE` or `INACTIVE` policies are rejected
- `name`, when supplied, is non-empty
- `purpose` is not editable after creation
- The replacement steps array (when supplied) must satisfy the same shape rules as `createApprovalPolicy`: at least one step, unique `stepOrder`, every step has at least one assignee, valid `minimumApprovals`, and assignee `userId` XOR `roleId`
- The replacement is atomic: prior step and assignee rows are deleted and the new rows inserted within a single transaction
- `activatedAt` remains null while the policy stays in `DRAFT`

## Process Flow

```mermaid
flowchart TD
    A[Receive update request] --> B{Policy exists?}
    B -->|No| BX[Return error: POLICY_NOT_FOUND]
    B -->|Yes| C{Policy.status = DRAFT?}
    C -->|No| CX[Return error: INVALID_STATUS_TRANSITION]
    C -->|Yes| D{Replacement steps supplied?}
    D -->|No| F[Patch header fields]
    D -->|Yes| E{Validate steps shape<br/>same rules as create}
    E -->|Invalid| EX[Return shape-specific error]
    E -->|Valid| F[Patch header fields]
    F --> G[Replace step subtree atomically:<br/>delete prior PolicyStep + PolicyStepAssignee,<br/>bulk-insert new rows]
    G --> H[Return updated policy]
```

## External Dependencies

- None

## Error Scenarios

- **POLICY_NOT_FOUND**: Specified `policyId` does not exist
- **INVALID_STATUS_TRANSITION**: Target entity is not in a status that permits this operation
- **MISSING_REQUIRED_FIELD**: A required input field is missing or empty
- **POLICY_HAS_NO_STEPS**: The supplied `steps` array is empty
- **DUPLICATE_STEP_ORDER**: Two or more steps share the same `stepOrder`
- **STEP_HAS_NO_ASSIGNEES**: A step has no assignees
- **INVALID_MINIMUM_APPROVALS**: A step's `minimumApprovals` is not a positive integer
- **ASSIGNEE_USER_ROLE_XOR_VIOLATED**: An assignee row sets both `userId` and `roleId`, or neither
- **ASSIGNEE_FLAGS_INCOMPATIBLE**: An assignee row sets `required = true` on a role assignee, or sets `roleQuorum` on a user assignee
- **DUPLICATE_USER_ASSIGNEE**: Two user assignees within the same step target the same `userId`
- **DUPLICATE_ROLE_ASSIGNEE**: Two role assignees within the same step target the same `roleId`

## Test Cases

- updates the name of a DRAFT policy
- updates the description of a DRAFT policy
- replaces the steps subtree atomically and prior step rows are gone
- rejects updating purpose after creation
- throws INVALID_STATUS_TRANSITION when policy is ACTIVE
- throws INVALID_STATUS_TRANSITION when policy is INACTIVE
- throws POLICY_NOT_FOUND for a non-existent policyId
- throws shape-specific errors for invalid replacement steps
- throws MISSING_REQUIRED_FIELD when name is supplied as empty or whitespace
