An identity on any channel other than email or phone is stored exactly as typed, so two spellings of the same handle become two contacts and the duplicate check misses them.
Current state: normalizeIdentifier in internal/contact/identity.go lowercases email, reduces phone to digits, and returns everything else verbatim from its default branch. Only email and phone are writable today, so nothing is broken yet. The moment a third channel is accepted, @MariaPerez and @mariaperez land as separate identities under UNIQUE (channel, identifier) and both resolve to different contacts.
Proposal: refuse a channel that has no normalizer instead of silently passing the raw value through. Adding a channel then forces the decision about how its identifier is compared, which is the decision that keeps deduplication working.
Trigger: before any new channel joins the writable set.
An identity on any channel other than email or phone is stored exactly as typed, so two spellings of the same handle become two contacts and the duplicate check misses them.
Current state:
normalizeIdentifierininternal/contact/identity.golowercases email, reduces phone to digits, and returns everything else verbatim from its default branch. Only email and phone are writable today, so nothing is broken yet. The moment a third channel is accepted,@MariaPerezand@mariaperezland as separate identities underUNIQUE (channel, identifier)and both resolve to different contacts.Proposal: refuse a channel that has no normalizer instead of silently passing the raw value through. Adding a channel then forces the decision about how its identifier is compared, which is the decision that keeps deduplication working.
Trigger: before any new channel joins the writable set.