SnapshotDiffer
Computes the structural Changes between two Snapshots. This is the pure heart of the engine (design §3): it reads two snapshots, never knowing where they came from (git ref, file, live), and emits direction-neutral deltas for the classifier to judge.
Identity is by serial name plus Contract.base for contracts (#200; design §8 covers how pre-base snapshots still pair) and by key for elements, so field reordering yields no change (the Snapshot model already normalizes order). A declared renames mapping lets a moved type be followed as a Change.ContractMoved rather than a spurious remove + add (design §8).
Output order is deterministic: config changes first, then contracts in serial name order, then each contract's member deltas in a fixed order, and finally the per-snapshot static-defect scans over the current snapshot — Change.CoverageGap for each unanalysable type, then Change.DiscriminatorCollision for each unserializable subtype/discriminator clash. A contract that was OPAQUE in the baseline but is analysed now is the one exception: it has no prior shape to diff, so it yields a Change.CoverageGap in its serial-name slot instead of a remove + add.