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 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.
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 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:
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: noneTwo 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 for the check that reading cannot do
- Approval gates for when someone else reviews it too