# ActivateApprovalPolicy

## Permission Scope

policy

## Overview

ActivateApprovalPolicy promotes a `DRAFT` policy to `ACTIVE`, stamping `activatedAt` and making the row eligible for selection by `listApprovalPolicies` and use as a template by `createApprovalRequest`. The command rejects activation when an existing `ACTIVE` policy already holds the same `(purpose, name)` — at most one `ACTIVE` row per pair is allowed at any time, so administrators must `deactivateApprovalPolicy` the prior row first. The activated policy and its nested rows become immutable.

## Business Rules

- The target policy must be in `DRAFT` status
- The target policy must contain at least one step and every step must have at least one assignee
- No other `ACTIVE` policy may exist for the same `(purpose, name)` pair at activation time
- The module stamps `activatedAt = now` and transitions `status` to `ACTIVE` in the same transaction
- After activation the policy and its `ApprovalPolicyStep` / `ApprovalPolicyStepAssignee` rows are immutable

## Process Flow

```mermaid
flowchart TD
    A[Receive activate 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{At least one step<br/>and every step has<br/>at least one assignee?}
    D -->|No| DX[Return error: POLICY_NOT_READY_TO_ACTIVATE]
    D -->|Yes| E{Another ACTIVE policy<br/>with same (purpose, name)?}
    E -->|Yes| EX[Return error: ACTIVE_POLICY_CONFLICT]
    E -->|No| F[Update policy: status = ACTIVE,<br/>activatedAt = now]
    F --> G[Return activated 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
- **POLICY_NOT_READY_TO_ACTIVATE**: Policy has zero steps or at least one step has zero assignees
- **ACTIVE_POLICY_CONFLICT**: Another `ACTIVE` policy already exists for the same `(purpose, name)` pair

## Test Cases

- activates a DRAFT policy, transitions status to ACTIVE, and stamps activatedAt
- activates a DRAFT policy when no prior row exists for (purpose, name)
- activates a DRAFT policy when one or more prior INACTIVE rows exist for (purpose, name) (lineage continues)
- throws ACTIVE_POLICY_CONFLICT when another ACTIVE policy holds the same (purpose, name)
- allows activation after the prior ACTIVE policy is deactivated
- throws INVALID_STATUS_TRANSITION when the policy is already ACTIVE
- throws INVALID_STATUS_TRANSITION when the policy is INACTIVE
- throws POLICY_NOT_FOUND for a non-existent policyId
- throws POLICY_NOT_READY_TO_ACTIVATE when the policy has zero steps
- throws POLICY_NOT_READY_TO_ACTIVATE when any step has zero assignees
- successive activations under the same (purpose, name) each stamp a fresh activatedAt; lineage is recoverable by ordering rows by activatedAt
