Skip to content

Make delivery ACK convergence durable and trustworthy #58

Description

@ULookup

Target Version

3.0-dev。复核基于 origin/3.0-dev 提交 a75fc8721b99e5c0b4421d0c58b5618bbe71ff31(PR #57)。PR #57 已消除按 message_id 查询 Message 表的读放大,但 ACK 的可信来源与失败收敛仍未闭环。

Evidence

  • push/source/push_server.h:384-440 校验 WebSocket 连接身份后,先调用 UnackedPush::ack(user_id, device_id, user_seq) 删除待重传项,再选择 Message Service 并异步上报。
  • 同一路径直接使用客户端 ACK 中的 conversation_id/seq_id 构造 UpdateReadAckReq;没有从服务端持久化的 unacked payload/ledger 恢复并校验可信会话序号。
  • 异步回调只检查 Controller::Failed(),没有检查 UpdateReadAckRsp.header.success;通道不可用、传输失败或业务失败时,已删除的 unacked 不会自动恢复。
  • message/source/message_server.h:447-463 已直接接收 seq_id,不再查询 Message 表;但仍先 require_member_,再执行 update_last_ack_seq,热路径仍是成员查询加条件更新。
  • common/dao/data_redis.hpp:1842-1855 的 ACK 删除使用同 slot Lua,Redis 内部删除是原子的,但它与 MySQL 水位线推进之间没有 durable handoff。
  • PR feat(cache): harden Redis resilience and multilevel caching #57 的 GitHub Actions build/service-artifacts 失败,Reliability 与端到端门禁被跳过,因此当前只能确认源码改造,不能确认故障场景已通过。

Problem or Goal

让送达 ACK 使用服务端可信的 conversation seq_id,并在 Redis unacked 删除与 MySQL last_ack_seq 推进之间建立可重试、幂等、可观察的收敛边界。PR #57 已完成“去 Message 表查询”,本 Issue 聚焦剩余的一致性与接口语义。

Scope

  • 从服务端已持久化的 unacked payload/ACK ledger 解析并校验 message_id/conversation_id/seq_id,不信任客户端自报的水位线。
  • Message Service 上报成功的定义同时包含 RPC 传输成功和 header.success=true
  • 仅在水位线推进成功后删除 unacked,或先持久化可恢复 ACK intent,保证任一崩溃点可重试。
  • 将成员有效性与 GREATEST(last_ack_seq, seq_id) 尽量合并为一次条件原子 UPDATE。
  • 明确送达 ACK、已读水位线与用户推送序号三个序号域;必要时将内部 RPC 更名为 UpdateDeliveryAck
  • 增加重复、乱序、伪造、Message Service 故障和 Push 崩溃测试及容量对比。

Non-goals

Acceptance Criteria

  • ACK 热路径不再按 message_id 查询 Message 表。
  • last_ack_seq 只使用从服务端持久状态恢复并校验的 conversation seq_id
  • 成员有效性和水位线推进合并为一次条件原子 UPDATE,或有等价的单事务证明。
  • Message Service 不可用、RPC 失败或 header.success=false 时,ACK intent/unacked 保持可重试。
  • 回调同时检查传输状态与业务响应,失败指标和最老 pending age 可观察。
  • 重复、乱序 ACK 不会回退水位线,重试不会产生重复副作用。
  • 伪造 message_id/conversation_id/seq_id 无法推进水位线。
  • 已读、送达与 user_seq 的契约测试相互独立。
  • 给出修改前后 ACK QPS、MySQL QPS、P95/P99 和故障恢复数据。

Test-first Plan

先扩展 tests/pkg/contracts 的 ACK 语义契约,并增加全栈 RL-ACK-01:在 ACK intent 已记录、Message RPC 前后分别停止 Push/Message,验证恢复后 last_ack_seq 最终推进且 unacked 最终删除。先运行:

cd tests && go test ./pkg/contracts/... -run TestReadAckUsesConversationSequenceWatermark -v -count=1

再运行目标 Reliability 用例。预期 RED 是客户端伪造 seq_id 可推进水位,或 Message Service 故障后 unacked 已删除且无可重试状态;最小 GREEN 是可信 payload/ledger 与 durable handoff。

Risk and Security

客户端可控 seq_id 属于完整性边界。修复必须绑定认证后的 user/device 与服务端持久 payload,防止跨会话推进;重试状态需要 TTL、容量上限和脱敏日志,不能形成无限 ACK backlog。

Architecture Impact

Yes。Push 与 Message 之间新增明确的 ACK durable handoff/ledger 边界,但 MySQL 仍是送达水位线真相源,Redis 仍负责短期重传状态。

Core-flow Impact

Yes。改变客户端 ACK 校验、unacked 删除时机、Message RPC 失败恢复和 last_ack_seq 推进流程。

Required Skill Updates

  • 更新 .agents/skills/chatnow-orienting/references/core-flows.md,区分 write-ahead delivery 与 durable delivery ACK。
  • 更新 .agents/skills/chatnow-orienting/references/technology-stack.md,记录 ACK 真相源、重试状态与指标。
  • 更新 .agents/skills/chatnow-testing/references/case-catalog.md,登记 RL-ACK-01、伪造/乱序/重复 ACK 用例。
  • 若 RPC 更名或契约变化,更新 repository map 与相关接口文档。

Metadata

Metadata

Assignees

No one assigned

    Labels

    Severity/S2bugSomething isn't workingtech-debt设计已规划但代码未对齐的实现差距

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions