Skip to content

team-foundation-followup: move per-platform user-identity extraction into adapter callback signatures #1784

@Wirasm

Description

@Wirasm

Context

In PR #1783 (user-identity foundation, PR-A of the team-foundation PRD at .claude/PRPs/prds/github-app-and-user-identity.prd.md) we plumbed user_id end-to-end. The implementation works correctly, but it surfaces an encapsulation inconsistency across the chat-platform adapters that's worth cleaning up before more team-features pile on top.

The inconsistency

Slack and Telegram adapters expose user identity via their onMessage callback arguments — the server-side handler receives normalized fields and never has to know the underlying platform's message shape:

// Slack — clean: the adapter pre-extracts userId + displayName
slackAdapter.onMessage(async event => {
  const userId = await resolveUserId('slack', event.user, event.displayName);
  
});

// Telegram — same pattern: the callback exposes pre-resolved fields
telegramAdapter.onMessage(async ({ conversationId, message, userId: telegramUserId, displayName }) => {
  const userId = await resolveUserId('telegram', telegramUserId, displayName);
  
});

Discord breaks the pattern — the server reaches into the raw discord.js Message object to pull author.id and author.username:

// Discord — the server has to know about message.author.{id,username}
discordAdapter.onMessage(async message => {
  const userId = await resolveUserId('discord', message.author.id, message.author.username);
  
});

This means server/src/index.ts carries platform-specific knowledge for Discord that it doesn't carry for Slack or Telegram. The forge adapters (GitHub, Gitea, GitLab) should be audited for the same drift — webhook payloads have a sender.login (GitHub) / user.username (GitLab) shape that's adapter-specific.

Why it matters

  • Inconsistent encapsulation — every new platform adapter has to re-decide where the user-extraction lives. The pattern should be one-direction: server consumes a normalized shape; adapter owns the conversion.
  • Easier to add new forges — Gitea, GitLab, Bitbucket, etc. each have a different payload structure. If the server already does the extraction inline, adding a new forge means another server/src/index.ts edit instead of a self-contained adapter.
  • Testing surface — adapter-internal extraction is easier to unit-test in isolation than scattered server-handler code.
  • Foundation for upcoming PRs — PR-B (GitHub App) and PR-C (per-user tokens, requires: gate) both add more per-platform branching in the same hot path. Tidying now keeps those PRs focused.

Suggested cleanup

  1. Audit every platform adapter (packages/adapters/src/chat/*, packages/adapters/src/forge/*, packages/adapters/src/community/*) for any user-identity extraction that currently happens server-side.
  2. Move each adapter's user-id + display-name extraction into the adapter itself.
  3. Expose normalized fields via the onMessage callback signature — at minimum { platformUserId: string, displayName?: string } — so server/src/index.ts calls resolveUserId(adapter.getPlatformType(), platformUserId, displayName) uniformly.
  4. Consider hoisting the per-platform resolveUserId calls into a shared adapter-bootstrap helper since they're now identical across platforms.

Out of scope

  • The shared resolveUserId(platform, platformUserId, displayName) helper itself — keep it where it is.
  • DB schema — no changes.
  • The actual user-creation transaction logic in db/users.ts — already adapter-agnostic.

Effort

~1 day. Mechanical refactor; covered by existing tests + the per-adapter test suite. Pure DRY/encapsulation cleanup, no behaviour change.

When to ship

Recommended after PR-A merges (#1783) and before PR-B opens so the GitHub adapter's auth-strategy swap lands on the cleaner pattern. Worst case, can wait until between PR-B and PR-C.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    architectureArchitectural changes and designarea: adaptersPlatform adapterschoreMaintenance (refactoring, CI, dependencies)

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions