feat(manager): reconcile uncertain delegated result delivery - #4372
Conversation
Signed-off-by: Lihua <1017343802@qq.com>
Signed-off-by: Lihua <1017343802@qq.com>
Signed-off-by: Lihua <1017343802@qq.com>
Signed-off-by: Lihua <1017343802@qq.com>
Signed-off-by: Lihua <1017343802@qq.com>
Signed-off-by: Lihua <1017343802@qq.com>
Signed-off-by: Lihua <1017343802@qq.com>
|
CI repair receipt for exact head
Requesting CI and owner review on this head; the private Lark canary remains with the maintainer as agreed. |
steven-kid
left a comment
There was a problem hiding this comment.
详细中文评审
结论:REQUEST_CHANGES。评审 head:f15b6eb4b77da7ee287839a115c0e5313e5e00fe;对照 base:6c1a4d2cc37280a1d652b4bf67afd9c7d69ce19e。
已复现一个需要修复的 P2 正确性问题:project_chat_return_deliveries 在按 session 过滤前截断全局前 2,000 个 route 文件,导致无关会话的记录数量改变当前会话的送达状态可见性。这不是可选的文案建议;它破坏了本 PR 承诺的统一送达读回。其他尚未完成的验证在下文明确列出,没有把 CI 通过当作独立验证。
动机
现有 worker 结果已经落盘,但 Lark 发送后立即读回失败时,旧 drain 会永久跳过 verification_required。用户无法确认原消息是否送达,直接重发又可能产生重复消息。此次改动保存 provider 返回的 locator,并让现有后台泵恢复核验,解决的是可观察的恢复缺口。
不修改代码会保留这个缺口;另建 outbox 或任务库会引入第二个状态 owner。把已知 locator 的核验接入现有 return pump,是合适且相对小的方案。未知 locator 的恢复、M2 生命周期迁移、session takeover 和私有 Lark canary 被明确排除,不能把当前测试解释为这些更广泛能力已经成立。
改动思路
主路径仍是 ReturnService → drain → LarkManagerReturnTransport → send_return/verify_return → inbox_reply。manager-context 保留结果、投递状态、锁和重试的所有权;Lark extension 解释 profile、chat、消息 locator 和 provider 读回;两个 effect-runtime 方法负责 attempt 形状和核验结果分类。Chat 通过派生投影展示同一份文件状态,没有另建一套投递数据库。
我对照了 base/head 的原有 return pump、共享 outbound/mention 校验,以及未改动的 operation result-card recovery。后者从 ChatActionStore 恢复 operation 卡片,涉及不同生命周期和更新效果,不能直接取代这里的委派文本回传;提取共用 mention/intent 逻辑有实际调用依据。
正向路径:发送得到 locator,先记录 attempt,再读回;后续 pump 使用保存的 locator 核验,成功后标记 delivered/reconciled_after_restart,不重新运行 worker。已有合成测试验证了 restart/store 重建后 send 次数仍为一。负向路径:provider 暂不可用时保留核验并退避;已确认 mismatch、缺失 locator 或权限撤销进入显式未核验状态。真实 Lark 网络行为仍需对应环境验证。
具体改动
关键代码讲解
roundtrip.py::drain增加 attempt recorder、verify 分支和终态处理;原有 source、session、完成回执、授权和 transcript 幂等检查仍在发送前执行。reply_status通过状态/错误白名单减少 provider 私有信息进入公共读回的机会。inbox_reply.py::verify_lark_inbox_reply重算 receipt/intent,检查 bot identity、群可访问性和 mention 身份,再对已知 message ID 执行 mget;复用 Markdown/plain-text 比较逻辑。_verified_mention_aliases从旧发送路径提取,长 Markdown 降级的 recorder 也继续向下传递。恢复不执行 messages-send/reply,但成功后仍会调用既有 reaction cleanup,因此“read-only”准确范围是消息送达恢复,不应扩大解释为所有 provider 效果均为零。manager_returns.py::_resolve_return由发送和核验共同使用,保留实时绑定、原请求 grant 和初始回复确认检查。LarkManagerReturnTransport在现有 service 组合点提供 send/verify,未授予新的 Goal/Todo 权限。collaboration/return_delivery.ts的 normalize/classify 两个函数,通过effect_runtime_handlers.ts注册到实际 Python 调用点;__init__.py与pyproject.toml保证新增 TS 子包进入打包范围。协议名称描述单次投递 attempt,未声称 peer 或长期协作权限。project_chat_return_deliveries由project_chat_session_snapshot和 Chat HTTP snapshot handler 调用,按确定性的 handoff message ID 关联状态。问题就在这个新增投影的全局截断位置。
前端方面,chat.ts、workspace model 与 dashboard message model 接收可选状态;dashboard 的轮询会更新已存在消息,ChannelTimeline 和 ManagerConversationTray 都使用新 ReturnDeliveryStatus;i18n/CSS 提供等待、核验、已送达和显式未核验显示。source-contract 测试和 chat-recovery 浏览器 fixture 增加状态变化检查。打包 HTML、asset-retention 和 CSS/JS 产物同步变化,README 记录新恢复行为;四个 Python/TS 测试文件覆盖 attempt、恢复、拒绝和降级路径。生成产物已核对关联关系,未独立重建验证全部 bundle 内容。
对主干的风险
[P2,需修复] 全局截断让已知送达状态静默消失。 位置:loopx/capabilities/manager_context/roundtrip.py:212(for path in paths[:2000])。这里先对全部 request 的 SHA 文件名排序/截断,才比较 session_id。只要目标 request 排在第 2,001 个之后,即使当前 session 只有一条回传,也不会获得 return_delivery。前端遇到缺失值返回空 badge;CLI/manager 的直接 reply_status 仍然有状态。
独立反例使用真实 ChatSessionStore、return 文件和 ChatRequestHandler._session_snapshot,没有 mock 投影函数:创建一个外部委派、报告结论、执行两次 pump,确认只发送一次;读取 snapshot;再添加 2,000 条排在目标前面的其他 session route,保持目标结果、消息和 delivery 文件不变,再读 snapshot。head 观察为 before=delivered, after=missing, canonical=delivered, same_message=true, sends=1。同一 harness 在 base 上观察到前后均没有新增字段,且仍只发送一次,说明新增字段本身是预期变化,而 head 内部随无关记录消失并非预期变化。
最小修复是从当前 session 的消息/request 关系查找 canonical 状态,或提供不会丢失当前会话结果的索引/完整分页;不能把全局有界视图当成完整状态。补充 delivered 和 verification_required 两种状态在无关记录超过上限时保持不变的回归测试。
验证结果:相关 Python 测试 92 passed,新 TS 合约测试 2 passed,git diff --check 通过。Dashboard aggregate source-contract 在 head 和 base 都停于既有 manager-projection phrase 断言,不能归因于本改动。尚未独立完成 packaged browser/build、wheel 安装、全分支 baseline 对照和真实 Lark canary。异常文本子串决定终态的方式也较脆弱;本轮未复现独立误分类,不另列为已证实 bug。
我的整体评价
恢复逻辑有明确价值,沿用了现有 owner,新增 TS 方法有真实调用,不应因为总行数或引入 TS 本身否定这个方案。不过公开状态投影的完整性是整个 PR 的交付承诺之一,不能在核心恢复测试通过后忽略它。当前反例已经证明:同一条 canonical 状态会因无关会话数量而消失,属于需要修复的 observable-semantic drift。
因此本轮请求修改。建议保持现有 recovery 设计,缩小并修正 Chat lookup,再补充超过截断上限的真实 snapshot 回归。未执行的 provider/browser/完整兼容性验证继续标为 unverified,不继承作者或 CI 的通过结论。此评审不授予合并权限;更新 head 后需重新核验改动与相关主干集成。
English verdict: REQUEST_CHANGES at exact head f15b6eb. The recovery design has a valid existing owner, but the new Chat projection truncates the global route list before session filtering. A real persistent-store/Chat snapshot-handler counterexample shows a delivered message losing its return_delivery field after 2,000 unrelated routes are added, while canonical status remains delivered. Resolve current-session messages without this completeness loss and add a regression. Independently validated: 92 Python tests, 2 TS tests, diff check, and the same baseline/head probe; the aggregate UI source assertion also fails on base. Live Lark, packaged browser/build and complete branch parity remain unverified. No merge authorization is given.
|
English verdict: REQUEST_CHANGES at exact head f15b6eb. The recovery design has a valid existing owner, but the new Chat projection truncates the global route list before session filtering. A real persistent-store/Chat snapshot-handler counterexample shows a delivered message losing its return_delivery field after 2,000 unrelated routes are added, while canonical status remains delivered. Resolve current-session messages without this completeness loss and add a regression. Independently validated: 92 Python tests, 2 TS tests, diff check, and the same baseline/head probe; the aggregate UI source assertion also fails on base. Live Lark, packaged browser/build and complete branch parity remain unverified. No merge authorization is given. |
Signed-off-by: Lihua <1017343802@qq.com>
|
Repair receipt for exact head Fix
The paginated Regression
ValidationRun locally with Node v22.18.0 (repo minimum; with the machine default v22.14.0 the TS effect runtime reports unsupported and the four provider-verification tests fail identically at reviewed head
Not verified here (unchanged holds from the review)Packaged browser/build, wheel install, full branch baseline parity, and real Lark canary remain unverified and are not inherited from CI. CI is running on the new head. This update does not claim merge authorization; requesting re-review of the two-file delta ( |
The PR base 6c1a4d2 predated three dashboard source commits and the matching packaged-asset refresh on main (fb4c17d). CI pull_request builds the merge of this branch with current main, so its clean build:chat output matched neither side's committed assets and the frontstage-pages build check failed. Merge origin/main, resolve loopx/web/chat to main's generation, and rebuild the packaged assets from the merged sources. The new generation (index-BKS7amWV.js, index-SAtwg4Wr.css) reproduces the exact CI clean-build bytes locally; the build is idempotent and the prior main generation stays retained for rolling upgrades. Signed-off-by: Lihua <1017343802@qq.com>
|
Repair receipt for exact head Root causeThe PR base Fix
Validation (Node v22.18.0; machine-default v22.14.0 still fails the TS effect runtime, same as before)
Pre-existing main failure (not introduced here)
The Files changed view now diffs directly against current main. Requesting re-review on the same two reviewed files plus the regenerated packaged assets. |
huangruiteng
left a comment
There was a problem hiding this comment.
动机
委派结果本身已经落盘,但当一个外部写入确实发生、而即时回读无法确认时,return pump 会把这个交付停在 verification_required,随后每一轮都跳过它:结论永远不会变成已送达,重启服务也不会重建原始消息,而直接重发又可能重复一条外部消息。受影响的是所有通过 manager 委派、并在原会话等待结论的 owner,以及读 transcript 的 operator;代价是结论长期「未送达」与手工重发带来的重复消息(且无法分辨是哪种)。最近的更小修法是把 provider 的 message locator 先写进既有状态文件,之后只做只读回读,本 PR 正是这么做;此外它把 provider-neutral 的 attempt 校验与验证分类交给 control_plane/collaboration 的 typed TS 契约(维护者 ownership map 的要求),Python 保留文件锁、持久化与 adapter 编排。非目标明确:不做 M2 事务迁移、不改 Todo/lease/Goal 权限、不做 session takeover、不做未知 locator 恢复,私有 Lark canary 仍留给维护者。
改动思路
入口链路是 Chat/Lark 服务内的 ReturnService 线程 → drain()(loopx/capabilities/manager_context/roundtrip.py:270)→ LarkManagerReturnTransport(send_with_attempt / verify,由 start_return_service 组装);权威状态是每阶段的 <phase>.delivery.json 加上持久化的 route 与 reply 文本,决策 owner 是 drain 的状态迁移与 typed classifier,provider 解释和只读回读留在 Lark adapter。发送路径只多做一件事:外部写入成功后立刻用 delivery_attempt_recorder 持久化 locator,然后再回读;核验路径只做 provider 读取(identity、membership、mention 解析、+messages-mget 内容比对),成功后写 delivered 并带 verification=reconciled_after_restart,provider 不可用则写回 verification_required 并带 retry_at 退避,performed-but-mismatch 则写终态 explicit_unverified 与有界原因码。
复用面是干净的:没有新增 outbox、任务库或第二套状态枚举,transcript 消息 id 继续沿用既有的 "handoff." + _hash([request_id, phase]),Lark 回读复用已有 readback 断言与 reaction cleanup,公开投影沿用 reply_status → project_chat_session_snapshot → dashboard 徽标。与「什么都不做」相比,旧行为把模糊写入永久搁置;与「只打印投影」相比,那样不会用新 envelope 重新裁决可恢复性。判断上的分歧不在设计,而在状态机是否完整:迁移路径与异常分类各自的终态必须显式,否则这个修复会把用户送回它本想消除的风险。
具体改动
关键代码讲解
drain(loopx/capabilities/manager_context/roundtrip.py:270):verification_required从跳过集合移除,进入只读核验分支;发送分支新增record_attempt回调(:416),把 locator 归一化后写盘,并对既有 attempt 做冲突检查。异常处理在:471先重读状态,再决定终态或退避。_delivery_attempt/_verification_decision(:43/:51):Python 侧的薄适配层,通过 effect-runtime 方法manager.return_delivery.normalize_attempt与manager.return_delivery.classify_verification调用 typed 契约。注意它抛出的是EffectRuntimeRejected(RuntimeError子类),不是ValueError。classifyManagerReturnVerification(loopx/control_plane/collaboration/return_delivery.ts:66):provider-neutral 分类器,verified && !performed直接拒绝;已核验→delivered,未执行核验→verification_required/provider_verification_unavailable,已执行但未通过→explicit_unverified且只暴露三个有界原因码,provider 散文不会进入状态。verify_lark_inbox_reply(loopx/extensions/lark/inbox_reply.py:720):provider 侧只读回读,先按 markdown/text 两次 dry-run 重新推导 receipt 并与持久化的provider_receipt/intent_digest比对(不一致即 intent conflict,不发任何 provider 调用),再做 identity、membership、mention 与内容比对;全程不调用 send。project_chat_return_deliveries(loopx/capabilities/manager_context/roundtrip.py:207):把交付状态投影到已有 transcript 行,仅转发reply_status的公开字段,message_ref/intent_digest/provider_receipt不进入 Chat 或前端。
其余改动为 dashboard 徽标(新增 return-delivery-status.tsx,接入 channel-timeline.tsx 与 personal-workspace-page.tsx,中英文案与 CSS 配色 token 齐全)、dashboard-page.tsx 的轮询回填(deliveryByMessage 让消息到达后仍能刷新状态)、浏览器 smoke 与 contract test、pyproject.toml 的包数据声明,以及重新构建的 loopx/web/chat 打包资产(打包产物中确实包含新文案)。
对主干的风险
本轮发现两个状态机缺陷与一个合并门禁问题,均为阻塞。第一,发布版本写下的 verification_required 记录(没有 locator 字段)不会像 README 与 PR 正文承诺的那样变成 explicit_unverified/provider_locator_unavailable:drain 在 :355 只捕获 ValueError,而 _delivery_attempt(None) 抛的是 EffectRuntimeRejected;异常落到外层处理时 current["attempt"] 为 None,于是写成 retry_pending 并退避,下一轮进入正常发送分支,把已经发出过的结论再发一次。我用独立 oracle 复现:三轮 pump 后 sends=1 且状态变成 delivered(重复外部消息)。第二,adapter 在核验期抛出的权限类异常靠散文子串分类:manager connection no longer authorized 命中 authorized 而终结为 explicit_unverified/return_authorization_unavailable,而同一个 _resolve_return 抛出的 context return authority revoked 不命中任何 token,于是不写状态、不加退避,verification_required 一直保留,每一轮 pump(约 3 秒)都会重新跑一遍完整 provider 回读;PR 正文承诺的「revoked authority 明确未核验」在这一支没有实现。第三,exact head 上 test-shard (3)/pytest/merge-gate 红:tests/control_plane/test_delivery_response.py 的 resume_at:2026-09-15T00:00:00Z 是时钟相关 fixture,CI 运行时该时刻已过去,期望值翻转为 canonical_todo_wait,而投影返回 history_supervision;main 已由 #4411 修成 2099-01-01,本地在 head 复现、在最新 main 通过,因此不是本 PR 引入,但分支 BEHIND、必需检查必须变绿才能合并。
正向证据同样明确:122 个相关 pytest(roundtrip、chat manager、两个 Lark reply 套件)通过,TS 契约测试与 effect-runtime handler 套件通过,恢复路径断言恰好一次发送与一次核验、公开投影不含任何 provider locator,损坏状态会被规范化为 explicit_unverified/delivery_state_unreadable,未知/超长 delivered_at 与未知 error 也被规范化。因此修复面很小:让 locator 缺失/不可归一化在进入通用重试分支之前就终结,并把 adapter 的核验期异常改成显式 typed 原因(或至少同时分类两种权限措辞、给未分类异常加退避),再加一条从既有 verification_required 记录出发、断言零发送的回归测试;同一次合并里把分支更新到最新 main 即可清除第三条。
我的整体评价
可观测语义我按 base a1c2516f4 与 head be8c25a6f 各跑了一次同一套夹具:base 上模糊写入停在 verification_required(7 passed),head 上有 locator 时能只读核验到 delivered + reconciled_after_restart 且只有一次发送(13 passed、聚焦 122 passed),这部分修复方向正确且验证扎实;但同一夹具下「无 locator 的历史记录」这一支出现真实漂移(重复发送),「权限被撤销」这一支则永久停留且无退避地轮询 provider,所以 observable_semantics 判为 unintended drift,不能给通过。代码体量(29 文件 +1457/-177,其中一半是测试、示例与重新构建的打包资产)与问题规模相称,typed 边界与既有 owner 的分工合理,本地与远端都没有引入第二套状态权威。本轮结论是 REQUEST_CHANGES:请补上上述两条状态机修复(含回归测试)并把分支更新到最新 main 后,在新 head 上重跑必需检查;届时按当前证据我可以给出通过结论。本轮不做合并动作。
English verdict: REQUEST_CHANGES at exact head be8c25a — the read-only recovery design, the typed control_plane/collaboration/return_delivery.ts boundary and the public-safe projection are sound and well tested (122 focused pytest passes, TS contract tests pass, exactly one send plus one verification on the recovery path, no provider locator in any public surface), but two state-machine defects block merge: a pre-existing verification_required record without a locator is converted to retry_pending and then resent (the except ValueError guard at roundtrip.py:355 can never fire because the effect runtime raises EffectRuntimeRejected/RuntimeError), and ValueError("context return authority revoked") from the same resolver matches no token in _verification_exception_error, so that delivery never terminalizes and re-runs the full provider readback every ~3s without backoff, contradicting the documented explicit_unverified outcome. Fix both with typed terminal/backoff handling plus regression tests seeding a legacy locator-less record, and update the branch to latest main so the clock-dependent resume_at fixture fixed by #4411 stops failing test-shard (3)/pytest/merge-gate.
…ification Resolve the packaged chat asset conflicts by rebuilding the chat bundle from the merged dashboard sources. Main's prior generation stays in the retention window so a rolling upgrade can still load the released assets. Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
…loop A return that cannot be re-verified could still leave the state machine, so an ambiguous delegated result either duplicated its external message or re-ran the provider readback until the process stopped. - A `verification_required` record written before locators were persisted has no trustworthy provider identity, but `_delivery_attempt(None)` raises the effect runtime's rejection, which is a `RuntimeError`, so the existing `except ValueError` never fired. The record fell through to the generic retry branch, became `retry_pending`, and the next pump sent the already published conclusion again. Locator-less and un-normalizable attempts now terminalize as `explicit_unverified`/`provider_locator_unavailable` before that branch. - Adapters raise the new typed `ReturnResolutionBlocked(reason, message)` so delivery state stops depending on provider prose: `context return authority revoked` matched no token, so a revoked return neither terminalized nor backed off and re-ran the full readback every ~3s. Prose classification is retained for text that still arrives that way, now including `authority`. - An unclassified readback failure keeps the locator and stays retryable, but records `attempts`/`retry_at` instead of re-running the provider on every pump. Backoff stays scoped to the verification read so the send-interruption recovery path still reconciles on the next pump. Regression coverage: a legacy locator-less record is never resent (send count 0, including after the backoff window); authority revocation terminalizes for the typed reason and both prose wordings and is never re-read; an unclassified readback failure backs off and then retries. The Lark adapter tests now assert the typed reason alongside the existing message expectations. Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
huangruiteng
left a comment
There was a problem hiding this comment.
复核:APPROVE(exact head 196e67174,对照 base 6bb413105)
我在作者 fork 上直接修复了上一轮请求的两处状态机缺陷并合入了最新 main。三个阻塞点现在都有先失败后通过的回归覆盖,不是"CI 绿了所以通过"。
修复内容与独立验证
- 无 locator 的历史记录会被重发(重复外部消息) —— 已修。根因确认:
_delivery_attempt(None)抛的是EffectRuntimeRejected(RuntimeError子类),所以except ValueError永远不会命中,记录落到通用重试分支变成retry_pending,下一轮把已经发出去的结论再发一次。我用变异校验复现了旧行为:撤掉修复后新测试稳定报assert 'retry_pending' == 'explicit_unverified'。现在 locator 缺失/不可归一化在进入重试分支之前就终结为explicit_unverified/provider_locator_unavailable,发信计数恒为 0(含退避窗口之后)。 context return authority revoked永不终结且无退避(约每 3 秒重跑一次完整 provider 回读) —— 已修。这条措辞不命中旧分类器的任何 token。现在 adapter 抛新的 typedReturnResolutionBlocked(reason, message),投递状态不再依赖 provider 散文;散文分类保留给仍以文本到达的路径,并补上authority。typed 原因与两种散文措辞在测试里参数化覆盖,且终结后不再重读。- 未分类回读失败 —— 保留 locator 与可重试性,但写入
attempts/retry_at,不再每轮重跑 provider。退避只作用于核验读取:发送后中断的 A8 恢复路径仍在下一轮对账(test_known_provider_locator_is_verified_after_restart_without_resend保持通过)。
main 合并:打包 chat 产物的三处冲突按仓库流程重建而非手工合并,main 的上一代保留在 retention 窗口内;重建幂等,且同一工具链能精确复现 main 已提交的产物(git status -- loopx/web/chat 为空)。
本轮证据
692 passed——tests/test_chat*.py、tests/test_manager_context*.py、tests/extensions/test_lark_*.py- 新增回归:legacy 无 locator 不重发;权限撤销(typed + 两种散文)终结且不重读;未分类失败退避后再试
ruff干净;git diff --check干净npm run smoke:personal-workspace-packaged通过(navigation-sorting/chat-recovery/typed-actions),产物内含中英双语新文案loopx canary premerge --from-git-diff:direct/catalog/risk-profile/public-boundary 全部通过,无 manual hold
评审镜面
- typed 状态规则:投递终态现在是显式原因码 + typed 异常,prose 只作兼容兜底,符合"状态分类属于 typed enum"的要求;兼容兜底已注明它必须由 typed 原因取代。
- domain 中性:原因码与状态名不含产品/benchmark 专属词汇。
- 行为变更披露:新增
explicit_unverified终态、退避语义与 adapter 异常类型变化已在描述中列出;token=ValueError兼容性保留,未改变既有调用方的处理。 - guidance vs 义务:未把机器强制的终结语义写成"建议"。
- 默认关闭与权限语义:未新增能力开关;
ReturnResolutionBlocked不授予任何目标/权限,仅表达"不可解析"。 - 残留风险:
_verification_exception_error仍保留子串分类作为兼容路径(typed 原因优先)。风险是第三方 adapter 继续用新措辞时会退回未分类→退避,而不再是静默热循环;已由退避兜底,不阻塞合并。
仍未验证(不计入通过)
私有 Lark canary / 真实 provider 读回(维护者保留的凭证步骤)、M2 事务迁移、Todo/lease/Goal 权限、session takeover、未知 locator 恢复、provider 晋级。
English verdict
APPROVE at exact head 196e67174. The two state-machine defects from my previous review are fixed on the author's fork and covered by regression tests that fail before the fix (EffectRuntimeRejected is a RuntimeError, so the old except ValueError could never fire and a locator-less verification_required record was resent; context return authority revoked matched no token and re-ran the full readback every ~3s with no backoff). Locator-less attempts now terminalize as explicit_unverified/provider_locator_unavailable before the retry branch, adapters raise a typed ReturnResolutionBlocked, and unclassified readback failures back off without losing retryability — scoped to the verification read so the A8 send-interruption recovery still reconciles on the next pump. main was merged in (packaged assets rebuilt, not hand-merged) and re-verified: 692 focused pytest passes, packaged browser smoke, ruff, git diff --check and loopx canary premerge --from-git-diff all clean. Still unverified and out of scope: the credentialed private Lark canary, M2 transaction migration, Todo/lease/Goal authority, session takeover, unknown-locator recovery and provider promotion.
合并说明(admin bypass)在 exact head 此次使用 admin bypass 的唯一原因是评审门禁: 仍未验证并明确留在范围外:私有 Lark canary / 真实 provider 读回、M2 事务迁移、Todo/lease/Goal 权限、session takeover、未知 locator 恢复、provider 晋级。 Merged at exact head |
Problem and resulting behavior
A delegated worker result was already durable, but when Lark reported that an external write occurred and immediate readback could not verify it, the return pump parked the result in
verification_requiredforever. Restarting the service did not reconcile the original message, and safely retrying the send was impossible without risking a duplicate external effect.This PR records the provider's message locator before readback, then lets the existing return pump verify that exact prior message without sending again. A successful readback advances the original delivery to
delivered; provider outages stay retryable, while missing legacy locators, changed intent/audience, missing messages, revoked authority and verified mismatches remain explicitly unverified.Ownership and compatibility
manager-contextremains the result and delivery-state owner; no second outbox, task database or capability is added.verification_requiredrecord without a trustworthy locator becomesexplicit_unverifiedinstead of being guessed or resent.The related future-facing pass keeps provider-neutral state transitions in
manager-context, centralizes Lark intent and mention readback rules, and moves Chat snapshot projection out of the already-hot server facade. No speculative provider framework was added.Following the maintainer's ownership-map feedback, provider-neutral attempt validation and verification classification now run through the typed
control_plane/collaboration/return_delivery.tsboundary. Python owns file locks, persistence and adapter orchestration; Lark keeps provider interpretation and readback.TypeScript migration economics
manager_context/roundtrip.pyregex/schema validation and provider-blocker classification → typed TS collaboration contract. The Python result/delivery writer remains until the broader M2 transaction migration is qualified.manager.return_delivery.normalize_attemptandmanager.return_delivery.classify_verification.roundtrip.pycontinues to own durable file transactions and retry timing. It may retire only when M2 migrates the complete result/return transaction with legacy parity, one writer and crash reconciliation; this PR does not claim that migration.User entry points
queued,verification_required,deliveredandexplicit_unverifiedfacts.RFC acceptance coverage
Validation
Base:
6bb41310517692c2266f7e4c38054e28c6a496e6Head:
196e67174ec7c2d3dd27ddbac370ea0901143bb1326 passed— alltests/test_chat*.py,tests/test_manager_context*.py, and the four affected Lark reply/return suites with Node 24.19.npm run buildpassed; packaged Chat assets were rebuilt.npm run smoke:personal-workspace-packagedpassed:navigation-sorting,chat-recovery,typed-actions.git diff --checkpassed.collaboration/{__init__.py,return_delivery.ts}, installed into an empty target, and successfully executed both new effect-runtime methods. This fixes the first CI head's Release Artifacts / installed-stage failure, which showed the new TS subpackage was absent from the wheel.loopx canary premerge --from-git-diffpassed all 18 selected direct/catalog/risk/public-boundary checks; no manual holds.The full TS suite reported 1,530 passing, 1 skipped and 7 failures on the first run. Five were nested Python runtime startup failures and passed when rerun with the required local runtime access. The remaining two legacy monitor codec assertions (
null !== 0) reproduce unchanged on the exact base; this PR does not alter either module.Two repository-wide checks were attempted and confirmed failing unchanged on the exact base: the Personal Workspace aggregate source-contract smoke stops at its existing manager-projection phrase assertion, and docs governance reports the existing missing Chinese mirror for
manager-runtime-profile-v0.md. The new return-delivery assertions execute before the unrelated aggregate failure; the built packaged browser journey is the behavior-level UI evidence for this PR.Private Lark canary was not run because the maintainer explicitly retained that credentialed step. Live provider readback therefore remains unverified; the recovery path is covered by the synthetic interruption, restart and readback fixtures above.
Maintainer follow-up at
196e67174The maintainer fixed the three blocking findings from review and merged the latest
maininto this branch.State-machine completeness (fixes the reviewed defects):
verification_requiredrecord written before locators were persisted has no trustworthy provider identity._delivery_attempt(None)raises the effect runtime's rejection, which is aRuntimeError, so the pre-existingexcept ValueErrornever fired: the record fell into the generic retry branch, becameretry_pending, and the next pump resent the already published conclusion. Locator-less and un-normalizable attempts now terminalize asexplicit_unverified/provider_locator_unavailablebefore that branch. Regression: send count stays0, including after the backoff window.ReturnResolutionBlocked(reason, message), so delivery state stops depending on provider prose.context return authority revokedmatched no token in the previous classifier, so a revoked return neither terminalized nor backed off and re-ran the full provider readback every ~3s. Prose classification is retained for text that still arrives that way and now also recognisesauthority.attempts/retry_atinstead of re-running the provider on every pump. Backoff is scoped to the verification read, so the send-interruption recovery path still reconciles on the next pump.Packaged assets:
main's prior generation stays in the retention window, and the bundle was rebuilt from the merged dashboard sources rather than hand-merged.Validation at this head:
692 passed—tests/test_chat*.py,tests/test_manager_context*.py,tests/extensions/test_lark_*.py.retry_pending; revoked authority never terminalized) and pass after it.ruffclean on every changed file;git diff --checkclean.npm run smoke:personal-workspace-packagedpassed:navigation-sorting,chat-recovery,typed-actions, with the built bundle containing the new delivery copy in both languages.main's committed packaged assets exactly.loopx canary premerge --from-git-diffpassed every selected direct, catalog, risk-profile and public-boundary check with no manual holds.Still out of scope and unverified: M2 transaction migration, Todo/lease/Goal authority, session takeover, unknown-locator recovery, provider promotion and the credentialed private Lark canary.
Coordination: #4339. Ownership map: #4339 (comment)