Skip to content

What happens when you type a command

shipped 2.0.0

Almost every command in cwp is the same pipeline. cwp pull, cwp push, cwp adopt and cwp scrub differ in what they carry, not in how they carry it.

Know that before you run one against production: the order of the steps is the safety model. Nothing in it serves convenience.

The architecture page draws the three diagrams below. Here they are in plain characters, so they read the same in a terminal, in a diff and on this page.

The ten steps

 1  survey  ─ read the world, write nothing
    │
 2  refuse  ─ the guards
    ├──► refused ─────────► stop · the refusal is logged
    │
    ├──► --dry-run? ─ yes ─► report the plan ─► stop
    │
 3  consent  ─ ask you
    ├──► declined ────────► stop
    │
 4  restore point
    │
 5  tree snapshot
    │
 6  apply  ─ the work
    │
 7  check the run against the plan
    │
 8  assert  ─ post-conditions
    │
 9  record what happened
    │
10  commit what was written
    │
    └──► report

Survey reads and writes nothing. It asks the environment what it holds and builds a plan. It runs under --dry-run too, so a dry run prints something true rather than a guess.

Refuse comes before consent, and runs in a dry run. The two are separate steps on purpose. A dry run against a protected environment still meets the refusal. Merge the two and cwp push prod --dry-run starts printing a plan for a write it would never permit.

The restore point sits between consent and the write. After you agree, so a run you declined backs nothing up. Before the write, so there is a way back. That position is the whole of it.

Assert runs after apply. A post-condition is a claim about the world, and the run checks it after the work. The mail guard is one: a pull that imported a database asserts that the guard intercepts outgoing mail, whichever path got there.

Commit comes after the post-conditions held. A commit of a state that failed its own checks would make the failure permanent in your history.

The plan is a contract, not a preview

The plan is not a summary of what is about to happen. It is what the guards read.

Each entry, an effect, says four things:

where it writesyour tree, this machine, or the far side
whether a failure stops the runone step of a pull is fatal; the other seven are not
what it can be reproduced fromthe file in the repository that the change comes from
whether it destroys anythingso a deletion is named before you agree to it

The first of those drives the guard ladder: a run that declares a write to a remote is the run that gets refused on a protected environment and takes a restore point. A write nobody declared is a write outside both.

So the plan cannot be decoration. It has to be true.

The run checks itself against it

Two comparisons run at the end of every command, and both are about cwp rather than about your project.

Whether the run wrote something the plan did not describe. Every subprocess cwp starts goes through one place. That place records every write that reached a far side. If the plan declared none, the run says so and names the commands.

Whether the run skipped a step the plan declared. Each step runs under its own declaration, so the run reports a step it announced and never took.

Both print a warning and ask you to report it. Neither stops your run: the work is right, and cwp’s account of the work is wrong.

! this run wrote 5 path(s) its plan does not declare, so they were not
  committed: media/2026/08/hero.png, … — please report this

Operations, and how they compose

An operation is one of those pipelines with one subject: read the content down, converge the roles, mint an application password. There are eighteen, and the register lists what each of them does.

A command is not always one operation. cwp pull runs six: content, the design system, settings, widget areas, roles, the inventory. It is one run rather than six:

  cwp pull
     │
     ├─ content ──┐
     ├─ bricks  ──┤
     ├─ settings ─┤        one refusal
     ├─ widgets ──┼──►  one restore point  ──►  the committed tree
     ├─ roles   ──┤       one confirmation
     └─ inventory ┘

Six operations in sequence would give six refusals and six restore points. The composite carries every sub-plan’s effects into its own plan instead. The ladder sees the whole run and asks once.

A composite that declared writes: "none" for a run whose parts wrote to production would meet no refusal and take no restore point. The effects travel for that reason.

Two seams

Everything host-specific sits behind one interface, and everything builder-specific behind another.

        the command you typed
                  │
                  ▼
             operations
              │       │
              ▼       ▼
      « Provider »  « Builder »
          │             │
          ├─ Cloudron   ├─ Bricks
          ├─ SSH        └─ none
          │  · any host
          └─ DDEV
             · this machine

No operation knows which host it is talking to. cwp push against a Cloudron app, a plain VPS over SSH and a local DDEV container is the same code. What differs is which restore point is available, and whether the transfer can say in advance what it would move.

The same holds for the page builder. A project on builder: none runs every command; the design-system steps have nothing to carry.

What this buys you, concretely: a second host is a new file behind the seam rather than a branch in twenty commands. The safety rules keep holding because they live above it.