Skip to content

The safety model

shipped 1.0.0

cwp exists to make one class of mistake impossible: pushing the wrong thing in the wrong direction to production. Six rules encode that, and they are not configurable away.

  1. Bulk data flows down, code flows up. See Asymmetric data flow for what “bulk” is doing there.

  2. Protected environments refuse upward writes. An environment marked protected: true rejects every upward write unless you pass --force. The refusal is read off the effects rather than off the command’s name, which is what makes that list complete rather than remembered.

  3. There is no bulk database push. It was designed, and then deferred. If it is ever built it will require the environment name typed back as confirmation — but that is a condition on a future command, not a description of a current one, and cwp db is local only.

  4. A remote restore point is taken before any upward write, unless explicitly disabled. On a host that cannot take one, disabling it makes the write refused rather than quietly unprotected.

  5. A local snapshot is taken before any downward write. cwp pull replaces your local database, and on a builder site that database is the only copy of your design work. So the pull snapshots first, as pre-pull-<env>-<timestamp>, and a snapshot that fails aborts the pull rather than warning — a safety net you believe in and do not have is worse than none. This is rule 4’s mirror image: the remote has its host’s backups, the local side has this.

  6. Every pull installs the mail guard, even when scrubbing is switched off. A pulled production database contains real customer addresses and can be tricked into sending mail; the guard intercepts and logs outbound mail instead of sending it. It is unconditional because a builder stack that ships its own SMTP bypasses the container’s mail catcher entirely.

And one that is not a refusal but belongs beside them: never raw SQL for URL replacement. WordPress stores serialised data and a page builder stores element trees as serialised JSON in postmeta, so a sed over a dump corrupts every array whose string lengths it changes. It is always wp search-replace.

The guards run in a dry run

--dry-run prints the plan and changes nothing — and it does not always exit 0. The refusals run; the confirmations do not. So cwp push prod --dry-run on a protected environment still exits 5, rather than printing a plan for a write it would never permit.

A dry run that stopped refusing would be describing a different command from the one you are about to type.

The rules have tests, and the tests have names

Twelve invariants are recorded as data in the source, each naming the test that asserts it — and a structural test fails the build when a named test is renamed or deleted. They are not documentation of intent; they are the tests, indexed by the rule they defend.

The safety page renders the catalogue, the guard ladder and the exit codes; Testing is the argument for why that is the product rather than a chore.