mirror of
https://gh-proxy.org/https://github.com/klinsmaya/multica.git
synced 2026-11-10 10:26:46 +08:00
* fix(wecom): clear the previous bot's rows when an agent swaps bots Pointing an agent at a different bot reuses the installation row — UpsertChannelInstallation conflicts on (workspace_id, agent_id, channel_type) and only rewrites config — so every dependent row keyed on installation_id crossed over into a bot it did not belong to. They are not merely stale. A WeCom aibot userid is anonymized per (bot, user), which is the premise the whole binding flow rests on, so a carried-over channel_user_binding holds an id from a namespace the new bot does not share. Outbound.tryDeliverInbox finds that binding by (workspace, member, channel_type), resolves the sender by its reused installation_id — now the LIVE NEW bot — and addresses the OLD bot's userid over it. A p2p chat binding carries the same userid as its channel_chat_id, and a queued task delivery carries it again. ClearChannelInstallationBotScopedRows is the reuse-case counterpart to the cleanup ReclaimDeadChannelInstallationByAppID already does for the dead-owner case. It runs in the install transaction, before the upsert, so a swap that fails later leaves the old bot's rows intact rather than half-cleared. The installation row itself, its audit trail and the Chats stay: the row is the same connection under new credentials, an inbound-audit row still records something an operator can act on, and a Chat is the user's work product that outlives whichever bot carried it — deleting the binding detaches the route, the same shape a disconnect leaves behind. A same-bot secret rotation is deliberately NOT a swap; clearing there would silently unbind every member. Both directions are tested. Closes #6547 * fix(wecom): report what a bot swap threw away, and cover every swept table Review follow-ups on #6547, none of which change what the sweep deletes. ClearChannelInstallationBotScopedRows becomes :one and returns a count per swept table. A queued channel_task_delivery is a RUNNING task's answer: dropping it is right — its address is the old bot's userid, unreachable from the new connection either way — but processEvent then finds no row and returns nil, with no counter and no line of its own, so the install path is the only place that can say where the answer went. InstallationService gains a nil-safe logger the way channel_media_reconciler already has one, and logs the three counts. The comment now says why the sweep is not airtight. The slot lock is the NEW bot's; the OLD bot's socket stays live until the Supervisor tears it down, so a message arriving under it between the DELETE and the COMMIT re-inserts a binding that outlives the sweep. Short and self-healing — that binding is the old namespace, so the next delivery fails to address and the user re-binds — and closing it properly needs a second clear once the old connection is confirmed down, which this transaction cannot observe. The test seeded four of the seven bot-scoped tables, so three deletes were asserted by the query text alone. It now seeds channel_task_delivery, channel_outbound_message and channel_outbound_card_message too, and counts the card table by chat_session_id since it carries no installation_id. With the swap gate forced off every one of the seven reports surviving rows; with it on, both directions still pass. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_015xUxTgkvJSCUpBTJDWZTYF * fix(wecom): count the queued outbound sends a bot swap drops Review's two nits from the second round. The log covered three of the seven swept tables, and the argument for logging channel_task_delivery — a queued answer disappears, processEvent finds no row and returns nil with no counter and no line — is the same one for channel_outbound_message. It is likely zero for WeCom in practice, which is a reason to count it rather than to argue about it: the query returns a fourth count and the swap line reports it. The race-window comment said the stray binding is self-healing without saying what healing looks like from the other side. It is one message that gets no answer and no line explaining why, until the user re-binds — the same silence the counts above just fixed one table over. The counts had no test. They go nowhere but the log — Upsert returns an Installation, not a report — so the swap test now captures the line through the service's own logger and asserts a count per swept table. Dropping the new field from the log fails it. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wv2qRKBY6fCBnhVae7Mj58 --------- Co-authored-by: YeJing <[email protected]> Co-authored-by: Claude Opus 5 <[email protected]>