The ledger
shipped 1.0.0-rc.2Every environment cwp manages has one file: cwp/refs/<env>.yml, committed with
the tree. It holds three things a crossing cannot work without, and they are
worth telling apart.
base and seen are not the same field
base is what cwp last transferred. It moves when a crossing completes, in
either direction, and at no other time. This is origin/main.
seen is how the environment looked the last time cwp read it.
The refusal does not depend on it. An upward crossing compares base
against what its own survey has read from the environment. A remembered
reading can go stale. The survey cannot, and it needs no recent
cwp status --remote.
Conflating them is the mistake the git analogy invites. git fetch moves
origin/main because the remote’s commits are the remote’s authored state.
Here they are not: an editor’s change noticed in wp-admin is not something you
have accepted, so it may not move the baseline cwp measures you against.
The consequence is concrete. If a read moved base, changed-here would be
unreachable. Every read would agree with itself, every conflict would quietly
vanish, and a push would overwrite somebody’s afternoon while reporting
success.
base records what the environment holds, item by item, with a hash over
what each item is. A role hashes by what it grants and a plugin by its version
and status. A widget area hashes by its contents in order and a settings key by
its value. A later comparison means something for that reason: a count would
not see a role renamed or a plugin deactivated at an unchanged version.
For content it also records which post each item is on that environment. A
post id is local to one install. The tree carries none of them: a committed
item has to mean the same thing on two sites. The ref is about one site, so it
can hold the pairing. The pairing is the one thing that survives a site losing
its identities to a restore. cwp adopt reads it before it falls back to the
slug, and never reads another environment’s.
The log
An append-only record of what happened, rendered by cwp log.
2026-08-22 09:14 deploy 12 content c3f8a2 main
restore point: 2026-08-22-091355-a3f
previous tree: 7f2c9ab (git checkout 7f2c9ab -- .)
A refusal is an event. A drift table can never give you that one. A prevented accident is a fact, and without the log it vanishes with the terminal scrollback. The log records why, not only that.
The events it writes are adopt, release, pull, push, deploy,
refused and observed. Together they cover every run that crossed, every run
the guards stopped from crossing, and every read that found the environment had
moved.
A survey writes an entry only when it found something. A read that agrees with the baseline is not news, and the log has a bound. Spending it on the cheapest and most frequent command there is would fill it with runs that changed nothing.
A read that disagrees lands in the log as observed, naming the artifacts
that had moved. It answers when did this start, the one question a drift
table cannot answer: the table says what differs now.
The entry carries both routes back where they exist: the remote restore point taken before an upward write and the dangling snapshot taken before the pull overwrote the tree. A backup nobody can find is not a backup, and a terminal is not where one is findable.
The log holds two hundred entries at most. The bound is about the file rather than about usefulness. The file goes into git, into diffs and occasionally into a merge by hand. A file nobody can read a conflict in is worse than a shorter history.
It is committed
The baseline travels with the tree, so a colleague’s checkout knows what the last transfer agreed on without anything shared out of band.
A merge conflict in the ref happens exactly when two people synced the same object. One of them has to find out. The conflict is information rather than friction.
The site’s copy is a witness
_cwp_sync stays on the post, and cwp writes it only on an upward write.
Authority is local: the ref is what cwp agreed to. The marker on the site is
evidence that survives a lost repository and tells a second checkout what
happened.
Where the two disagree about adoption, cwp reports both and chooses neither. Choosing would mean guessing which one is in front of you, and guessing wrong mints a second identity over the first.