agent-ecosystem/docs/research/messenger-connectors-uncertainty-pass-27.md
2026-04-28 21:03:21 +03:00

345 lines
12 KiB
Markdown

# Messenger Connectors - Uncertainty Pass 27
Date: 2026-04-28
Scope: remaining low-confidence areas after topic capability design
Context source: previous architecture worktree doc at `/Users/belief/dev/projects/claude/_worktrees/claude_team_messenger_connectors/docs/messenger-connectors-architecture.md`
## Executive Delta
The highest risk is no longer "can Telegram topics work at all". The design now has proof and fallback paths.
The next real risk is identity and lifecycle:
```text
Telegram topic route -> team identity -> member identity -> message identity
```
Current app code is mostly keyed by `teamName`. That is workable for UI, but risky for messenger routes because external provider state can outlive local team folders.
## Source Facts Rechecked
Telegram official docs checked on 2026-04-28:
- `getUpdates` update ids are useful for ignoring repeated webhook or polling updates.
- Telegram stores incoming updates only until the bot receives them, and not longer than 24 hours.
- Webhooks retry on non-2xx responses.
- `User.has_topics_enabled` and `User.allows_users_to_create_topics` are returned only by `getMe`.
- Bot API 9.4 allowed bots to create topics in private chats and allowed bots to prevent users from creating/deleting topics through BotFather Mini App.
- `reply_to_message` is only for replies in the same chat and message thread.
- `external_reply` can come from another chat or forum topic and must not be used for teammate routing.
- MTProto send errors include `TOPIC_CLOSED` and `TOPIC_DELETED`; Bot API adapter should classify equivalent provider failures into typed sanitized errors.
Local code facts checked:
- `TeamConfig` has `name`, `description`, `color`, `members`, `projectPath`, `leadSessionId`, `deletedAt`, but no public stable `teamId`.
- `TeamChangeEvent` does not include delete, restore, permanent-delete or rename event types.
- `deleteTeam` soft-deletes by writing `deletedAt` into `config.json`.
- `restoreTeam` removes `deletedAt`.
- `permanentlyDeleteTeam` removes team and task dirs.
- Team backup has private `identityId` and writes `_backupIdentityId` into config as a backup guard, but this is not a product-level team identity.
- Many runtime paths use `teamName` as the runtime/team id.
Sources:
- https://core.telegram.org/bots/api
- https://core.telegram.org/bots/api-changelog
- https://core.telegram.org/method/messages.sendMessage
## 1. Team Identity Gap
Messenger routes must not be keyed only by `teamName`.
Danger scenario:
```text
1. User connects Telegram topic to teamName="frontend".
2. User permanently deletes the team.
3. User later creates a new unrelated team with the same teamName="frontend".
4. Old Telegram topic receives a message.
5. If route is keyed only by teamName, message can route to the new unrelated team.
```
This is worse than a normal UI cache bug because Telegram routes are external and long-lived.
Top 3 team identity options:
1. Add feature-owned `messengerTeamIdentityId` registry keyed by current `teamName` and backup marker if available - 🎯 8 🛡️ 8 🧠 5, approx 700-1400 LOC.
- Does not require changing global `TeamConfig` schema immediately.
- Gives messenger routes stable identity.
- Can reconcile with `_backupIdentityId` but does not depend on it.
2. Promote a stable `teamId` into `TeamConfig` globally - 🎯 7 🛡️ 9 🧠 8, approx 1800-4000 LOC.
- Best long-term domain model.
- Larger migration blast radius because many services assume `teamName`.
3. Keep `teamName` only and rely on tombstones - 🎯 5 🛡️ 6 🧠 3, approx 400-900 LOC.
- Fast.
- Still fragile when tombstones are pruned or route state is restored from backup.
Recommendation:
```text
Use option 1 for messenger MVP.
Design it so global TeamConfig.teamId can replace it later.
```
Suggested identity record:
```ts
type MessengerTeamIdentityRecord = {
messengerTeamIdentityId: string;
currentTeamName: string;
observedDisplayName: string;
backupIdentityId?: string;
firstSeenAt: string;
lastSeenAt: string;
state:
| "active"
| "soft_deleted"
| "restored_requires_reconnect"
| "permanently_deleted"
| "name_reused_different_identity";
};
```
Route binding should store both:
```text
teamNameSnapshot
messengerTeamIdentityId
routeGeneration
```
The runtime delivery adapter can still call existing services by `teamName`, but only after the identity registry confirms that the route still points to the current team folder.
## 2. Lifecycle Hooks Need Command-Side Events
File watcher events are not enough for messenger routes.
Why:
- Soft delete and restore are command intents, not just file changes.
- Permanent delete removes files before a watcher can read useful context.
- Connector cleanup must run before or during destructive operations.
- Renderer-only refresh events cannot protect background delivery.
Required main-process lifecycle port:
```ts
type MessengerTeamLifecyclePort = {
beforeSoftDeleteTeam(input: { teamName: string }): Promise<void>;
afterSoftDeleteTeam(input: { teamName: string; deletedAt: string }): Promise<void>;
beforeRestoreTeam(input: { teamName: string }): Promise<void>;
afterRestoreTeam(input: { teamName: string }): Promise<void>;
beforePermanentDeleteTeam(input: { teamName: string; deleteLocalConnectorPlaintext: boolean }): Promise<void>;
afterPermanentDeleteTeam(input: { teamName: string }): Promise<void>;
afterTeamConfigChanged(input: { teamName: string; previousDisplayName: string; nextDisplayName: string }): Promise<void>;
};
```
Top 3 integration points:
1. Call messenger facade directly from team IPC handlers around delete/restore/updateConfig - 🎯 8 🛡️ 9 🧠 6, approx 900-1800 LOC.
- Strong command ordering.
- Easy to test with mocked facade.
2. Emit richer domain events from `TeamDataService` and subscribe in messenger feature - 🎯 8 🛡️ 9 🧠 7, approx 1200-2500 LOC.
- Cleaner long-term.
- Wider refactor.
3. Infer lifecycle from file watcher and config scans - 🎯 5 🛡️ 6 🧠 4, approx 600-1200 LOC.
- Too late for permanent delete.
- Race-prone.
Recommendation:
```text
Use option 1 first.
Keep the facade shape compatible with option 2 later.
```
## 3. Member Identity Gap
Team members are also name-keyed.
Risk:
```text
1. Telegram bot sends a teammate message from "Alex".
2. User replies to that bot message later.
3. Meanwhile "Alex" was removed and a different member with same name was added.
4. Reply may route to the wrong teammate unless the message link stores member generation.
```
Minimum route target identity:
```ts
type MessengerRouteTarget =
| { kind: "lead"; teamIdentityId: string; leadSessionId?: string | null }
| {
kind: "teammate";
teamIdentityId: string;
memberNameSnapshot: string;
memberAgentIdSnapshot?: string;
memberRouteGeneration: number;
};
```
Top 3 member identity strategies:
1. Use `agentId` when present, otherwise member name plus `memberRouteGeneration` - 🎯 8 🛡️ 8 🧠 5, approx 700-1500 LOC.
- Fits current data.
- Avoids blocking MVP on member schema migration.
2. Add stable `memberId` to every member and migrate roster stores - 🎯 7 🛡️ 9 🧠 8, approx 1800-4000 LOC.
- Best long-term.
- Larger blast radius.
3. Use member display name only - 🎯 5 🛡️ 5 🧠 2, approx 200-600 LOC.
- Too weak for delayed Telegram replies.
Recommendation:
```text
Use option 1 in MVP.
Store target snapshots in every ProviderMessageLink.
```
## 4. ProviderMessageLink Must Be A Contract, Not Cache
The link is the most important durable object in the feature.
Recommended shape:
```ts
type ProviderMessageLink = {
linkId: string;
provider: "telegram";
accountBindingId: string;
routeId: string;
routeGeneration: number;
providerChatId: string;
providerThreadId: string | null;
providerMessageId: string;
internalMessageId: string;
internalMessageKind:
| "messenger_inbound"
| "lead_reply"
| "teammate_reply"
| "system_notice"
| "topic_probe";
origin:
| "provider_user"
| "team_lead"
| "team_teammate"
| "connector_system";
target: MessengerRouteTarget;
createdAt: string;
expiresAt?: string;
};
```
Rules:
- Never trim links only because UI messages were trimmed.
- Links for route targets should outlive `sentMessages.json`.
- Links for topic probes can have short TTL.
- Links from tombstoned routes should remain as tombstones long enough to block stale replies.
## 5. Reply Routing Should Be Two-Phase
Do not immediately turn a Telegram reply into a teammate message.
Phase 1 - resolve anchor:
```text
reply_to_message.message_id -> ProviderMessageLink
same chat id?
same thread id?
same account binding?
same route generation?
link target still valid?
```
Phase 2 - route message:
```text
valid teammate target -> teammate inbox
valid lead target -> lead
missing/stale target -> lead with context
tombstoned route -> reject with reconnect notice
unknown topic -> help flow
```
Critical rule:
```text
external_reply must never route to a teammate.
```
Bot API explicitly distinguishes same-thread `reply_to_message` from `external_reply`, so adapter normalization must preserve that distinction.
## 6. Privacy Risk Shift
After the no-plaintext-queue decision, the main privacy risk is not storage. It is accidental logging and diagnostic capture.
High-risk payloads:
```text
Telegram update JSON
callback_query data if it embeds route ids
Bot API error description if request URL/token leaks through HTTP client
message text in failed sends
team display names in topic titles
member names in projected message prefixes
```
Top 3 diagnostic strategies:
1. Feature-owned sanitized diagnostic DTOs plus tests - 🎯 9 🛡️ 9 🧠 5, approx 700-1500 LOC.
2. Generic logger wrapper only - 🎯 6 🛡️ 6 🧠 4, approx 400-900 LOC.
3. Rely on "do not log raw errors" convention - 🎯 3 🛡️ 3 🧠 1, 0 LOC.
Recommendation:
```text
Use option 1.
Also add Sentry beforeSend scrubbing as defense in depth.
```
## 7. Current Lowest-Confidence Map
1. Cross-client Telegram private topic UX - 🎯 5 🛡️ 8 🧠 6.
- Requires live probe.
- Design is resilient because of account-level confirmation and fallback.
2. Stable local team identity for external routes - 🎯 6 🛡️ 8 🧠 6.
- Current app is name-keyed.
- Needs a messenger-owned identity registry before route activation.
3. Member identity for delayed teammate replies - 🎯 6 🛡️ 8 🧠 6.
- Current member names can be reused.
- Store `agentId` and member generation snapshots.
4. Lifecycle ordering on permanent delete - 🎯 7 🛡️ 9 🧠 6.
- Policy is clear.
- Needs command-side hook, not watcher inference.
5. Outbound ambiguous Telegram sends - 🎯 7 🛡️ 9 🧠 6.
- Technical state is clear: `acceptance_unknown`.
- UX still needs concise wording.
6. Flat menu fallback correctness - 🎯 8 🛡️ 8 🧠 6.
- Good fallback.
- Needs strict selection lease tests to avoid wrong-team delivery.
## 8. Revised Next Slice
Before building UI, implement/test these core pieces:
1. Messenger identity registry and route generation policy - 🎯 9 🛡️ 9 🧠 6, approx 1000-2200 LOC.
2. ProviderMessageLink repository and reply route resolver - 🎯 9 🛡️ 9 🧠 6, approx 1200-2600 LOC.
3. Team lifecycle facade hooks around delete/restore/permanent delete/updateConfig - 🎯 8 🛡️ 9 🧠 6, approx 900-1800 LOC.
4. Telegram topic live probe fixtures - 🎯 9 🛡️ 9 🧠 5, approx 700-1500 LOC.
This is the point where the design becomes robust against the bugs most likely to happen months later, not only during the happy-path onboarding demo.