Repository navigation
fix(lark): fail closed on damaged inbox state - #7979
huangruiteng merged 1 commit into
Conversation
Path.is_file() treated existing non-file durable state as missing, reopening acknowledged events and emptying receipt ledgers. Read directly and only accept FileNotFoundError as absence so damaged authority cannot replay work. Signed-off-by: Duang777 <duangjl007@gmail.com>
loopx-agent
left a comment
There was a problem hiding this comment.
Reviewer: model_agent · gpt-6.1-sol · OpenAI · runtime_reported · reasoning_effort=max
精确评审 head:1afeb7b652583f9b36d9c999ee5b23fdbcefd2cc;不可变差异基线:2afe4b0a0d2a45b5f7a172f01bdc0542adcdec52。未发现当前改动的阻塞问题;独立结论为 APPROVE。
动机 / Motivation
读取飞书收件箱并处理、确认消息的操作者与执行 Agent。
消息已经确认后,若 processed.json 被目录替换,旧版会再次显示它待处理;修复后返回明确读取错误,恢复原状态文件后继续保持已确认。
本轮真实 CLI 对照验证了损坏时拒绝、修复后恢复,以及材料结算的幂等重放;正常缺失文件和禁用收件箱仍可正常读取。
本评审不发送飞书消息、不修改真实收件箱,不认证长期采用、存储迁移或合并资格。
改动思路 / Approach
仅缺失文件可以代表空状态,这是既有读取边界的不变量;保留通用 urgency 与 Lark provider 的既有所有权,无需新增日志或通用恢复框架。
当前切片覆盖三个已存在的文件读取点及其真实读取、确认、材料结算入口,不改变消息授权、锁和写入格式。
不修改会让一次目录损坏重新唤起已确认消息。仅修 Lark drain 会让 generic urgency 仍错误安排回复;给三处读取再包一个通用恢复器则增加无必要结构。直接读取并只捕获 FileNotFoundError,是这条既有 IO 边界内更小的修复。原 schema 校验、per-inbox lock、ledger-first 写入及 ACK 权限不变;错误时先恢复真实状态,再沿原操作继续。
具体改动 / Changes
接受依据:loopx/extensions/lark/docs/lark-event-inbox.md,spec_revision 2afe4b0a0d2a45b5f7a172f01bdc0542adcdec52。下列 criterion_id 是本次映射标签,不是新协议。同时检查 原 Lark authority contract:消息可见性不授予 Turn/回复权限,重复与重启恢复须幂等。
- INBOX-ACK:implemented。A message is acknowledged only after its effect is written back; repeated acknowledgement is idempotent.
- LEDGER-REPLAY:implemented。Receipt replay/conflict, ledger commit and processed ACK share one lock; interruption recovery repairs the missing ACK.
- DISABLED-READ:implemented。Disabled/no-inbox paths stay quiet and do not confer Lark reply authority.
关键代码讲解
_pending_events:Wrong-type path no longer treated as empty. 输入和关键分支:Missing/valid/directory/parent-file/malformed state. Only FileNotFoundError yields absence. 现有调用:event_inbox.py:502 and operator_inbox.py:272。_load_processed:Direct read propagates path errors. 输入和关键分支:Same state matrix with disabled inbox and read-only drain. No acknowledged message becomes pending due to wrong path type. 现有调用:event_inbox.py:477,681,759; inbox_reactions.py:181。_material_review_ledger:Only missing ledger initializes empty receipts. 输入和关键分支:Wrong ledger type and recovery, then replay. Do not invent an empty ledger over damaged state. 现有调用:event_inbox.py:872 before processed ACK。
完整差异3文件 +63/−5,生产2文件 +10/−5,原测试模块 +53。独立 inbox/material/urgency/reaction 模块89 PASS,包含两个新路径损坏回归、并发结算、同消息重放冲突、ledger已写但ACK未写及共享锁路径;Ruff、配置的17文件 mypy、diff advisory、完整 semantic 和3路径 public scan 通过。
另外用同一冻结 fixture 在真实 source CLI 和真实文件状态运行24个语义观察:基线18 PASS/6 FAIL,当前24 PASS。覆盖 missing/valid/directory/parent-not-directory/bad-JSON、禁用路径、恢复原ACK以及材料结算重放。17个完整观察相同,另一个只差真实 oldest_pending_age_seconds 的1秒;原始值保留,未删除错误、身份、状态或指令。被损坏的读取和 preview 没有改写已有 JSON;恢复状态后仍是两项材料待处理,重复材料结算返回 already_settled 且 write_performed=false。临时 extension 激活仅限合成 runtime,没有连接飞书。
对主干的风险 / Risks
最强反例是已确认的消息仍存在于 event files,而 processed/receipts 路径变成目录或父级文件;仅测试空目录或成功 ACK 会漏掉这个错误。六个基线反例均在当前拒绝。修复有意把这类旧“成功空状态”改成可见错误,而真正没有文件的初始化仍成功。错误本身不等于恢复;本轮实际恢复原状态并完成正常重放。路径权限/原生Windows尚未独立资格化,不从 POSIX 的目录错误推断所有 ACL。
标准 premerge:6项直接检查,2项 catalog 与8项 risk checks,没有阻断失败/人工 hold;其中一个 catalog 项保留 inherited maintainability advisory。原 ratchet FAIL 在基线/当前均输出相同393字节、空stderr,报错 render_canonical_todo_sections 的完整因果 source 未改;不是本 PR 引入的错误。严格质量回执 cqr_ef5ee6563e42e48d40bf 仍为 FAIL,没有借本次批准清除或豁免;合并/质量门保持独立。
一次初始pytest调用使用了不存在的文件名,没有运行测试;修正选择后才计89项。首轮 public scan 的默认路由/空临时registry调用失败保留,补全隔离registry后扫描通过。没有修改产品、降低断言或把调用失败重标为通过。
语义与 CI 对齐
复用 OperatorInboxSourceContract 和既有processed/ledger schema,不新增布尔状态、枚举、CLI或迁移。通用读取错误保持领域中性;Lark术语仅存在于provider。没有把强制拒绝称作建议,也没有扩大可见消息的执行/回复权。三个读取点以同一缺失规则修复,保留不同解析返回职责。
本轮未查询、轮询或等待远端 CI;仓库全量测试、原生Windows、安装态/实际模型采用与持续完整成本未验。评审不把未知CI视为缺证,也不把本地通过写成远端CI通过。
我的整体评价 / Verdict
APPROVE 当前精确头。long_horizon=improved、user_experience=improved 仅限这条已验证的失败/恢复边界;普通既有任务零额外确认轮次,错误可读且能沿原 owner 继续。仅缺失文件可以代表空状态,这是既有读取边界的不变量;保留通用 urgency 与 Lark provider 的既有所有权,无需新增日志或通用恢复框架。 当前切片覆盖三个已存在的文件读取点及其真实读取、确认、材料结算入口,不改变消息授权、锁和写入格式。
已检查 CLI/受管结果入口及完整差异,没有渲染页面、首屏、布局或控件改动,视觉评审不适用。 视觉未验证范围:none;本次没有渲染表面变化。
已应用的有界简化是删除重复/错误的分支并复用当前 owner,不需要新的通用框架或仪式性后续任务。回忆经验中“先验证可用结果与恢复”的建议在这里得到局部执行;它既不追加父路线门禁,也不证明 memory utility 或长程净收益。没有当前P0–P2 finding;保留上述质量FAIL和未验边界,本结论不授予合并、安装或外发权限。
English verdict: APPROVE the exact head. Damaged processed/receipt paths fail closed across the real inbox CLI and urgency reader; missing-state initialization, disabled paths, restoration and idempotent replay are preserved. Independent24-case comparison repairs six baseline failures. Strict quality FAIL remains a separate hold; live Lark and native Windows were not qualified.
Summary
Root cause
Path.is_file()returned false for both a missing path and an existing directory. The readers collapsed those two states into the same empty-state result, which reopened acknowledged messages and could reportreply_dueagain.Test plan
1afeb7b652583f9b36d9c999ee5b23fdbcefd2cc.Validation notes
Changed surfaces: control plane, Lark extension runtime, and Python tests. The premerge report retained one inherited maintainability advisory for
render_canonical_todo_sections; it does not mention or overlap the changed files. All eight selected risk-profile smokes passed. Merge authority remains with the maintainers.