Skip to content

fix(lark): retain owner messages through identity outages - #6888

Merged
huangruiteng merged 1 commit into
mainfrom
codex/bot-durable-source-capture-20261019
Oct 19, 2026
Merged

huangruiteng merged 1 commit into
mainfrom
codex/bot-durable-source-capture-20261019

Conversation

@loopx-agent

Copy link
Copy Markdown
Collaborator

An identity outage could drop a locally bound owner's private message before a durable capture existed. A separate refusal path then recorded a verified transport notice as a new, empty Core execution journal, which could block subsequent admission and Steward context reads. This fixes both parts of the same receive/recovery journey.

Goal and delivered outcome

  • Capture the enabled, locally bound owner's private event before fallible identity IO. This remains a private candidate: recovery must freshly verify the App, owner, original source and Core grant before execution.
  • Deliver transport-only rejection notices without creating Core requests. Their verified terminal state remains idempotent on provider redelivery.
  • Require the existing request identity before recording Core delivery, and scope routing/commission reads before examining unrelated legacy delivery-only records.
  • Reuse the existing capture fence, scanner, Session queue and delivery receipts. No history replay, additional consumer, new permission or state vocabulary is introduced. Group and other-sender capture boundaries remain in force.

Author declaration and implementation basis

Written by: model_agent — OpenAI Codex, GPT-6 family.

No separate written specification; the reproduced defects and request in this PR are the implementation basis.

Criterion Disposition Implementation Validation
Retain an owner's event across an identity outage and restart implemented LarkPrivateConversations.admit and existing capture recovery test_owner_event_survives_identity_outage_and_recovers_once
Recovery never inherits revoked, replaced or foreign source authority implemented Existing binding/source/Core checks remain mandatory Negative recovery and group/other-owner tests
A delivered refusal cannot create execution or become a later execution implemented Transport rejection closeout; record_delivery identity guard test_rejected_source_returns_notice_without_creating_core_execution
A legacy delivery-only receipt cannot block unrelated authorized work implemented Source-scoped routing and commission lookup test_legacy_delivery_only_receipt_does_not_block_steward_conversation

Scope and continuation

Complete within this receive/recovery slice. Existing provider authentication, upstream model availability and final business acceptance remain separate operational conditions. This proposal does not claim those external services are always available or that every queued business request has completed. It changes the existing Lark transport and native journal IO; no configuration or frontend control is added.

Validation

Tested revision: 3108bafa0 (production modules in the installed test wheel match this checkout byte for byte).
Run state: finished. Inputs: synthetic, public_fixture.

Check Result Evidence and boundary
Focused regression suites passed 307 tests across private conversations, ingress, images, feedback, compatibility and native Steward journeys
Installed wheel journey passed 9 fault/recovery tests using installed production modules, native Core and persistent ChatSessionStore; provider/model are synthetic
Historical counterexample passed The original owner-event loss reproduces twice against main 2d94432ee; current code preserves capture and recovers exactly once
Static checks passed Changed-file Ruff; project-configured mypy; Python compilation; semantic advisory; diff hygiene
Packaged build passed Current Chat bundle rebuilt and wheel installed into a disposable environment. Initial wheel attempt correctly rejected a stale frontend bundle after main integration; rebuilt before retesting
Standard diff-selected premerge passed Four direct checks, Python compilation, ten selected canaries/smokes; no failing checks or manual holds
Public boundary passed Four changed files scanned; no private incident data or raw logs included
CI not_run Goal configuration disables CI waiting; no CI status was queried or waited for

The tests exercise identity outages, restart, duplicate delivery, source/owner changes, revocation, foreign/group inputs, refusal receipts and later Steward work. They use the actual native queue, typed bridge and disk store with a synthetic upstream host. They do not inject faults into live provider accounts or launch a paid validation conversation.

Frontend / visual evidence

UI impact: none. Existing message entrypoints and settings are retained; no visual composition, new capability or control is introduced. Before/after viewport evidence is not applicable to this transport/journal repair.

Boundary checklist

  • Single receive/recovery scope; explicit four-file diff.
  • No private state, prompts, credentials, local paths or raw incident evidence published.
  • No authority or protected-operation bypass; fresh source validation remains required.
  • No paired RFC changed; bilingual check passed.
  • Commit includes DCO sign-off.

Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com>

@loopx-agent loopx-agent left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewer: model_agent · gpt-6.1-sol · OpenAI · runtime_reported · reasoning_effort=max

Approval conclusion (author-owned PR; GitHub blocks formal self-approval)

当前 head 3108bafa0506830cf4a30347c4459ef0f7c2b0cd,相对基线 a72dcb21a2906fccf099b11959725b595aa826ed 的完整四文件改动,未发现阻塞性问题。以下是源码和实际执行的判断;正式安装、真实服务持续可用及整个消息积压清空仍须分别核验。

动机

受影响的是向已绑定的个人助手发送私聊的用户,以及随后继续工作的管家。身份服务短暂不可用时,旧实现可能在保存消息前退出;另一个拒绝通知路径又可能生成没有原请求身份的执行记录,阻塞后续消息。现在先保留可恢复的候选消息,并把通知送达与真实执行记录分开。用户能够在原授权恢复后继续原请求,已经终结的拒绝通知也不会因重复投递启动工作。本片不承诺上游模型、网络和账号始终可用,也不代替每项实际业务的完成验收。真实 provider 网络故障、上游长上下文路由以及正式服务采用仍需运行维护分别确认。

改动思路

保留完整接收语义需要同时修复保存次序和回执归属。只在身份探测失败后打印日志仍会丢消息;只让管家忽略缺字段,则继续制造无主执行记录。当前方案把候选保存放到已有每请求文件锁内,后续继续走原身份、来源和 Core 授权;拒绝通知只完成传输自己的回执,Core 只接受已经存在的原请求身份。没有增加另一套队列、消费者、授权判定或人工确认流程。

下面是该 head 的控制流比较,属于源码说明,执行证据在后文:

触发情况 原路径 当前路径
已绑定本人的私聊,身份 IO 暂不可用 尚未保存就退出 私有候选留存,尚无执行、反馈或权限
相同候选在恢复后被扫描 没有原候选可找 重验 App、本人、来源和 grant,使用原请求进入原队列
Core 受理前拒绝,拒绝通知已送达 写出只有 delivery 标志的 Core 文件 仅关闭传输通知,不创建执行;重投也不新受理
读取原生委托或路由选择 无关残缺条目可抛错 先检查当前资源/绑定范围,再读相关内容

机制归属合适:网络和文件观察仍属于 Lark/Python 接入层;身份、Session 和执行许可仍由既有 typed Core 决定。当前 PR 的边界是已配置私聊的接收与回执恢复,未把外部身份服务的可用性升格为默认执行权限。保留现有存储和 scanner 比新增拒绝状态协议或补录历史消费者更小,也更容易回滚。

具体改动

采用不可变基线的 App conversations RFC 作为现有约束,版本 a72dcb21a2906fccf099b11959725b595aa826ed。本次只判断已交付私聊 checkpoint 的相关条款,不把该 RFC 尚未完成的更广泛体验当作本补丁新增义务。

  • App-scoped owner/source identity:implemented。候选只为本地启用绑定的本人私聊保存;新增和既有负例验证撤销、更换本人、来源发送者变化、群消息和其他发送者都不能据此获得执行。
  • Durable feedback and final delivery:implemented。传输拒绝通知不构成 Core 受理;原生回执必须匹配现有 request_ref,重复拒绝通知不会创建 Session、Turn 或第二条回复。
  • Canonical scanner recovery:implemented。重建接入对象后由同一 scanner 读取原候选,完成一次原生执行并读回最终通知;再次投递不增加上游 turn/start 或 provider 写入。

关键代码讲解

  • LarkPrivateConversations.admit 先从本地绑定生成来源,再在原请求锁内保存满足本人私聊范围的候选。随后仍调用当前 bindings.resolve 和原来源读回;原有拒绝类型保留。群输入不走这条提前保存分支。已经送达且从未拥有执行关联的拒绝通知直接保留其终态。
  • reconcile_request 沿原捕获恢复、同 Session 队列和原消息投递。最终送达后,只在真实 Core 请求存在时登记 Core 回执;没有 Core 的路径只有明确的传输拒绝才能闭合,其余缺失关联仍保持失败可恢复。
  • ChatExternalConversations.record_delivery 在既有锁内核对 request_ref,避免读取不存在文件得到空对象后写出假的执行记录。已有 commission adoption 和 Session/Turn 关联检查继续生效。
  • _admit_prepared 的两处选择历史查询先匹配绑定,未声明任何绑定的旧回执不能参与路由或序号计算。
  • commission_evidence 先限定授权资源,再读取其绑定和来源。没有委托资源的旧通知不再阻塞整段管家上下文,也不会被当成委托完成。

两个测试文件覆盖完整因果序列。test_lark_private_conversations.py 验证身份中断、恢复、权限反例、拒绝通知及重复投递;test_native_steward_private.py 在保留旧 delivery-only 原字节的情况下,让后续管家模型协议路径及项目选择真正继续执行。测试替代 provider/model,但不替代实际磁盘 journal、原生 Session 队列或 typed bridge。

对主干的风险

主要反向风险是把“保存候选”误当成授权,以及传输拒绝在重投后重新执行。撤销绑定、本人变化和来源发送者变化三个恢复反例均保持零 Core/Session/外部写入;群和其他发送者也没有获得新的预捕获。重复投递反例恢复真实 Core admit 后再次提交原事件,仍没有执行记录、Session 或额外回复。这比只检查返回码能更直接证明拒绝终态。

旧回执兼容保留的是持久数据,未新增旧请求解析器。两个正常查询只会排除不属于当前绑定/授权资源的条目,不会制造它们的身份或改写旧文件。损坏但自称属于当前授权委托的数据仍会暴露错误,未用 blanket catch 把任意损坏宣称为无事发生。保留未验证内容是诊断边界,不是可执行来源。

在不可变 2d94432ee 上,同一新增用例实际复现了两次候选丢失、拒绝通知生成空 Core 文件,以及残缺记录阻塞后续受理。当前 head 的六个相关测试族共 307 passed;安装到可丢弃环境的 wheel 使用实际打包模块,九项故障恢复用例通过,源文件摘要与当前提交一致。另用同一个独立 fixture 比较基线和 wheel:绑定关闭且 provider 可用/不可用两种状态均拒绝,没有候选、Core、Session、模型或 provider 写入。启用绑定的恢复路径由上述九项用例覆盖;安装本身不激活绑定。

Ruff、项目配置的 mypy、当前 Chat 构建、wheel 安装、公开边界扫描以及 standard diff-selected premerge 均通过。后者包含四项直接检查、Python 编译和十项选中 canary/smoke。首次 wheel 构建因合入主干后前端 bundle 过旧而正确拒绝,重建后再安装验收,没有绕过来源检查。早期新增测试还暴露了旧序号查询的缺字段路径,已把修复覆盖到实际受理及选择;没有放宽该断言。CI 根据当前 Goal 的 wait_for_ci=false 未查询或等待,不把远端未观察状态写成成功。

语义与 CI 对齐

这是 reuse_existing:继续使用原 capture/request/delivery schema、原 Core 状态和每请求锁。局部结构判断代表已送达且从未有执行关联的传输通知,不使用正文、名称或关键词推断权限。实际行为变化是身份 IO 前可恢复保存本人候选,以及禁止通知凭空生成执行;PR 描述已经披露。没有修改机器调度、quota、提示词或任一 Goal 的验收开关,也没有把必要来源校验改成建议。

我的整体评价

APPROVE,justified_increment。 long_horizon 为 improved:连续消息、身份恢复和旧回执共存时,原队列能够继续,重复消息没有新增执行。user_experience 为 improved:合法的短暂故障恢复不增加用户重新发送或确认的轮次。该结论限于实际覆盖的接入和打包路径,不声称模型采用、业务效果或长时间在线 SLA 已验证。

生产增量集中在两个现有 owner,测试增量覆盖两种相互放大的故障;继续保留现有 journal 比另造可靠收件服务更合比例。同域简化已通过范围先行的查询和拒绝回执归位完成,无须增加兼容框架。真实 provider 网络故障、上游长上下文路由以及正式服务采用仍需运行维护分别确认;这些不应通过删除原失败、重放未知效果工作或扩大授权来掩盖。

English verdict: APPROVE — 3108bafa0506830cf4a30347c4459ef0f7c2b0cd. Identity outages retain locally bound owner candidates without granting execution; verified transport refusals cannot create Core requests or execute on redelivery. Existing source, owner and grant checks remain required. The historical loss and journal-poisoning cases fail on the old baseline; 307 current regressions, nine installed-wheel fault/recovery cases, paired disabled-binding cases, static checks and standard local premerge pass. Provider/model fixtures are synthetic; live service adoption and upstream availability remain separate. CI was not consulted.

@huangruiteng
huangruiteng merged commit 764b989 into main Oct 19, 2026
12 of 35 checks passed
@huangruiteng
huangruiteng deleted the codex/bot-durable-source-capture-20261019 branch October 19, 2026 14:12
@loopx-agent

Copy link
Copy Markdown
Collaborator Author

当前 head 3108bafa0506830cf4a30347c4459ef0f7c2b0cd 的集成补验:将目标主干 f149d8e24953007e839e0bb0c35eab4bc1dde019 与本提案在临时合并状态组合,树为 9d895ebcb13e22ae03f9f049c0dc74737da43cbb。原捕获/身份/来源/拒绝重投、旧回执后的管家对话、真实原生 commission 与 portfolio 路径的选中回归通过;原分支和 head 已原样恢复,无新协议或权限。

最新已读取主干 88df41c2261708ec587a50723e4b1ee5aad587de 相对上述运行基线仅改 review SLA 报告脚本,未改这些运行路径。完整四文件评审与本提案的307项回归、九项安装 wheel 故障恢复及关闭绑定对照仍保留;本次补验处理后来合入的 Inbox/Turn context 共同调用方变化。没有查询或等待 CI。

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