# Reviewing a draft

A drafted plan is a proposal, not a decision. Six things to check, in the order that catches the most for the least reading.

Source: https://opafra.com/docs/plans/reviewing-a-draft

---

A drafted plan is a proposal. It is usually structurally right and occasionally wrong
about something that matters, and the wrongness is rarely where a reader looks first.

Check these six things in this order. It catches the most for the least reading.

## Before you begin

You need a drafted plan open in the composer, and the **Operator** role or above to keep
or edit it.

## 1. The targets, before anything else

Read the target list of every step. This is the single highest-value check, because a
correct plan aimed at the wrong hosts is more dangerous than an incorrect plan aimed at
the right ones: the steps will work, and they will work on the wrong machines.

Watch for a step that inherited targets from the step above when it should have narrowed
them. Backing up a config on one host and deploying to twelve is a common and reasonable
shape; deploying to twelve when you meant one is not.

## 2. The order, especially around failure

Steps run top to bottom, and the run halts at the first failure. So ask of each step: if
this fails, is the plan in a state someone can recover from?

The specific thing to look for is a backup that comes after the change it protects, or a
validation that comes after the reload it should have gated. Both read fine and both are
useless in the order they were written.

## 3. Anything raw

Find every `ssh.cmd` and `ssh.script.file` step and read the actual text. These are the
steps Opafra cannot preview or reason about, so they are the ones where your reading is
the only check.

Ask whether a [named tool](/docs/reference/tools) would do the same job. A raw
`echo >> file` that a `file.lineinfile` could do is a step that will quietly duplicate
itself on every future run.

## 4. Destructive steps

Look for deletion, truncation, service stops, package removal, anything with `rm`, and
anything writing to a path outside the one the plan is about.

The question is not whether the step is correct. It is whether the step is **recoverable
if it is correct and the situation is not what you assumed**.

> **Warning**
>
> The composer drafts what you asked for. If a request implies deleting something, the
> draft will delete it, and it will do so competently. Reading for destructiveness is your
> job, not the draft's.

## 5. Variables and secrets

Every `{{ vars.x }}` should resolve to something. A reference to a variable that does not
exist is empty at run time, which turns a path into a bare `/` and a service name into
nothing.

Every credential should be a `secret://` reference rather than a literal. A literal
password in a plan is written into the plan, the run log, and every preview of it, and
nothing can redact a value it was never told was secret.

## 6. Conditions

If a step carries a condition, check both branches make sense. Remember that a malformed
condition **fails open**, so the step runs. A condition you meant as a guard, written
slightly wrong, is not a guard at all.

## Then dry run it

Reading catches intent. A [dry run](/docs/running/dry-run) catches reality, and it is the
step that finds the things reading cannot:

- Which hosts would actually change, and which are already in the target state
- Whether a variable stayed unresolved
- What a raw command resolves to once variables are substituted
- Whether the config you are deploying differs from the one in place, and how

A plan that reads correctly and dry runs against three hosts with one surprise is a plan
that just saved you an incident.

## What the composer is reliably good and bad at

Worth calibrating on, so you spend your attention where it pays:

**Reliably good at** structure, ordering, choosing a sensible tool for a stated job, and
wiring arguments consistently.

**Worth checking every time**: target lists, whether it reached for a raw command when a
tool existed, and anything where your intent was implied rather than stated.

The composer drafts; you decide. That division is the reason a drafted plan is safe to
work with at all.

## Verify the review caught everything

A review is finished when a dry run against the real targets produces no surprises. Run
one and read the report end to end:

```text title="dry run, after review"
Step 1  Back up the config      file.backup     3 hosts   would change
Step 2  Deploy the config       template.deploy 3 hosts   2 no change, 1 would change
Step 3  Reload nginx            nginx.reload    3 hosts   will run (not simulated)

Unresolved variables: none
```

Two things prove the review worked: **Unresolved variables: none**, and every host
appearing under every step you expected it to. A host missing from a step means its
targets were narrower than you read them to be.

## Next steps

- [Dry runs](/docs/running/dry-run) for the check that reading cannot do
- [Approval gates](/docs/running/approval-gates) for when someone else reviews it too
