# Approvals and Gates

CAPTF can stop a `TerraformCluster` before it changes infrastructure and wait for a person to say yes. It has two such gates, and they apply to the cluster kind only. This chapter explains what each gate does, what it binds, what is deliberately not gated, and what an operator needs to run them safely. The task-oriented commands are in [Plan Approval](<https://captf.io/docs/user-guide/plan-approval/index.md>).

The pages:

1. This page: the gates, a decision table and the scaling questions.
2. [Manual plan approval](<https://captf.io/docs/concepts/approvals/manual-approval/index.md>): `applyPolicy: Manual`, the plan Job, `status.plan` and the approved apply.
3. [What the plan hash binds](<https://captf.io/docs/concepts/approvals/fingerprint/index.md>): the `p2:` fingerprint.
4. [The destructive-plan guard](<https://captf.io/docs/concepts/approvals/destructive-guard/index.md>): the check on every cluster apply under `Automatic`.
5. [Operating the gates](<https://captf.io/docs/concepts/approvals/operating/index.md>): commands, conditions, events, retries, upgrades and who can approve.
6. [Other manual actions](<https://captf.io/docs/concepts/approvals/other-manual-actions/index.md>): restores, abandoning an object, and the fixes that are not approvals.
7. [Limits](<https://captf.io/docs/concepts/approvals/limits/index.md>): what an approval does and does not promise.

## The two gates

| Gate | Set by | What it stops | Annotation that releases it | Binds |
| --- | --- | --- | --- | --- |
| **Manual plan approval** | `spec.applyPolicy: Manual` on a `TerraformCluster` or its template | Every apply except the first one | `captf.io/approve-plan=<plan hash>` | The plan: what each change does, and the output changes |
| **Destructive-plan guard** | Always on, for every `TerraformCluster` apply | An apply whose plan deletes or replaces a resource | `captf.io/approve-destructive-plan=<inputs hash>` | The inputs, not the plan |

Under `Manual` the first gate subsumes the second: approving a plan also approves the deletes and replacements that plan lists, so the second annotation is never needed there.

Both gates are specific to `TerraformCluster`, with one narrow extension. `applyPolicy` exists on no other kind, and the Job builder adds the guard to a `TerraformCluster`’s apply, and to a `TerraformMachinePool`’s apply **only when it renders cluster exports that differ from its last successful apply** (see [Machine pools](<https://captf.io/docs/concepts/approvals/destructive-guard/#machine-pools>)). A `TerraformMachine`’s apply is never gated: its instance is immutable, and Cluster API replaces it rather than its apply changing it. Destroy, restore, refresh and drift Jobs are never gated on any kind.

## What is gated

An apply decision has one reason; the reason decides whether a gate sees it.

| Situation | Kind | `Automatic` | `Manual` |
| --- | --- | --- | --- |
| First apply of a new object (no state) | `TerraformCluster` | Guard runs, nothing to delete | Not gated, applies at once |
| Changed inputs (image, spec, variables) | `TerraformCluster` | Guard: blocked if the plan deletes or replaces | Plan, then wait for `approve-plan` |
| Retry after a failed apply | `TerraformCluster` | Guard | Plan, then wait (an earlier approval is reused if the re-plan matches) |
| Drift remediation (`drift.action: Remediate`) | `TerraformCluster` | Guard | Plan, then wait |
| State with no inputs hash | `TerraformCluster` | Guard | Plan, then wait |
| Apply that renders a changed set of cluster exports | `TerraformMachinePool` | Guard: held if the plan deletes or replaces; the pool keeps applying the last exports | Not applicable: no `applyPolicy` |
| Any other apply | `TerraformMachinePool` | Not gated | Not applicable: no `applyPolicy` |
| Any apply | `TerraformMachine` | Not gated | Not applicable: no `applyPolicy` |
| Destroy, restore, refresh, drift check | Any | Not gated | Not gated |

## Scaling is not gated

Scaling does not go through an approval, so a large change to machine counts does not wait for one:

- **Scaling a `MachineDeployment` up** creates `TerraformMachine` objects. Each one’s first apply creates its machine; there is nothing for a gate to check.
- **Scaling it down** deletes `Machine` objects. After Cluster API drains them, each `TerraformMachine` runs a destroy Job, which is never gated.
- **A fixed-replica `MachinePool`** re-applies its pool module when `spec.replicas` changes. Such an apply is not gated, unless it also renders changed cluster exports.
- **An autoscaled `MachinePool`** scales in the cloud, with no Terraform run at all (see [Machine Pools](<https://captf.io/docs/user-guide/machine-pools/index.md>)).

A 100-machine `MachineDeployment` therefore scales without an approval. If a change to shared infrastructure must wait for a person, that infrastructure belongs in the cluster module. See [Limits](<https://captf.io/docs/concepts/approvals/limits/index.md>) for how cluster outputs reach machines and pools.

## In this section

- **Approve a Plan**

  ---

  The commands to review and approve a destructive plan or a manual plan.
- **Manual Plan Approval**

  ---

  applyPolicy: Manual, the plan Job, status.plan and the approved apply.
- **What the Plan Hash Binds**

  ---

  The p2: fingerprint an approval names, and what it covers.
- **The Destructive-Plan Guard**

  ---

  The check on every cluster apply under Automatic.
- **What Approval Does Not Guarantee**

  ---

  What an approval does and does not promise.

## Where the state lives

An approval rests on three things that other chapters describe:

- the plan key, a Secret that makes the plan hash a keyed value (see [Run inputs and the plan key](<https://captf.io/docs/concepts/secret-management/run-inputs/#the-plan-key>));
- `status.plan`, written by the controller from what the runner reports;
- annotations on the object, which are the approval itself.

> [!NOTE]
>
> **See also**
>
> - [Plan Approval](<https://captf.io/docs/user-guide/plan-approval/index.md>) for the commands.
> - [Conditions](<https://captf.io/docs/reference/conditions/#applyjobsucceeded>) and [Events](<https://captf.io/docs/reference/events/index.md>) for the exact reasons.
> - [Annotations, Labels and Finalizers](<https://captf.io/docs/reference/annotations-labels/index.md>).
> - [Security Model](<https://captf.io/docs/concepts/security-model/index.md>).
