Skip to content

fix(lark): verify provider-normalized Card 2.0 callbacks - #4375

Merged
huangruiteng merged 6 commits into
mainfrom
codex/fix-4370-card-readback-callback
Sep 14, 2026
Merged

huangruiteng merged 6 commits into
mainfrom
codex/fix-4370-card-readback-callback

Conversation

@huangruiteng

Copy link
Copy Markdown
Collaborator

Why / 动机

Lark Card 2.0 has multiple provider projections: the submitted JSON, normalized message readback, and callback user_card_content. Treating them as byte-identical made a valid button click fail after delivery, leaving the original card without a result receipt.

飞书 Card 2.0 在发送、消息读回和回调 user_card_content 中具有不同的提供方投影。把这些投影按字节完全相等校验,会让有效点击在送达后被拒绝,原卡也无法更新结果回执。

Closes #4370.

What / 变更

  • Strictly compare the complete visible Card 2.0 semantics across provider-normalized projections.

  • Keep the immutable submitted-card digest and the existing exact message, route, app, action-digest, membership, and authorized-principal checks.

  • Retain a bounded legacy title/placeholder fallback after those independent bindings pass.

  • Rebuild the immutable confirmation projection for idempotent callback replay without redispatching the operation.

  • Expose a stable public-safe callback failure code for diagnosis.

  • 对平台归一化后的 Card 2.0 做完整可见语义等价校验。

  • 保留不可变发送卡 digest,以及消息、群、机器人、动作 digest、成员身份和授权主体的精确校验。

  • 在上述独立绑定均通过后,兼容受限的旧版标题/占位符读回。

  • 回放时重建不可变确认投影,保证幂等且不重复执行。

  • 暴露稳定、可公开的回调失败码,避免让用户盲目重复点击。

Entry points / 入口

  • Lark: callback verification, original-card result update, and failure diagnosis changed.
  • CLI/managed Turn: no command or schema change; they continue to consume the same provider-neutral TypeScript operation review frame.
  • Frontend: no control change is needed because authority and lifecycle remain in the existing shared operation envelope; this patch only repairs the Lark provider adapter projection/readback.

Validation / 验证

  • 122 adjacent Lark tests passed.
  • 22 focused operation/collector tests passed, including provider-normalized callback replay with exactly one execution.
  • Ruff format/check passed.
  • Pre-merge canary: 12/12 selected checks passed before rebasing; focused and adjacent tests were rerun after rebasing onto current main.
  • Public/private boundary scan passed.

Live group-click qualification is intentionally still pending and will be recorded before merge; no real broker, signature, transfer, or order path is involved.

@huangruiteng

Copy link
Copy Markdown
Collaborator Author

@LIHUA919 可否在你当前 M3 known-locator 恢复切片之外,独立 review 一下本 PR 当前 head 8742e4c8a2a720ee295c5fd734e940addec6f90b?不要求你接管实现或合并。

请重点检查:

  • Card 2.0 provider-normalized readback 是否仍绑定原消息、App、chat、action digest 和不可变 submitted-card snapshot;
  • 空 card content 的受限 hydration、重复/并发 callback 和重启恢复是否可能重复执行 operation;
  • 结果恢复是否只补投递、不重跑 domain executor;
  • failure-stage / shape 诊断是否只保存 value-free 元数据;
  • effectful operation 权限边界是否没有被放宽。

当前作者侧证据:436 个 tests/extensions/test_lark*.py 通过;一次真实群中的 simulation-only 请求已基于已到达的 callback locator 受限恢复,最终 simulated_filled、external_write_performed=false,原消息 patch readback 成功。该恢复证明结果卡投递路径,但不冒充新 head 的全自动 live callback qualification。

这是 runtime/permission-sensitive PR,仓库政策要求独立 review;请把结论直接留在 PR,发现阻塞就 request changes。谢谢。

English: Please independently review the exact head above, focusing on immutable message/action binding, idempotent callback and restart behavior, delivery-only result recovery, value-free diagnostics, and unchanged effectful-operation authority. The 436 adjacent tests pass. A live simulation-only request was recovered and its original message patch was read back, but that is not being presented as a fresh automatic callback qualification. Please leave findings on the PR and do not merge it.

Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
@huangruiteng
huangruiteng force-pushed the codex/fix-4370-card-readback-callback branch from 35c6227 to a85dd22 Compare September 14, 2026 10:49

@huangruiteng huangruiteng 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.

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

结论 / Conclusion:APPROVE。我按 a85dd22cba3d4ceb24bab763147a5f759972ae1d 完成了 exact-head 自审;没有遗留阻塞项。结构化 review result 已通过 loopx pr-review --check-result 一致性校验。

动机 / Motivation

Card 2.0 的提交 JSON、消息读回和 card.action.trigger 并不是同一字节表示。旧实现把它们按完全相等校验,导致一张已送达、已被授权用户点击的卡片仍可在 claim 前被拒绝,且原卡没有结果反馈。历史诊断没有保存足够阶段信息,因此本审查不臆测旧 ValueError 的精确字段;可以确认的是:回调到达、操作未 claim、可见反馈缺失。

The change fixes a real provider-projection mismatch while preserving the existing operation authority boundary. Copy-only changes or dropping card verification would not solve both feedback reliability and action identity.

改动思路 / Design

复用现有三类 owner:Lark adapter 负责 provider projection;ChatActionStore 继续独占 operation lifecycle/claim;现有 Goal Channel delivery session 继续负责 Bot、chat、message 和 readback。新 delivery snapshot 只写一次,并须与既有 card_digest 相等。回调仍须同时满足 message/chat/app、action digest、授权 principal、tenant membership 和 lifecycle 检查,provider-normalized card 只是其中一个证据。

Result delivery recovery is deliberately M3/known-locator only: it patches and reads back the recorded message after an outcome already exists; it never reruns a model, worker, claim, executor, or venue write.

具体改动 / Concrete changes

  • message_card_matches 统一 Card 2.0 exact/visible projection 比较;带 callback 的历史卡片禁止仅凭可见文本去重,避免同文案、不同 operation 误绑旧 action。
  • record_operation_delivery_snapshot 在既有 delivery 下保存 immutable submitted card,并对 digest 与重复写入 fail closed;旧记录使用 canonical proposal 重建后仍校验原 digest。
  • callback handler 接受受限的 user-card-content / legacy placeholder 投影,但保留全部独立身份和权限检查;同一 exact event replay 复用 durable outcome。
  • result-card readback 使用同一 semantic verifier;失败后仅对 known message 做补送达与读回。
  • collector 保存稳定 failure code、stage 和不含原值的 event shape,并通过普通 inspect 投影;repair pattern 记录了中英文可复用诊断。

Key reviewed symbols: message_card_matches, record_operation_delivery_snapshot, _callback_card_content_matches, recover_goal_channel_operation_results, and handle_goal_channel_operation_callback.

对主干的风险 / Main-branch risk

自审中发现并已修复的最强反例是:两张可见文本相同、callback identity 不同的卡片可能复用旧 message。当前 exact head 对 actionable history 禁止 normalized-only match;若 provider 无法给出完整 action identity,安全退化是额外发送一张受 idempotency 保护的卡,而不是误绑旧 action。

The remaining provider risk is an undocumented future Card 2.0 shape. It fails closed and now exposes a stable code/stage/value-free shape. A fresh live click after installing the merged build remains operational qualification evidence, not an excuse to weaken pre-claim checks.

我的整体评价 / Overall assessment

范围与问题严重度相称:没有新增 daemon、queue、database、broker authority 或第二套 UI/state owner;新增状态是 existing delivery/status 下的 additive evidence。442 个 Lark/operation tests 全绿;premerge gate 的 4 个 direct checks 与 12 个 canary/smoke checks 全绿,零 warning/manual hold,self_merge_allowed=true。DCO-only rebase 前后 tree 完全一致,六个 outgoing commits 的 author、committer 与 Signed-off-by 均为已验证 noreply identity。

No blocking finding remains. Verdict: APPROVE.

@huangruiteng huangruiteng 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.

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

结论 / Conclusion:APPROVE。我按 a85dd22cba3d4ceb24bab763147a5f759972ae1d 完成了 exact-head 自审;没有遗留阻塞项。结构化 review result 已通过 loopx pr-review --check-result 一致性校验。

动机

Card 2.0 的提交 JSON、消息读回和 card.action.trigger 并不是同一字节表示。旧实现把它们按完全相等校验,导致一张已送达、已被授权用户点击的卡片仍可在 claim 前被拒绝,且原卡没有结果反馈。历史诊断没有保存足够阶段信息,因此本审查不臆测旧 ValueError 的精确字段;可以确认的是:回调到达、操作未 claim、可见反馈缺失。

The change fixes a real provider-projection mismatch while preserving the existing operation authority boundary. Copy-only changes or dropping card verification would not solve both feedback reliability and action identity.

改动思路

复用现有三类 owner:Lark adapter 负责 provider projection;ChatActionStore 继续独占 operation lifecycle/claim;现有 Goal Channel delivery session 继续负责 Bot、chat、message 和 readback。新 delivery snapshot 只写一次,并须与既有 card_digest 相等。回调仍须同时满足 message/chat/app、action digest、授权 principal、tenant membership 和 lifecycle 检查,provider-normalized card 只是其中一个证据。

Result delivery recovery is deliberately M3/known-locator only: it patches and reads back the recorded message after an outcome already exists; it never reruns a model, worker, claim, executor, or venue write.

具体改动

  • message_card_matches 统一 Card 2.0 exact/visible projection 比较;带 callback 的历史卡片禁止仅凭可见文本去重,避免同文案、不同 operation 误绑旧 action。
  • record_operation_delivery_snapshot 在既有 delivery 下保存 immutable submitted card,并对 digest 与重复写入 fail closed;旧记录使用 canonical proposal 重建后仍校验原 digest。
  • callback handler 接受受限的 user-card-content / legacy placeholder 投影,但保留全部独立身份和权限检查;同一 exact event replay 复用 durable outcome。
  • result-card readback 使用同一 semantic verifier;失败后仅对 known message 做补送达与读回。
  • collector 保存稳定 failure code、stage 和不含原值的 event shape,并通过普通 inspect 投影;repair pattern 记录了中英文可复用诊断。

Key reviewed symbols: message_card_matches, record_operation_delivery_snapshot, _callback_card_content_matches, recover_goal_channel_operation_results, and handle_goal_channel_operation_callback.

对主干的风险

自审中发现并已修复的最强反例是:两张可见文本相同、callback identity 不同的卡片可能复用旧 message。当前 exact head 对 actionable history 禁止 normalized-only match;若 provider 无法给出完整 action identity,安全退化是额外发送一张受 idempotency 保护的卡,而不是误绑旧 action。

The remaining provider risk is an undocumented future Card 2.0 shape. It fails closed and now exposes a stable code/stage/value-free shape. A fresh live click after installing the merged build remains operational qualification evidence, not an excuse to weaken pre-claim checks.

我的整体评价

范围与问题严重度相称:没有新增 daemon、queue、database、broker authority 或第二套 UI/state owner;新增状态是 existing delivery/status 下的 additive evidence。442 个 Lark/operation tests 全绿;premerge gate 的 4 个 direct checks 与 12 个 canary/smoke checks 全绿,零 warning/manual hold,self_merge_allowed=true。DCO-only rebase 前后 tree 完全一致,六个 outgoing commits 的 author、committer 与 Signed-off-by 均为已验证 noreply identity。

No blocking finding remains.

English verdict: APPROVE at exact head a85dd22.

@huangruiteng
huangruiteng merged commit 78b7d79 into main Sep 14, 2026
25 of 26 checks passed
@huangruiteng
huangruiteng deleted the codex/fix-4370-card-readback-callback branch September 14, 2026 11:10
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.

fix(lark): qualify card callback delivery and project truthful no-callback outcomes

1 participant