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.

PowerShell
m365-governance migration-verify baseline.json verification.json `
  --kind tenant-to-tenant --performed-by "a migration tool" `
  --out record.json --report report.md

What 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:

Text
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        e68d1652512b5e7c35b1178e9f53343f7a50fe880b2dc5f40680418003f3e438

The 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:

RefusalWhy
a baseline that is not earlier than the verificationit is not a baseline
the same read handed in twicethere is nothing to compare
two reads that record different permissions, or different accounts where a read names oneevery 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 matchedtwo 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.