Skip to main content
← Back to list
01Issue
BugOpenSwamp Club
AssigneesNone

Decouple identity_map from main app: username renames via event, not shared DB

Opened by stack72 · 4/9/2026· GitHub #211

Context

identity_map is a MongoDB collection in the telemetry service that maps usernames to their device identifiers (distinct_ids). It's maintained by two bounded contexts:

  1. Telemetry service — StatsConsumer creates/updates mappings when authenticated events arrive
  2. Main appchooseUsername() in lib/auth.ts directly modifies identity_map during username renames (pull distinct_ids from old entry, addToSet to new entry, delete old)

This is shared-database integration. The main app knows the identity_map schema, knows the merge logic, and breaks if the telemetry service changes its storage format. Two contexts, one collection, no single owner.

After #102 lands, the main app no longer reads identity_map (scores come from queue snapshots keyed by username). But it still writes to it on renames. This issue eliminates that last cross-context write.

Current rename flow (lib/auth.tschooseUsername())

1. Update BetterAuth user record (username field)
2. Directly modify telemetry DB:
   a. identity_map: rename or merge old → new entry
   b. user_scores: re-key old → new document
3. Return

Step 2a is a bounded context violation — the main app reaches into telemetry's storage to fix up its internal index.

Step 2b goes away with #102 (user_scores is owned by the main app's UserScoreRepository, keyed by username — the main app re-keys its own data, which is fine).


Plan

1. Publish username_changed event

lib/auth.ts — after the BetterAuth username update succeeds, write a username_changed event to swamp_club_events (the same queue collection from #102, reverse direction: main app → telemetry service).

{
  type: "username_changed",
  oldUsername: string,
  newUsername: string,
  userId: string,
  createdAt: Date,
  status: "pending",
}

Remove the direct identity_map and user_metrics modifications from chooseUsername(). Keep the user_scores re-keying (that's the main app's own collection).

2. Consume username_changed in telemetry service

services/telemetry/lib/consumers/identity-migration.ts (new) — registered consumer that handles username_changed events:

  1. identity_map: rename entry or merge old → new (same logic currently in lib/auth.ts)
  2. username_metrics: re-key old → new document (if it exists)
  3. user_metrics: no change needed (keyed by distinct_id, not username)

The telemetry service owns the migration logic for its own collections. The main app just says "username changed" and moves on.

3. Event queue direction

#102 establishes swamp_club_events as telemetry → main app. This issue needs main app → telemetry. Two options:

Option A: Shared bidirectional queue. Add a direction or target field to events. Each consumer filters to its own events. Simple, one collection, but muddies the contract.

Option B: Separate telemetry_commands collection. Main app writes, telemetry service polls. Clean separation. The telemetry service already has a watcher loop — add a second poll source or a dedicated watcher for this low-volume collection.

Recommend Option B — username renames are rare (single-digit per day at most). A dedicated collection keeps the high-volume telemetry event queue uncontaminated by low-volume command events. The telemetry watcher can poll it on a longer interval (30s).

4. Remove direct telemetry DB access from main app

lib/auth.ts — delete the identity_map and username_metrics migration code from chooseUsername(). The function becomes:

  1. Update BetterAuth username
  2. Re-key user_scores (own collection)
  3. Publish username_changed event to telemetry_commands

No more importing the telemetry database connection for renames.

5. Update tests

tests/auth/choose_username_fn_test.ts — update to verify event publication instead of direct identity_map modification. The test should assert a username_changed document appears in telemetry_commands, not that identity_map was updated.

New test for the telemetry consumer:

  • username_changed event with existing mapping → entry re-keyed
  • username_changed with mappings on both old and new → merged
  • username_changed with no existing mapping → no-op

Files

Action File Part
MODIFY lib/auth.ts 1, 4
CREATE services/telemetry/lib/consumers/identity-migration.ts 2
MODIFY services/telemetry/main.ts 2
MODIFY tests/auth/choose_username_fn_test.ts 5
CREATE tests/telemetry/identity-migration_test.ts 5

Ordering

Deployable in one step — the consumer must exist before the old direct-write path is removed. If a phased rollout is preferred:

  1. Deploy telemetry consumer (processes events, no-ops if none exist)
  2. Deploy main app change (publishes events, stops direct writes)

Depends on

  • #102 (establishes queue infrastructure and watcher patterns)

Verification

  1. Rename a user → telemetry_commands gets a username_changed event
  2. Telemetry consumer processes it → identity_map updated correctly
  3. lib/auth.ts has zero imports from telemetry DB
  4. Leaderboard and stats still resolve renamed users correctly
  5. deno task check + deno task test pass
02Bog Flow
OPENTRIAGEDIN PROGRESSSHIPPED

Open

4/9/2026, 4:43:05 PM

No activity in this phase yet.

03Sludge Pulse

Sign in to post a ripple.