The list a screen removes from is the union of the device's contact table and
the contacts MeshTerm remembers for it, so an entry can exist only on our side
— or the firmware can drop one since we last read. Removing such a contact hit
ERR_CODE_NOT_FOUND and reported "couldn't remove", leaving a row the reader had
no way to get rid of.
The device layer now raises ContactNotOnDeviceError for that code, the same
class (and the same table lookup) the send path already raises. The single
removal reports it as a warning naming what happened, forgets our half and ends
the visit; the purge sweep counts it as purged rather than failed. The
simulator refuses the same way, so the path is walkable under --mock.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>