docs/design/2026-07-18-observed-channel-group-names.md
The workspace-scoped observed-contact graph introduced by #7109 preserves complete platform group IDs, but every groups[].label currently falls back to that ID. Some inbound channel callbacks already carry a human-readable group name, and the adapters discard it before the shared observation boundary.
Users selecting a proactive-delivery target need the readable name alongside the complete, stable platform ID. The name is observational metadata, not a routing key.
Add an optional group name to the shared inbound envelope and populate it only from metadata already present in an accepted inbound message.
conversationTitle.title for groups and supergroups.chat_id fallback because im.message.receive_v1 does not include a chat display name.This change does not call a platform directory, group-detail, or chat-info API; add permissions; alter routing or session identity; discover authoritative membership; observe bot output; or add topic names.
Envelope gains one optional field:
chatName?: string;
The field describes the display name of chatId as observed on that message. It is ignored for direct messages. chatId remains the complete platform delivery key and continues to determine sessions, deduplication, and graph identity.
The common observation path uses a sanitized, non-empty chatName as the group label. Missing or unusable values fall back to the complete chatId. The existing registry store bounds persisted labels to 256 UTF-16 code units without splitting surrogate pairs.
An accepted later message for the same channel, user, and group refreshes the observation. If it carries a different usable chatName, the existing store replacement semantics update the derived group label without creating another group node. Freshness remains lastObservedAt; names are not treated as permanent or authoritative.
A platform that omits a group name on a later message contributes the ID fallback for that observation. Graph derivation already selects the most recent observation, so the returned label represents the newest accepted evidence rather than a hidden long-lived name cache.
conversationTitle in the inbound callback: DingTalk Stream protocol.Message.chat as a Chat, whose title is available for group chats and supergroups: Telegram Bot API — Chat.chat_id, chat_type, and thread_id, but no chat display name: Feishu Open Platform — Receive message.chatName, and later observations can refresh labels.conversationTitle enters the envelope without changing callback handling.group.label.