When an existing install upgrades to the hashed-pending schema, its
on-disk pending.json still has the old {code: entry} format with no
hash/salt fields. The original PR #8056 assumed every entry had both
fields and would have KeyErrored in approve_code, list_pending, and
_cleanup_expired.
Guard each consumer:
- approve_code: skip entries that are not a dict, lack salt/hash,
or have a non-hex salt. Legacy entries simply fail to match.
- list_pending: tolerate missing 'hash' (show "legacy" placeholder)
and non-numeric created_at (skip the row).
- _cleanup_expired: treat malformed/legacy entries as expired so
they get pruned on the next call rather than wedging the file.
Regression tests cover all three consumers plus a mixed-malformed
case.