30-second answer
migration-verify takes two reads of the same estate, one from before a move
and one from after it, and produces a record of what arrived and what did not.
It is never produced by whatever performed the move: a report by the producer
of the work is the producer's account of its own work, and that is the
condition this contract exists to remove.
m365-governance migration-verify baseline.json verification.json `
--kind tenant-to-tenant --performed-by "a migration tool" `
--out record.json --report report.mdWhat it prints
From the engine's own end-to-end test at 1.0.0b9: two simulated reads of one
library, in which one file did not arrive, compared with the command above
without --performed-by. Long lines are wrapped:
contoso-projects
baseline baseline-001 (2026-07-04T06:10:22Z)
verification verification-001 (2026-08-11T05:58:41Z)
compared presence, count, content, size, authorship
not compared versions: at least one read carries no versions for the items
they share, though the source is not declared unable to
provide it
not compared permissions: at least one read carries no permissions for the
items they share, though the source is not declared unable to
provide it
not compared sharing-links: at least one read carries no sharing_links for
the items they share, though the source is not declared unable
to provide it
fail 2
digest e68d1652512b5e7c35b1178e9f53343f7a50fe880b2dc5f40680418003f3e438The two failures are the file that did not arrive and the count that moved with it.
Two things in that output are the product. compared lists what was
actually examined, so a reader never has to infer from silence that a
dimension was fine. not compared carries a reason for each one, and the
reason distinguishes a limit of the source from a thinner read than the source
allows: only the second is worth fixing by reading again.
unknown is not a failure, and the exit code says so
The command exits 1 when a fail was established. The contract also defines
invalid-evidence, which would count the same way; this version of the
comparison never produces it.
An operator who could not read half the estate has a coverage problem, not
a migration problem, and conflating them is what this whole contract refuses.
An unknown never moves the exit code. A missing item does.
What it refuses to produce
The record is refused rather than written when the inputs cannot support it, because printing a warning and continuing would put the operator's mistake inside a document that then travels as evidence:
| Refusal | Why |
|---|---|
| a baseline that is not earlier than the verification | it is not a baseline |
| the same read handed in twice | there is nothing to compare |
| two reads that record different permissions, or different accounts where a read names one | every absent item is then a permission difference wearing the clothes of a loss. Reads taken by migration-read record the permissions but not the account, so two people holding the same permissions are not told apart |
a size-only comparison concluding that content matched | two files of equal size differ |
The report
--report renders the record for people, as HTML or Markdown, chosen by the
file's extension or by --report-format. It states its own limits in the
document, and carries no percentage, deliberately:
There is no percentage in this report, deliberately. A single number would be read instead of the sections, and the sections are what answers the question.
A difference is established only where both reads could see the item. Where either could not, the result is recorded as not established and never as a loss.
What it does not read
Age. An item dated 1984 is semantically absurd and legitimately survives transport, so nothing here infers loss from how old a timestamp is. The product may show a difference; it will not invent one from a date.
Nor does it compare when a file was modified, or its columns and content type: no dimension covers them.
Options
--kind is what kind of move it was, and --performed-by what performed
it, so a reader can see it was not us.
--out writes the record. --report writes the readable report, in
--report-format markdown or html. The record is the evidence and the
report is the document; both are delivered, and neither replaces the other.
Exit codes
0 no failure established. 1 a failure was established, or the record was
refused as incoherent. 2 a file does not declare the migration-read/1.0.0
contract. Only that declaration is checked: the reads are not validated against
the schema.