# collections.diff Compares two lists of records matched by `key` and says what happened between them: records that were added, records that were removed, and records present in both whose contents changed, with the names of the fields that differ. It is the heart of a sync job, an audit log or a "review changes before import" screen. ORDER: `added` and `changed` follow the order of the `after` list, `removed` follows the order of the `before` list. `unchanged` is only a count, because a caller showing a diff never wants the untouched rows back. CHANGED FIELDS are listed by name, sorted by Unicode code point. Sorting, rather than keeping the records' own field order, is deliberate: JavaScript reorders integer-like object keys ("2" before "a") and the three languages would otherwise disagree about the same records. EQUALITY is deep JSON equality, spelled out the same way in all three languages rather than inherited from each one's `==`: - numbers compare by value, so `1` and `1.0` are equal (JSON does not distinguish them, and a round trip through a float column must not show up as a change); - `true` is not `1`, and `"1"` is not `1`: a value that changed type did change; - lists are equal when they have the same length and equal items in the same order; - maps are equal when every field is equal, where a missing field and a null field count as the same. That is the house rule across the collections capabilities, and it stops a serialiser that drops nulls from making every record look changed. It applies to the top-level fields too: a field that goes from null to absent is not reported. KEYS are rendered the way `collections.group-by-key` renders them: strings are themselves, whole numbers their digits, booleans `true`/`false`. So a record keyed `1` in one list and `"1"` in the other is the same record (and the key field itself is then reported as changed, because its type did change). A fractional number, a list or a map at the key is an error. ERRORS, not guesses: a record with no value (or null) at the key, and a key that appears twice in the same list. A diff that silently picked one of two duplicates would report changes that never happened. Also an empty key name and a non-list argument. Inputs are never modified; the records in the result are the records passed in. The result types are generic (`RecordDiff`, `RecordChange`) and this function returns them over `record`. That keeps the door open for a typed variant, and it is also how the manifest gets a record-valued field past the Python type generator, which does not yet import `Any` for a bare `record` field.