Skip to content

fix(runtime): 统一原话题来源校验与异步副作用归属 - #1724

Open
ChenziSC wants to merge 42 commits into
deepcoldy:masterfrom
ChenziSC:codex/upstream-topic-source-consolidated-20261006
Open

ChenziSC wants to merge 42 commits into
deepcoldy:masterfrom
ChenziSC:codex/upstream-topic-source-consolidated-20261006

Conversation

@ChenziSC

@ChenziSC ChenziSC commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

会话关闭、切换或来源不一致时,消息入口、派发和延迟回复需要遵守同一个原话题边界。本改动在既有会话与发送路径核对来源,在执行副作用前重新检查,避免迟到回复、卡片更新或 workflow 恢复写入失效的话题。

统一覆盖消息/CLI 入口、daemon 派发与报告、Worker 回复目标与卡片归属、workflow 执行与恢复、新会话初始化。这些路径共用来源校验并存在实现依赖,因此本 PR 整体承接 #1666、#1667、#1668、#1669、#1670;不再分别合入这五个分支。接口保持平台中立,没有平台角色、业务阶段、私有账号或业务存储路径。

验证:固定提交 b3f7fb4,BOTMUX_NO_CLAIM=1 bun run build 通过;Node 24 运行 28 个相关测试文件,1777 passed、1 skipped、0 failed。53 个差异文件来自原依赖组的正常合并,保留其来源与回归,没有重写历史或修改主分支。验证使用隔离传输/执行夹具,实际客户端验收范围另行记录。

zhichen added 30 commits October 2, 2026 07:08
@deepcoldy

Copy link
Copy Markdown
Owner

感谢这个整合 PR!以下是自动评审的初步意见,最终以维护者审阅为准。

已在本地把最新 master(d65145eb9)与本 PR 头(b3f7fb40a)合并:src 零冲突,仅 2 个测试文件冲突(双方各加不同 seam,取并集即可);merge 树上 bun run build 通过,28 个改动测试文件在 Node 腿 1789 通过 / 1 跳过,相关主干邻居测试也通过。来源围栏的整体方向与覆盖面没有问题,但有 1 个默认策略下的回归,建议修改后再合。

P1:只含 feishu-reply 的 saved workflow 在默认 legacy 策略下被永久拦截

src/workflows/v3/botmux-host-policy.ts 的 chatBoundWorkflowWriteOptions 在策略判断之前硬校验了 chatId:

if (!appId || !chatId) throw new TopicSendError('TOPIC_SEND_CHECK_FAILED', '缺少已授权 workflow 的原会话依据。');

但 feishu-reply 节点的冻结身份按设计只有 larkAppId + rootMessageId:template-bindings.ts 对 feishu-reply 的 requiredIdentity、library-materialize.ts 的 resolveContext 都只解析这两个 ref,saved_definition 落盘的 context 允许是 sparse 的(binding 校验只校 context 里实际出现的键)。于是一个只含 feishu-reply 节点的 saved workflow:

  • 默认 legacy 策略下,首次 effect 的 beforeWrite 直接抛 CHECK_FAILED;
  • durable recovery 的 reconciler 用同一份 options,恢复提交时同样永久 blocked;
  • master 上没有这道闸门,属于默认行为回归(stop 策略反而不受这行影响:rootMessageId 存在时会继续走话题查询)。

现有测试的 sparse context 用例要么带了 chatId,要么在 stop 下断言 CHECK_FAILED,没有覆盖「legacy + 仅 rootMessageId」这一形态。建议按执行器身份要求分别校验:reply 路径要求 appId + rootMessageId,chatId 不作为前置必填;并补一个三态用例(legacy 可发送 / stop 且话题存活可发送 / stop 且已撤回 BLOCKED)。

P2(建议):跨主体独立任务在来源检查失败后缺少终态收口

prepareIndependentCrossPrincipalSession 三处 beforeWrite 抛出的 TopicSendError 会冒到 driver 的 catch,而那里只特判了队列满与 store busy。原话题被撤回(BLOCKED)时记录会一直停在 preparing_independent:无终态通知、无重试调度,重启后重新驱动仍以同样方式失败。建议 BLOCKED 走 settleCrossPrincipalTerminal 给发起/所有者终态通知,CHECK_FAILED 进入有界重试。

P3(可选)

  • cot-message.ts 的 assertCotResponse 在 legacy 下对所有非 0 业务码都抛错:中途的 append 业务错误会直接禁用本轮思考气泡;终态批次失败时 catch 里还可能以 reason: 'error' 收口一个本应 done 的气泡。如确认保留这个行为变化,建议补一条 legacy 非 0 码的测试明确登记。
  • session-group-birth.ts 的 catch 中,失败通知发出之后又 await 了一次 beforeWrite:写后检查撤不回已发消息,还会把已通知路径变成 reject,建议删掉那次后置检查。

以上均为本地合并最新主干后的核验结果,供参考。

@deepcoldy

Copy link
Copy Markdown
Owner

补充更正上面 P1 的影响面(经复审独立探针确认):

  1. stop 策略同样受影响,不止 legacy。if (!appId || !chatId) throw ... 在策略判断之前,是无条件检查:只含 feishu-reply 节点的 saved workflow 在 stop 下也会直接抛 CHECK_FAILED,且一个 GET 都不会发出。即 reply-only 节点在两种策略下当前都不可用。
  2. 修复时请注意:chatBoundWorkflowWriteOptions 产出的同一份 options 同时传给 createDefaultHostExecutorRegistry 和 createDefaultProviderReconcilers,工厂本身不知道具体 executor。建议放宽为「larkAppId 必填 + 按实际执行器所需身份校验」(reply 路径只要求 appId + rootMessageId,send/im 路径仍要求 chatId),或让工厂接收执行器身份;不要只删掉 chatId 检查而削弱 feishu-send 的目的地保障。

另:上条 P3 中「重复 items 严格判定无测试」一条撤回——test/lark-topic-write-guard.test.ts 的 malformed-observation 用例已覆盖重复 id(变异验证有牙),是我漏看了该测试文件。

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants