TL;DR

I use two identifiers per row: a UUID as the public ID (URLs, APIs, forms) and an auto-increment integer as the internal key (foreign keys, joins).

The bug class: those two leak into each other — a foreign key stores a UUID, a <select> posts a UUID where a key is expected, an action writes a raw key it never resolved.

The fix is a discipline: resolve UUID → key at the write boundary, and reject unknown IDs instead of inventing rows or writing nulls.

Why two IDs at all