The agent lowercased the lookup key and broke every case-sensitive record
The lookup map held external provider identifiers, mixed case exactly as the source returned them. The agent saw two keys that differed only in capitalization, called it inconsistent, and normalized everything to lowercase on both write and read. New rows matched. Existing rows did not, and the missed lookups quietly fell back to a default that nobody noticed until a user reported the wrong account.
A representation change to an identifier is data corruption even when every test passes.
Where the instinct comes from
The task usually arrives as a vague quality complaint: lookups are flaky, the map keeps duplicating, two spellings exist for the same entry. The agent reaches for the most obvious consistency move: fold the two spellings together, normalize the key, make the map deterministic. Lowercasing ASCII looks harmless, and the helper it writes now normalizes on every path.
The tests it writes use the same lowercased inputs, so read matches write and CI turns green. Nothing in the new code touches a real existing record, so the drift between stored canonical values and normalized queries never shows up locally.
Why this class is hard to catch
Case sensitivity is owned by the storage and the upstream source, not by the code that reads it. An opaque token from another service is a string you compare exactly; lowercasing it changes the thing rather than how you look it up. That makes the bug invisible on three layers at once:
- The stored value changed for new writes, while historical rows kept their original mixed-case form.
- Read normalization masks the mismatch instead of surfacing it, so a miss looks like an empty result rather than an error.
- The agent authored both the write helper and the tests, so the seed data repeats its own convention and never represents real upstream data.
A unit test written against normalized input cannot fail on a collision between two genuinely distinct canonical keys.
Before normalizing any identifier
Confirm the source actually declares the ID case-insensitive in its documentation or the database collation. Opaque tokens, external user ids, and provider references are almost always exact-match, and the burden of proof sits with the claim that case does not matter.
When matching must be case-insensitive, keep two representations instead of rewriting one:
- Leave the stored canonical value untouched; never write a transformed primary key.
- Derive a separate normalized comparison key at read time, or keep a parallel lookup column.
- Replay existing records against the new path before merge and count actual misses, not just unit results.
- Seed a test with a realistic mixed-case identifier and assert the exact stored match, rather than reusing the normalized fixture.
If two distinct canonical values collapse under normalization, treat it as a conflict and fail loudly instead of merging them into one entry.
Keep the source of truth in its original form
Normalization has a legitimate place, but it belongs in the comparison layer, not in the stored value. A lowercased copy used for matching leaves the canonical token intact for display, upstream calls, and audit; collapsing the stored value burns information that cannot be reconstructed later.
Green tests do not prove representation safety when the same agent wrote both the path and the fixture. Before accepting a key cleanup, ask whether this identifier is yours to normalize at all.