# DeactivateApprovalPolicy

## Permission Scope

policy

## Overview

DeactivateApprovalPolicy retires an `ACTIVE` policy by transitioning it to `INACTIVE`. After deactivation the policy is no longer returned by `listApprovalPolicies({ status: "ACTIVE" })` and cannot be selected as the source of a new `ApprovalRequest`, but in-flight requests already created from it continue to progress unchanged. Inactive rows are retained indefinitely so that requests can resolve `ApprovalRequest.sourcePolicyId` back to `name` and `activatedAt` for reporting.

`INACTIVE` is a terminal state for an approval policy: this module deliberately does not expose a `reactivateApprovalPolicy` command, because `ACTIVE` policies are immutable (so reactivating the row offers no new capability vs. drafting a new policy with the same content) and the new-DRAFT-then-activate path already covers every "reuse this name" use case while keeping `(purpose, name)` ACTIVE-uniqueness intact. The supported successor path is therefore to draft a new policy under the same `(purpose, name)` and activate it, which becomes the next revision in the lineage and supersedes the deactivated one.

## Business Rules

- The target policy must be in `ACTIVE` status; deactivation is rejected on `DRAFT` and `INACTIVE` policies
- After deactivation, the policy and its nested rows remain immutable
- The row is never hard-deleted so that historical `ApprovalRequest.sourcePolicyId` references remain resolvable
- `INACTIVE` is terminal; this module does not expose `reactivateApprovalPolicy`

## Process Flow

```mermaid
flowchart TD
    A[Receive deactivate request] --> B{Policy exists?}
    B -->|No| BX[Return error: POLICY_NOT_FOUND]
    B -->|Yes| C{Policy.status = ACTIVE?}
    C -->|No| CX[Return error: INVALID_STATUS_TRANSITION]
    C -->|Yes| D[Update policy: status = INACTIVE,<br/>deactivatedAt = now]
    D --> E[Return deactivated 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

## Test Cases

- deactivates an ACTIVE policy and stamps deactivatedAt
- throws INVALID_STATUS_TRANSITION when the policy is in DRAFT
- throws INVALID_STATUS_TRANSITION when the policy is already INACTIVE
- throws POLICY_NOT_FOUND for a non-existent policyId
