Most descriptions of a migration are written from the engine's side: what it reads, what it writes, in what order. This one is written from yours - what you do, what you're shown, and what you're being asked to decide, in the order it happens.
There are five phases. You are involved in two of them.
01Connect both systems
You authorise a source and a target. The source is opened read-only and stays that way for the entire run - nothing is written back to it, not even a marker recording that a record was migrated.
Credentials are held encrypted and scoped to the migration that needs them. Nothing else is required at this stage: no field mapping, no configuration file, no schema export. The next phase produces all of that.
02Schema discovery
Both systems are read live. Not a cached catalogue, not a list of fields someone wrote down when the connector was built - the actual shape of your instance, as it exists at the moment the run starts.
This matters more than it sounds. Two organisations using the same product have different schemas: different custom fields, different naming, different structures nested to different depths. A mapping derived from a generic description of the product would be wrong for almost everyone. A mapping derived from your instance is wrong for nobody.
What comes out is an inventory: the collections that exist, the fields on each, the types those fields carry, and how records reference one another.
03The coverage report
The two inventories are compared and every source field is matched to a target field where one exists. Then - this is the part that matters - each match is labelled rather than simply accepted.
A field is fully supported, partially supported, supported through a workaround, impossible in the target, or a decision only a person can make. The report is that list, in plain language, with a reason attached to every label that isn't the first one.
You are reading this before anything has been written. This is the last cheap moment in a migration - the point where changing your mind costs a conversation instead of a restoration.
04You confirm what won't survive
Anything not fully supported stops and asks. Not a warning in a log that nobody reads after the fact - a decision, presented before the run, that has to be answered.
Some answers are easy: a field the target has no concept of, that nobody has looked at in two years, can be dropped on purpose. Some are not: a field carrying information the business actually depends on may be worth restructuring the target to accommodate. Both are fine. Deciding without being told is not.
The run does not start until this list is empty.
05Execution
Now it writes. Not in the order the records were listed, and not all at once.
What “dependency waves” means
Records depend on other records. A record cannot be written into a container that doesn't exist yet. A field cannot hold a value of a type the target hasn't been told about yet. A link cannot point at something that hasn't been created yet.
So the work is sorted by what it depends on, and written in waves. Everything in a wave is independent of everything else in that wave, which means a wave can run in parallel. Nothing in a wave depends on anything in a later one.
Watching a run, this is what the progress you see is measuring: not a percentage of rows, but which wave is in flight and how much of it is left.
The reference drain
References are the exception to clean ordering, because they don't obey it. Two records can point at each other. A record can point at something scheduled for a much later wave. A record can point at something that lives outside this migration entirely.
So references aren't resolved once. Anything that can't be resolved at the moment it's encountered is set aside, and the set-aside pile is drained again after each wave - repeatedly, until a full pass produces nothing new. A cycle resolves on the second pass, because by then both ends exist.
Whatever is still unresolved at the end is reported, not silently dropped. And it stays resolvable: run a second migration later that brings in the missing end, and the earlier run's dangling references start working when it lands.
Verification
When the writing stops, the run isn't finished. Records are sampled from both systems and compared field by field - the source read again, fresh, and set against what actually exists in the target.
The output is a discrepancy report: what differs, where, and a proposed correction for each item, which a person approves or rejects. Approved corrections are applied as a normal write, in the same auditable way as everything else.
This is the difference between believing a migration worked and being able to sign off that it did.
When something goes wrong
A record that fails does not end the run. It is isolated, logged with the reason, and the run continues - one malformed row does not cost you the night's work.
If the run itself stops - a network partition, a rate limit, a machine going away, someone pressing stop - it resumes from where it stopped rather than from the beginning. Running the same migration twice does not produce two copies of anything: a record already written is recognised and skipped, not duplicated.
Every action, in every phase, is written to a history that is only ever appended to. Not to reconstruct what happened, which is the usual reason logs exist, but because a migration you cannot audit is a migration nobody can responsibly approve.