Before you start
- The SQL editor for the engine, with three tables open in it: the findings the sweep writes, the crosswalk that pairs the two sides, and the integration ledger that records every attempt to cross. Their names are on Names, and how to read the ledger is on What replaces the audit log.
- Access to the backend repo’s Actions page on GitHub, for the workflows below. Each workflow’s name and its boxes are on Names.
- The record open in Zoho, if it exists there.
- Nothing else touching the same record. A push writes every shared field, so an edit on the other side in the same few seconds is lost.
The four kinds of finding
The sweep writes one of four kinds. Read the kind first; it tells you which steps apply.
A finding closes itself: once the next sweep no longer sees the difference, the finding is marked closed with the reason “not re-seen by sweep”. Nothing is deleted, so the history stays.
Steps
1
Read the finding and the crosswalk
In the SQL editor, open the finding. It names the module, the Zoho record, the row here, and for a field difference the field with both values. Then look the record up in the crosswalk: a live pair, a retired pair (a tombstone, which carries the reason it was retired), or no pair at all. A tombstone with a reason that starts “retired by” was written by the engine; the words after it name the caller.
2
Decide which side is right
If one side was deleted on purpose, the other side is the one to fix, and the delete policy for the module says what a delete there does; see Sync rules by module. If the record was created on one side and never crossed, look for its dead letter or its human-review flag: a create the engine refused is recorded with the reason, and fixing that reason is the whole fix. See Redrive a dead letter and Resolve a human-review flag.
3
A record that exists only in the CRM
If the row here was retired and Zoho still has the record, the two need pairing again: fill the engine’s id field on the Zoho record, then run the bootstrap-crosswalk workflow, first as a rehearsal and then for real. The rehearsal prints the pairs it would seed; the real run seeds them and then runs the sweep as a gate, which goes red if more findings stay open than the ceiling you give it. Before it seeds a pair it checks three things: the pair does not already exist, neither side is claimed by another live pair, and the ids are ones the engine accepts. A side already claimed by another pair is retired with the reason “bootstrap remap” and the new pair takes its place. A module whose Zoho records carry no engine id at all has nothing to seed from, and the run fails closed on it unless you name the module, on purpose, as one allowed to yield nothing.If the record was never here at all and is old (its create notification is long gone), run the backfill workflow for the module, as a rehearsal first: it counts what it would create, and only when you ask it to write does it create the rows and their pairs, up to the limit you give it. A record it cannot create is listed with the reason, most often a required link whose target has no pair yet: pair the target first.If the record was created in Zoho and refused on arrival, fix what the refusal names in Zoho and save the record. The next edit sends a fresh notification and the record applies.
4
A record that exists only here
A row here that was never pushed is sent by the next edit to a shared field. If it was refused because a link’s target had no pair yet, the engine re-sends it on its own when that pair arrives. If it was refused because the Zoho record was locked, the engine re-sends it when the lock lifts.A row whose Zoho twin was deleted keeps a tombstone and is never pushed again on purpose; for a module whose delete policy is deactivate, the row is inactive and stays that way. If the CRM record should exist again, create it in Zoho with the engine’s id in its id field and pair it with bootstrap-crosswalk as in the previous step.Rows that were never mapped in bulk, after a module is switched on, are sent with the backfill-outbound workflow: it counts them first, queues them when you ask it to write, and sends no more than the limit you give it. Go one, then ten, then a hundred, then the rest, reading the outcome between runs.
5
A pair that points at nothing here
The row here is gone and the pair is still live. Retire the pair in the SQL editor with a reason that says who removed the row and why; a pair retired with no reason gets a default one stamped by the engine, which is less useful to the next reader. The next sweep closes the finding.
How you know it worked
- The next sweep closes the finding. Run it by hand from the sweep workflow if you cannot wait for the night; see Run the sweep and the reconciler.
- The crosswalk shows one live pair for the record, and the record reads the same on both sides.
- No new dead letter and no new human-review flag for the record.
If it did not work
- The finding came back as a field difference. The pair is right and a field still differs. If the CRM owns the field, the reconciler copies the CRM’s value here; if the engine owns it, the next push sends the engine’s value. The reconciler never writes a link and never touches invoices, payments, subscriptions or subscription payments.
- The push was refused again. Read the reason on the integration ledger line. A locked record, a missing link target, or a value the CRM rejects each name themselves.
- The bootstrap gate went red. The seed itself succeeded; the red is the sweep saying more findings are open than the ceiling allows. Read them and fix the biggest group first.