A baseline per field, not per record
Comparing two records tells you they differ. It cannot tell you who changed, and a sync that guesses is a coin toss where the last writer wins. Ledger stores the value both sides agreed on, field by field, so drift on each side is known independently — and “both changed” becomes a conflict with a policy instead of an accident.
three-way, not two-way
Some transforms only run one way
A first and last name join into a display name exactly. Splitting one back apart is a guess, and a guess repeated nightly rewrites somebody’s name over a year. So each mapping declares whether it is reversible, and the unsafe direction is reported and held rather than applied. That single rule is most of the difference between a sync you can leave running and one you cannot.
lossy → reported, never guessed
The plan is the product
A dry run is not an estimate of what would happen; it is the same decision list the real run executes. So you can read it, argue with it, show it to a client and store it beside the run. Every line names the field, the direction, the old value, the new one and the rule that chose.
plan → apply · identical code path
The second run does nothing
A run repeats its passes until nothing changes, capped at three — because creating a record on the far side produces a record that then needs its own computed fields, and a single-pass engine leaves that until tomorrow. Run it again and it writes zero. That is the property you actually want from something running at four in the morning.
idempotent · and it says so