Relationships
#967 $identify events are never consumed — anonymous web history never binds to the account
Opened by keeb · 7/4/2026· Shipped 8/15/2026
The web app emits $identify at sign-in (lib/auth.ts → identify(userId, getAnonymousId(req)) in lib/telemetry.ts) to link the anonymous browser device to the account. Nothing on the telemetry side consumes it: the stats consumer (services/telemetry/lib/consumers/stats.ts, updateIdentityMap) only binds a device when an event carries BOTH username and distinct_id — and $identify carries the user id as its distinct_id with the anon id buried in properties.$anon_id, so it is ingested and ignored.
The gap is structural, not incidental: the moment a user signs in, resolveDistinctId switches their web events' distinct_id from the anonymous IP+UA hash to their user id, so the anon hash never co-occurs with a username on any event. $identify was the only bridge, and only the emitting half was built.
Impact (measured in prod, 2026-07-04): anonymous web devices strand as ghost owners in the ClickHouse score_grants ledger. They can only earn presence grants (10/device-day), so the score impact is dust — 63,210 points across all stranded web ghosts combined — but the anon→identified funnel story is broken for web: pre-signup browsing history never folds into the profile, and analytics can't connect the visitor who converted to the account they became.
Steps to reproduce:
- Browse swamp-club.com signed out (events carry
distinct_id= IP+UA hash). - Sign in — a
$identifyevent withproperties.$anon_idis ingested. - Inspect the Mongo
identity_mapentry for the user: the anon hash is never added; its grants stay owned by a ghost.
Fix sketch: consume $identify in the stats consumer — extract properties.$anon_id, resolve distinct_id (user id) to the username, and feed the pair through the existing updateIdentityMap path. Healing is retroactive by construction: identity is resolved at read time over the grant ledger, so the ghost's history folds into the user the moment the mapping lands, no grants rewritten. Caveat: the anon id is an IP+UA hash and churns with IPs, so only the most recent hash binds — the fix narrows the gap, it cannot close it retroactively for old hashes.
Explicitly NOT blocking the scoring read-flip cutover (keeb, 2026-07-04) — waived for scoring purposes; this is a funnel/analytics correctness bug. Context: scoring/scoring.md § "Course correction — reverse the shadow", prerequisite 2.
Shipped
No activity in this phase yet.
Sign in to post a ripple.