Repository navigation
fix(lark): qualify card callback delivery and project truthful no-callback outcomes #4370
Description
Problem / 问题
A post-merge, packaged Goal Channel qualification exposed a distinct gap from #4366: a Card 2.0 operation request was created and read back successfully, the local card-action consumer was running, and the Lark app had the published card.action.trigger subscription, but no callback event was produced for the operator's click. The operation stayed awaiting confirmation until expiry, with no claim, dispatch, simulation outcome, or result-card receipt.
合并并升级后的真实 Goal Channel 验收暴露了一个与 #4366 不同的问题:操作卡已成功创建并读回,本机卡片回调 consumer 正常运行,Lark 应用也已发布 card.action.trigger 订阅;但用户点击后,开放平台事件日志没有产生任何卡片回调。操作一直停留在 awaiting confirmation,最终过期;没有 claim、dispatch、模拟执行结果或结果卡回执。
This means listener-process health is necessary but not sufficient evidence that the interaction is qualified. The current surface can look healthy while the only user interaction path is silent.
这说明“监听进程健康”不能等价于“真实交互链路已验收”;当前状态会在唯一用户交互路径静默失效时仍显示健康。
Deployment evidence / 部署证据
- Qualified against packaged LoopX main revision 49f979b with paired CLI and macOS Desktop App.
- The original operation card had provider-native create/readback evidence.
- The callback listener was active and its event-bus counters showed zero received and zero dropped card-action events.
- The published Lark application configuration contained the card.action.trigger subscription using long connection.
- The provider event log for the interaction window contained the later IM text message but no card.action.trigger event.
- The operation expired while still awaiting confirmation; no domain consumer ran and no financial action occurred.
- The card uses a callback behavior plus a secondary confirm dialog. Evidence cannot distinguish between a client-side secondary-confirmation flow that was not finalized and a Lark client/platform delivery failure; it does prove the LoopX callback handler never received a finalized decision.
Required behavior / 验收要求
- Separate local listener readiness from live callback qualification. Status should expose a typed state such as listener_ready_unqualified until one real callback and canonical readback succeed for the exact app/route.
- Provide an explicit preflight/qualification command and receipt that checks published subscription, consumer identity, route binding, and one real callback round trip without granting domain execution authority.
- Make the two-step confirmation UX explicit, or remove redundant secondary confirmation when the canonical protected gate already provides the required confirmation boundary.
- When an awaiting-confirmation operation expires without a callback, project a truthful terminal expiry/timed-out result to the original card and the same Goal Channel. Do not execute, claim, rerun the worker/model, or blindly resend.
- Preserve known-locator idempotency and recovery boundaries. If the original provider message exists, repair only presentation/readback; unknown provider outcome remains fail-closed.
- Readback must use the exact configured bot profile and return typed invalid-argument details instead of a generic missing-receiver conclusion.
- Product acceptance must use a fresh simulation-only operation after the fix: desktop and mobile click, callback captured, authenticated decision, exactly-once simulation receipt, original-card result patch/readback, and the same canonical state in CLI/managed Turn and Desktop.
- Chinese and English operator copy must explain whether the click was received, whether execution started, and what the safe next action is.
Scope / 边界
This issue owns callback qualification, no-callback expiry projection, and interaction feedback. It does not grant live trading authority, does not add finance semantics to Core, and does not replace #4366 known-locator delivery recovery. A fresh operation must be deliberate; the expired card must never be replayed as authorization.
本 issue 只负责 callback 验收、无 callback 的过期投影和交互反馈;不授予实盘交易权限,不把 finance 语义放入 Core,也不替代 #4366 的 known-locator 投递恢复。任何新卡都必须是明确的新请求,已过期卡不得被重放为授权。
Activity
huangruiteng commentedon Sep 14, 2026
Progress / 进展:#4371 已合并并完成 packaged post-merge 安装验收(merge commit
6c1a4d2cc37280a1d652b4bf67afd9c7d69ce19e)。This slice fixes two concrete causes without replaying the expired operation:
Verification: 93 adjacent Lark tests, full GitHub CI, standard pre-merge canary, exact CLI/Desktop source-revision pairing, and the packaged collector now reads back
listener_ready_unqualifiedwith the callback consumer running without--quiet. An independent review found one JSON-diagnostic readiness false positive; commit0299ec987fixed it and the reviewer withdrew that blocker.仍未把 #4370 关闭:下一步需要经用户明确确认后发送一张全新的 simulation-only 卡,完成真实 callback → authenticated decision → exactly-once simulation receipt → original-card patch/readback 验收;旧卡不会重放。另一个独立切片仍需补 awaiting-confirmation 超时后的 canonical terminal expiry 投影和 known-locator 原卡修复。
huangruiteng commentedon Sep 14, 2026
Follow-up / 后续:真实 packaged 验收继续暴露了 listener-ready 之后的下一层兼容问题,并由 #4382 修复。
The current event consumer passes the upstream
create_timethrough verbatim. A live callback carried a 16-digit microsecond timestamp while the LoopX callback owner admitted the width but always interpreted it as milliseconds, so the operation failed atvalidate_timestampbefore confirmation, claim, simulation, or result delivery.#4382 keeps the existing callback and authority owners, accepts only explicit 13-digit millisecond and 16-digit microsecond shapes, rejects every other precision, and adds a full callback-to-simulation-to-same-card-update regression. The expired request is not replayed. Final acceptance remains one fresh simulation-only card after the merged runtime is installed.