@agentunion/fastaun-browser 0.5.18 → 0.5.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +172 -79
- package/_packed_docs/CHANGELOG.md +172 -79
- package/_packed_docs/INDEX.md +244 -116
- package/_packed_docs/KITE_DOCS_GUIDE.md +98 -69
- package/_packed_docs/agent.md/SCHEMA.md +245 -205
- package/_packed_docs/agent.md//350/277/234/347/250/213agent.md/347/274/223/345/255/230/344/270/216etag/351/200/217/344/274/240/346/226/271/346/241/210.md +11 -11
- package/_packed_docs/aun/345/210/206/345/270/203/345/274/217/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md +42 -15
- package/_packed_docs/aun/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md +4 -4
- package/_packed_docs/protocol/01-/350/272/253/344/273/275/344/270/216/345/207/255/350/257/201/345/215/217/350/256/256-auth.md +45 -134
- package/_packed_docs/protocol/06-/346/234/215/345/212/241/345/215/217/350/256/256.md +1072 -1072
- package/_packed_docs/protocol/07-/351/224/231/350/257/257/347/240/201/344/270/216/347/212/266/346/200/201/346/234/272.md +5 -20
- package/_packed_docs/protocol/10-Group-/345/255/220/345/215/217/350/256/256.md +687 -668
- package/_packed_docs/protocol/11-Storage-/345/255/220/345/215/217/350/256/256.md +19 -28
- package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/211.md +443 -0
- package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2112.0.md +161 -0
- package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2112.1.md +658 -0
- package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2113.0.md +233 -0
- package/_packed_docs/protocol/agent.md/SCHEMA.md +4 -0
- package/_packed_docs/sdk/03-/346/240/270/345/277/203/346/246/202/345/277/265.md +1 -1
- package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +68 -61
- package/_packed_docs/sdk/05-E2EE/345/212/240/345/257/206/351/200/232/344/277/241.md +1 -1
- package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +279 -285
- package/_packed_docs/sdk/08-/346/234/200/344/275/263/345/256/236/350/267/265.md +3 -3
- package/_packed_docs/sdk/09-group-rpc-manual.md +271 -235
- package/_packed_docs/sdk/09-message-rpc-manual.md +47 -6
- package/_packed_docs/sdk/09-storage-rpc-manual.md +70 -68
- package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +63 -34
- package/_packed_docs/sdk/INDEX.md +105 -34
- package/_packed_docs/sdk/Ownership-SDK/345/256/236/347/216/260/346/214/207/345/215/227.md +169 -0
- package/_packed_docs/sdk/Ownership-SDK/347/274/226/346/216/222/346/214/207/345/215/227.md +349 -0
- package/_packed_docs/sdk/Ownership-SDK/347/274/226/346/216/222/346/214/207/345/215/2272.1.md +922 -0
- package/_packed_docs/sdk/Ownership-SDK/351/233/206/346/210/220/346/214/207/345/215/227.md +270 -0
- package/_packed_docs/sdk/Ownership2.1-SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +300 -0
- package/_packed_docs/sdk/Ownership2.1-SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +382 -0
- package/_packed_docs/sdk/Ownership2.1-TDD/345/210/206/351/230/266/346/256/265/345/256/236/346/226/275/350/256/241/345/210/222.md +708 -0
- package/_packed_docs/sdk/Ownership2.1/350/257/246/347/273/206/350/256/276/350/256/241.md +476 -0
- package/_packed_docs/sdk/Ownership/347/256/241/347/220/206/344/270/223/351/242/230.md +559 -0
- package/_packed_docs/sdk/ProtectedHeaders/347/224/250/346/263/225/344/270/223/351/242/230.md +299 -0
- package/_packed_docs/sdk//345/207/255/350/257/201/344/270/216/347/273/204/347/273/207/346/234/215/345/212/241SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +173 -0
- package/_packed_docs/sdk//345/207/255/350/257/201/344/270/216/347/273/204/347/273/207/346/234/215/345/212/241SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +211 -0
- package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273SDK/345/256/236/347/216/260/347/247/273/346/244/215/346/214/207/345/215/227.md +331 -0
- package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273SDK/351/233/206/346/210/220/346/214/207/345/215/227.md +207 -0
- package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273/344/273/243/347/240/201/345/256/241/346/237/245/346/212/245/345/221/212.md +252 -0
- package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273/345/256/236/346/226/275/350/256/241/345/210/222.md +156 -0
- package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273/346/226/271/346/241/210.md +273 -0
- package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273/350/257/246/347/273/206/350/256/276/350/256/241.md +211 -0
- package/dist/agent-md-schema.d.ts +17 -3
- package/dist/agent-md-schema.d.ts.map +1 -1
- package/dist/agent-md-schema.js +94 -26
- package/dist/agent-md-schema.js.map +1 -1
- package/dist/agent-md.d.ts +54 -3
- package/dist/agent-md.d.ts.map +1 -1
- package/dist/agent-md.js +711 -65
- package/dist/agent-md.js.map +1 -1
- package/dist/aid-store.d.ts +58 -46
- package/dist/aid-store.d.ts.map +1 -1
- package/dist/aid-store.js +254 -248
- package/dist/aid-store.js.map +1 -1
- package/dist/aid.d.ts +4 -1
- package/dist/aid.d.ts.map +1 -1
- package/dist/aid.js +27 -33
- package/dist/aid.js.map +1 -1
- package/dist/auth.d.ts +4 -1
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +426 -39
- package/dist/auth.js.map +1 -1
- package/dist/bundle.js +38179 -32054
- package/dist/cert-utils.d.ts +50 -2
- package/dist/cert-utils.d.ts.map +1 -1
- package/dist/cert-utils.js +105 -47
- package/dist/cert-utils.js.map +1 -1
- package/dist/client/delivery.d.ts +22 -1
- package/dist/client/delivery.d.ts.map +1 -1
- package/dist/client/delivery.js +389 -78
- package/dist/client/delivery.js.map +1 -1
- package/dist/client/rpc-pipeline.d.ts.map +1 -1
- package/dist/client/rpc-pipeline.js +103 -77
- package/dist/client/rpc-pipeline.js.map +1 -1
- package/dist/client/v2-e2ee.d.ts +1 -0
- package/dist/client/v2-e2ee.d.ts.map +1 -1
- package/dist/client/v2-e2ee.js +182 -89
- package/dist/client/v2-e2ee.js.map +1 -1
- package/dist/client.d.ts +100 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +1220 -25
- package/dist/client.js.map +1 -1
- package/dist/crypto.d.ts.map +1 -1
- package/dist/crypto.js +8 -6
- package/dist/crypto.js.map +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +3 -2
- package/dist/errors.js.map +1 -1
- package/dist/events.d.ts +9 -2
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +72 -13
- package/dist/events.js.map +1 -1
- package/dist/facades.d.ts +82 -2
- package/dist/facades.d.ts.map +1 -1
- package/dist/facades.js +100 -6
- package/dist/facades.js.map +1 -1
- package/dist/index.d.ts +7 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/keystore/index.d.ts +17 -0
- package/dist/keystore/index.d.ts.map +1 -1
- package/dist/keystore/indexeddb-shared.d.ts +10 -1
- package/dist/keystore/indexeddb-shared.d.ts.map +1 -1
- package/dist/keystore/indexeddb-shared.js +111 -0
- package/dist/keystore/indexeddb-shared.js.map +1 -1
- package/dist/keystore/indexeddb-token-store.d.ts +7 -1
- package/dist/keystore/indexeddb-token-store.d.ts.map +1 -1
- package/dist/keystore/indexeddb-token-store.js +30 -1
- package/dist/keystore/indexeddb-token-store.js.map +1 -1
- package/dist/org-protocol.d.ts +98 -0
- package/dist/org-protocol.d.ts.map +1 -0
- package/dist/org-protocol.js +1802 -0
- package/dist/org-protocol.js.map +1 -0
- package/dist/ownership-proof.d.ts +93 -5
- package/dist/ownership-proof.d.ts.map +1 -1
- package/dist/ownership-proof.js +239 -53
- package/dist/ownership-proof.js.map +1 -1
- package/dist/ownership-v21.d.ts +194 -0
- package/dist/ownership-v21.d.ts.map +1 -0
- package/dist/ownership-v21.js +678 -0
- package/dist/ownership-v21.js.map +1 -0
- package/dist/ownership-workflow.d.ts +151 -0
- package/dist/ownership-workflow.d.ts.map +1 -0
- package/dist/ownership-workflow.js +1229 -0
- package/dist/ownership-workflow.js.map +1 -0
- package/dist/seq-tracker.d.ts +1 -0
- package/dist/seq-tracker.d.ts.map +1 -1
- package/dist/seq-tracker.js +10 -2
- package/dist/seq-tracker.js.map +1 -1
- package/dist/tools/cross-sdk-agent.d.ts +83 -0
- package/dist/tools/cross-sdk-agent.d.ts.map +1 -1
- package/dist/tools/cross-sdk-agent.js +415 -7
- package/dist/tools/cross-sdk-agent.js.map +1 -1
- package/dist/transport.d.ts +7 -0
- package/dist/transport.d.ts.map +1 -1
- package/dist/transport.js +90 -2
- package/dist/transport.js.map +1 -1
- package/dist/v2/e2ee/decrypt.d.ts +1 -0
- package/dist/v2/e2ee/decrypt.d.ts.map +1 -1
- package/dist/v2/e2ee/decrypt.js +9 -1
- package/dist/v2/e2ee/decrypt.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# AUN 跨设备已读未读实施计划
|
|
2
|
+
|
|
3
|
+
> 对应设计:`docs/跨设备已读未读详细设计.md`
|
|
4
|
+
>
|
|
5
|
+
> 版本:v1.4
|
|
6
|
+
>
|
|
7
|
+
> 更新时间:2026-10-11
|
|
8
|
+
|
|
9
|
+
## 1. 交付边界
|
|
10
|
+
|
|
11
|
+
本计划只交付跨设备业务已读状态:Message/Group 服务端状态存储与 RPC、Gateway 事件转发、Python/TypeScript/JavaScript/Go SDK facade 与事件缓存,以及单域集成、E2E、cross-SDK、跨域测试。现有 Pull、History、投递 ACK、SeqTracker、E2EE payload 和对端已读回执不在本次改动范围。
|
|
12
|
+
|
|
13
|
+
## 2. 任务清单和依赖
|
|
14
|
+
|
|
15
|
+
| 编号 | 任务 | 依赖 | 完成标准 |
|
|
16
|
+
| --- | --- | --- | --- |
|
|
17
|
+
| P0-1 | 冻结四个 RPC 名称、字段、错误和事件名 | 无 | 设计文档与契约测试一致 |
|
|
18
|
+
| P0-2 | 为 Message/Group 编写状态与顺序单测 | P0-1 | 测试先失败,覆盖单调、幂等、批量、过滤 |
|
|
19
|
+
| P1-1 | Message 状态表迁移与 `MessageStore` repository | P0-2 | MySQL/SQL mock 迁移、写入和 seq 映射通过 |
|
|
20
|
+
| P1-2 | Group 状态表迁移与 repository | P0-2 | 成员权限、群内 seq、映射通过 |
|
|
21
|
+
| P1-4 | 状态缓存与异步批写 | P1-1,P1-2 | 有界 TTL 缓存命中、同键合并、多值批写、失败重试和关闭刷盘通过 |
|
|
22
|
+
| P1-3 | Message/Group RPC handler 和 Gateway method surface | P1-1,P1-2 | 短名/全名均可路由,旧方法回归通过 |
|
|
23
|
+
| P2-1 | 已读变更事件和同 AID 在线广播 | P1-3 | 只在汇总前进时广播,离线不阻断写入 |
|
|
24
|
+
| P2-2 | 四端 facade、类型、事件分发和本地缓存 | P1-3 | 四端 API 参数/响应/事件字段一致 |
|
|
25
|
+
| P2-3 | 重连查询补偿 | P2-2 | 保留空/全量/按群过滤范围,通过本地 read_state_sync 交付状态和错误 |
|
|
26
|
+
| P3-1 | 单域集成与 E2E 测试 | P2-1,P2-2 | 双设备 P2P/Group、定向消息、重连通过 |
|
|
27
|
+
| P3-2 | cross-SDK 测试矩阵 | P2-2 | Python/TS/JS/Go 交叉上报和查询通过 |
|
|
28
|
+
| P3-3 | 跨域测试 | P2-1,P3-1 | 双域 federation 状态与事件通过 |
|
|
29
|
+
| P4-1 | 文档索引、兼容审查和最终回归 | 全部 | 索引行号、测试报告、差异清单完成 |
|
|
30
|
+
|
|
31
|
+
## 3. TDD 执行顺序
|
|
32
|
+
|
|
33
|
+
### Phase 0:契约测试
|
|
34
|
+
|
|
35
|
+
1. 新增 Message/Group handler 契约测试,先断言方法 surface 包含 `read`、`read_state`。
|
|
36
|
+
2. 新增 service 层状态模型测试:顶层单条兼容、显式空/null 批次拒绝、scope/group/topic 校验、旧消息不回退、重复幂等、部分批量更新、同名 topic 跨 scope 隔离。
|
|
37
|
+
3. 新增映射测试:当前设备有消息返回本地 seq;公共位置已知但当前设备没有返回 `null/not_received`;未知或已清理位置返回 `null/unresolved`,且不触发汇总事件。
|
|
38
|
+
|
|
39
|
+
### Phase 1:服务端
|
|
40
|
+
|
|
41
|
+
1. 在 Message `MessageStore` 和 Group `EnsureGroupSchema` 中创建紧凑状态表:8 字节自增主键,`owner_aid`、`device_id`、`topic_key`、`last_read_message_id` 和 Group `group_id` 均限制为 128 字符;服务固定的 `scope` 不落库,并使用可覆盖过滤查询的唯一索引。
|
|
42
|
+
2. 为旧状态表和 Message `message_delivery_targets` 执行幂等迁移;迁移前检查全部字符串字段长度、scope 合并冲突和投递目标唯一键冲突,异常数据保留原表并明确失败。投递目标旧复合主键和 `idx_message_delivery_targets_target` 必须替换为 8 字节 `id` 主键及 `(message_id,target_aid)` 唯一索引。
|
|
43
|
+
3. 实现 repository:认证主体/设备维度写入、公共顺序比较、汇总查询和当前设备 seq 映射。
|
|
44
|
+
4. 在 RPC handler 中统一字符串类型及成员权限校验;同域先校验该域全部项,混合 Group 域逐域提交及幂等重试,失败不承诺跨域回滚。
|
|
45
|
+
5. 两类状态仓储使用本请求提交句柄,事务内核实同设备事实和共享汇总;查询同时保留数据库与 pending 候选,GC 映射回源,迁移中断后重试 order=0 回填;晚到 order=0 事实条件持久回填,Group ID 历史别名 canonical 化与跨实例权限版本同步;关闭数据库前完成积压刷写。
|
|
46
|
+
6. 更新 service method surface、Gateway attach methods 与 federation allowlist;运行服务端单测和 Go lint/build。
|
|
47
|
+
|
|
48
|
+
### Phase 2:事件与 SDK
|
|
49
|
+
|
|
50
|
+
1. 在数据库提交成功且状态汇总前进时发布 `message.read_changed`/`group.read_changed`,事件 payload 不带设备 seq。
|
|
51
|
+
2. 四端新增同名 facade 方法和类型;调用方法名只差语言命名风格,wire 字段完全一致。
|
|
52
|
+
3. 四端仅将查询和有效事件写入共享缓存,写入回执只登记输入过滤;旧事件不交付,同位置事件保留映射,AID/device 切换清理状态。
|
|
53
|
+
4. 先补充失败回归,再验证四个已读门面显式空值保留、scope 绑定原事件、必需 owner、公开生命周期 A→B→A 身份代际隔离、Gateway 来源和 owner 路由及本域/federation 回程无损整数、无歧义事件 ID、失败日志不泄露 topic,以及空/全量/按群查询补偿、read_state_sync、TS/JS 无损整数与 Unicode 排序、Go Connect context 取消后补偿。
|
|
54
|
+
|
|
55
|
+
### Phase 3:分层验收
|
|
56
|
+
|
|
57
|
+
1. 单域集成:设备 A/B 交错提交 P2P 话题,查询只见汇总;按 topic/group 过滤;旧值、重复、批量和定向边界。
|
|
58
|
+
2. E2E:真实 Gateway/Message/Group + 两个设备,在线事件、断线重连、事件丢失查询补偿、Group 成员权限。
|
|
59
|
+
3. cross-SDK:标准矩阵验证各端 wire;共享 AID 矩阵让 Python/TS/Go/JS 轮流写入同一 AID,再由其余 SDK 查询,Message/Group 各 12 个跨 SDK 组合全部通过。
|
|
60
|
+
4. 跨域:只使用本地 Docker 的 `agentid.pub` 与 `agentid.cn`,P2P 和 Group 消息通过 federation,验证状态写入、当前设备 seq 映射和 `message.read_changed`/`group.read_changed` 在线事件;离线设备依靠查询恢复。
|
|
61
|
+
|
|
62
|
+
## 4. 测试文件规划
|
|
63
|
+
|
|
64
|
+
| 层级 | 文件 |
|
|
65
|
+
| --- | --- |
|
|
66
|
+
| Message Go 单测 | `extensions/services/message/go/internal/store/read_state_test.go`、`extensions/services/message/go/cmd/message-go/startup_migration_test.go`;服务入口路由回归在 `main.go` 的现有测试集中 |
|
|
67
|
+
| Group Go 单测 | `extensions/services/group/go/internal/repository/read_state_test.go`、`extensions/services/group/go/internal/store/schema_test.go`;handler 和 Gateway surface 使用现有 `internal/rpc`、`internal/gatewayattach` 测试集 |
|
|
68
|
+
| Python SDK/单域 | `python/tests/unit/test_sdk_facades.py`、`python/tests/integration_test_read_state.py`、`python/tests/e2e_test_read_state_multi_device.py` |
|
|
69
|
+
| TypeScript SDK | `ts/tests/unit/client.test.ts` 中的跨设备已读状态用例 |
|
|
70
|
+
| JavaScript SDK | `js/tests/unit/client.test.ts` 中的跨设备已读状态用例 |
|
|
71
|
+
| Go SDK | `go/facades_test.go` 中的 read/read_state facade、事件缓存和 wire 参数用例 |
|
|
72
|
+
| cross-SDK | `tests/cross-sdk/cases.single-domain.json` 的标准矩阵、`tests/cross-sdk/cases.read-state-shared.json` 的共享 AID 矩阵,以及 `tests/cross-sdk/runner/run_cross_sdk_e2e.py` 和四端 agent |
|
|
73
|
+
| 跨域 | `D:\modelunion\kite\docker-deploy\tests\agentid_cross_domain_e2e.py --phase read-state`;只连接本机 Docker 的 `agentid.pub` 与 `agentid.cn` |
|
|
74
|
+
|
|
75
|
+
如果当前 Docker 或 cross-SDK runner 不具备新场景的服务启动条件,先完成服务端/SDK 合同测试,再在环境可用时按断点续跑原则执行分布式用例,并在最终报告中列出未执行原因。
|
|
76
|
+
|
|
77
|
+
## 5. 断点续跑与故障处理
|
|
78
|
+
|
|
79
|
+
- 测试按 P0→P1→P2→P3 顺序执行;同一层首次失败立即停止,修复后从失败用例继续。
|
|
80
|
+
- 已通过且代码未影响的用例不重复执行;服务端 schema 或 wire 变更影响的用例必须重跑。
|
|
81
|
+
- 服务流程问题先阅读完整调用链;只有定位不到根因时才提出临时 `DEBUG:` 日志方案,并在问题确认后清理。
|
|
82
|
+
- 不启动 `python main.py`;真实 Docker/Launcher 启动由测试环境脚本或用户手动完成。
|
|
83
|
+
- 日志写入既有测试日志目录,报告记录命令、起止时间、通过/失败和环境前提。
|
|
84
|
+
|
|
85
|
+
## 6. 兼容性审查门
|
|
86
|
+
|
|
87
|
+
1. 跨平台:只使用 JSON、SQL、WebSocket 现有抽象,不引入平台特有 API。
|
|
88
|
+
2. 跨语言:四端 wire 字段、空值、整数范围、事件名和错误语义逐项比对。
|
|
89
|
+
3. 跨域:方法 surface、federation allowlist、事件目标 AID 和离线补偿逐项验证。
|
|
90
|
+
4. 数据:新表使用现有 MySQL migration/lock 机制;状态写入不触碰 ACK、Pull 或消息正文。
|
|
91
|
+
5. 安全:所有状态读取和写入绑定 `_auth.aid`,Group 写入复用成员权限,不返回设备明细。
|
|
92
|
+
|
|
93
|
+
## 7. 历史验收记录(2026-10-09)
|
|
94
|
+
|
|
95
|
+
以下保留 2026-10-09 验收记录;2026-10-10 隔离回归也属于历史证据,均不能代替 2026-10-11 本轮验证。本轮实际隔离回归、静态检查和未执行项已记录于代码审查报告第 7.3 节;真实 MySQL 多实例、单域集成/E2E、cross-SDK 和双域须对更新后的镜像另行验证。
|
|
96
|
+
|
|
97
|
+
验收环境固定为本机 Docker,不连接公网或线上环境:服务镜像为 `docker-deploy-kite:latest`,构建摘要为 `sha256:be5b92aaadb81c847f0bbb4c78b73ad876cf3d19a9e1a256b17a0714db1274a`;跨域网络为 `kite-agentid-federation-net`,两端身份域为 `agentid.pub` 和 `agentid.cn`。
|
|
98
|
+
|
|
99
|
+
| 层级 | 实际命令或运行入口 | 结果 | 证据 |
|
|
100
|
+
| --- | --- | --- | --- |
|
|
101
|
+
| Message Go | 在 `extensions/services/message/go` 执行 `go test ./... -failfast`、`go vet ./...` | 通过 | `internal/store/read_state_test.go`、Go 全量测试输出 |
|
|
102
|
+
| Group Go | 在 `extensions/services/group/go` 执行 `go test ./... -failfast`、`go vet ./...` | 通过 | `internal/repository/read_state_test.go`、Go 全量测试输出 |
|
|
103
|
+
| Python SDK | `python -X utf8 -m pytest tests/unit/test_sdk_facades.py -q` | `21 passed` | 定向单测输出 |
|
|
104
|
+
| TypeScript SDK | 在 `ts` 执行 `npm exec -- vitest run tests/unit/client.test.ts --reporter=dot` | `192 passed` | `ts/tests/unit/client.test.ts` |
|
|
105
|
+
| JavaScript SDK | 在 `js` 执行 `npm exec -- vitest run --environment jsdom tests/unit/client.test.ts --pool forks --maxWorkers=1 --reporter=dot` | `137 passed` | `js/tests/unit/client.test.ts` |
|
|
106
|
+
| 单域集成 | `python/tests/integration_test_read_state.py --phase all` | P2P、Group、topic 原值、未知映射、128 Unicode 字符、无效批量隔离通过 | `D:\modelunion\kite\docker-deploy\data\sdk-tester-logs\read_state_review_integration_p2p.log`、`read_state_review_integration_group.log` |
|
|
107
|
+
| 单域 E2E | `python/tests/e2e_test_read_state_multi_device.py --phase all`;定向阶段 `--phase targeted` | P2P/Group 多设备、在线事件、重连补偿,定向 P2P 加密和非加密隔离通过 | `D:\modelunion\kite\docker-deploy\data\sdk-tester-logs\read_state_review_e2e_p2p.log`、`read_state_review_e2e_targeted_final.log` |
|
|
108
|
+
| cross-SDK 标准矩阵 | 在 `tests/cross-sdk` 设置 `CROSS_SDK_CASES_BASENAME=cases.single-domain.json`,执行 Docker Compose runner | `read_state_matrix_python_ts_go_js`、`group_read_state_matrix_python_ts_go_js` 通过 | `tests/cross-sdk/artifacts/read-state/results.json`、`D:\modelunion\kite\docker-deploy\data\sdk-tester-logs\cross_sdk_read_state.log` |
|
|
109
|
+
| cross-SDK 共享 AID 矩阵 | 在 `tests/cross-sdk` 设置 `CROSS_SDK_CASES_BASENAME=cases.read-state-shared.json`、`CROSS_SDK_READ_STATE_SHARED_AID=1`,执行同一 Docker Compose runner | `read_state_matrix_shared_aid`、`group_read_state_matrix_shared_aid` 通过;四端轮流写入并由其余端查询同一 AID | `tests/cross-sdk/artifacts/read-state-shared/results.json`、`D:\modelunion\kite\docker-deploy\data\sdk-tester-logs\read_state_review_cross_sdk_shared.log` |
|
|
110
|
+
| 本地双域 | 使用下方完整 Docker 命令,运行 `agentid_cross_domain_e2e.py --phase read-state --skip-reconnect --run-id agentid-read-state-review-final-20261009` | 双方向 P2P/Group、跨设备汇总、Message/Group federation 事件路由通过;输出 `PASS phase=read-state` | `D:\modelunion\kite\docker-deploy\data\sdk-tester-logs\read_state_review_cross_domain_final.log` |
|
|
111
|
+
| 数据库校验 | 在两域 Message/Group 数据库查询字段、排序规则和索引 | 两域状态表均为 128 字符、`utf8mb4_0900_bin` topic、8 字节自增主键;唯一索引最坏 1537/2048 bytes,均低于 3072 | `D:\modelunion\kite\docker-deploy\data\sdk-tester-logs\read_state_review_schema_final.log` |
|
|
112
|
+
|
|
113
|
+
跨域最终运行 ID 为 `agentid-read-state-review-final-20261009`,日志中包含双方向 `READ_STATE_FEDERATION_EVENT_OK`、`READ_STATE_P2P_OK`、`READ_STATE_GROUP_OK`、`READ_STATE_CROSS_DEVICE_OK` 和 `PASS phase=read-state`。失败时按测试指南从 `--read-state-from` 断点继续,不重复已通过阶段。
|
|
114
|
+
|
|
115
|
+
cross-SDK 标准矩阵运行命令(四端 agent 使用同一份 `cases.single-domain.json`):
|
|
116
|
+
|
|
117
|
+
```powershell
|
|
118
|
+
cd D:\modelunion\kite\aun-sdk-core\tests\cross-sdk
|
|
119
|
+
$env:CROSS_SDK_CASES_BASENAME = "cases.single-domain.json"
|
|
120
|
+
docker compose -f docker-compose.cross-sdk.yml up --force-recreate --abort-on-container-exit --exit-code-from cross-sdk-runner
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
共享 AID cross-SDK 矩阵运行命令(四端固定身份属于同一 AID,轮流写入并交叉查询):
|
|
124
|
+
|
|
125
|
+
```powershell
|
|
126
|
+
cd D:\modelunion\kite\aun-sdk-core\tests\cross-sdk
|
|
127
|
+
$env:CROSS_SDK_CASES_BASENAME = "cases.read-state-shared.json"
|
|
128
|
+
$env:CROSS_SDK_READ_STATE_SHARED_AID = "1"
|
|
129
|
+
docker compose -f docker-compose.cross-sdk.yml up --force-recreate --abort-on-container-exit --exit-code-from cross-sdk-runner
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
本地双域运行命令(完整挂载身份和源码,网络只指向 `kite-agentid-federation-net`):
|
|
133
|
+
|
|
134
|
+
```powershell
|
|
135
|
+
docker run --rm --network kite-agentid-federation-net `
|
|
136
|
+
-v "D:\modelunion\kite\aun-sdk-core\python\src:/sdk/src:ro" `
|
|
137
|
+
-v "D:\modelunion\kite\docker-deploy\tests\agentid_cross_domain_e2e.py:/tests/agentid_cross_domain_e2e.py:ro" `
|
|
138
|
+
-v "D:\modelunion\kite\docker-deploy\data\extended-acceptance\group-management:/identities/pub:ro" `
|
|
139
|
+
-v "D:\modelunion\kite\docker-deploy\data-agentid-cn\sdk-testers\python-fixed:/identities/cn:ro" `
|
|
140
|
+
-e PYTHONPATH=/sdk/src `
|
|
141
|
+
-e AUN_E2E_PUB_IDENTITY_ROOT=/identities/pub `
|
|
142
|
+
-e AUN_E2E_CN_IDENTITY_ROOT=/identities/cn `
|
|
143
|
+
-e AUN_E2E_CN_ENCRYPTION_SEED=cross-domain-e2e-v1 `
|
|
144
|
+
-e AUN_E2E_RUN_ROOT=/tmp/aun-e2e `
|
|
145
|
+
-e AUN_SDK_UVLOOP=0 -e AUN_CLI_UVLOOP=0 -e AUN_BENCH_UVLOOP=0 `
|
|
146
|
+
-e HTTP_PROXY= -e HTTPS_PROXY= -e ALL_PROXY= -e NO_PROXY=* `
|
|
147
|
+
aun-sdk-tester:latest python /tests/agentid_cross_domain_e2e.py `
|
|
148
|
+
--phase read-state --skip-reconnect --run-id agentid-read-state-review-final-20261009
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## 8. 完成定义
|
|
152
|
+
|
|
153
|
+
- 相关现有文档已按本轮契约同步,`docs/INDEX.md`、`docs/KITE_DOCS_GUIDE.md` 和 SDK 索引引用已核对。
|
|
154
|
+
- 服务端与四端 SDK 的新增测试在代码实现前能失败,完成后全部通过。
|
|
155
|
+
- 修复后隔离单元与静态检查的本轮实际结果已记录;部署层验收需使用更新后的镜像,不能引用第 7 节历史记录代替本轮结果,跨域只使用本地 `agentid.pub` ↔ `agentid.cn`。
|
|
156
|
+
- 已核对本功能涉及的代码、测试和文档文件,无临时 `DEBUG:` 日志、无未记录的公共接口变化;工作区中其它任务的并行修改按约束保留,不纳入本计划清理范围。
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
# AUN 跨设备已读未读方案
|
|
2
|
+
|
|
3
|
+
> 状态:实施契约,细节以详细设计为准
|
|
4
|
+
>
|
|
5
|
+
> 更新时间:2026-10-11
|
|
6
|
+
|
|
7
|
+
## 1. 目标与范围
|
|
8
|
+
|
|
9
|
+
在同一 AID 拥有多个设备时,同步各设备的会话已读位置,并让前端按话题计算会话未读数。
|
|
10
|
+
|
|
11
|
+
本方案覆盖:
|
|
12
|
+
|
|
13
|
+
- P2P 单聊中同一对端下的多个应用会话和话题;
|
|
14
|
+
- Group 会话及 Group 话题;
|
|
15
|
+
- 同一 AID 的设备间已读状态汇总;
|
|
16
|
+
- 按 AID、按 AID + 话题、按 AID + Group 查询;
|
|
17
|
+
- 单条上报、批量上报和只更新部分会话。
|
|
18
|
+
|
|
19
|
+
本方案不处理对端已读回执。对端是否读过自己发送的消息属于另一套状态和权限模型。
|
|
20
|
+
|
|
21
|
+
## 2. 现有约束
|
|
22
|
+
|
|
23
|
+
### 2.1 P2P namespace 不是前端会话
|
|
24
|
+
|
|
25
|
+
SDK 的 P2P Pull namespace 是 `p2p:<owner_aid>`,覆盖该 AID 收到的全部 P2P 消息。它只用于传输扫描和补洞。
|
|
26
|
+
|
|
27
|
+
P2P 收件消息的 `seq` 按 `(owner_aid, device_id)` 分配。不同设备的 `seq` 互相隔离,不能直接比较。
|
|
28
|
+
|
|
29
|
+
`message.ack` 保存的是设备/slot 的投递消费游标,表示 SDK 已经消费消息,不表示用户在前端打开了对应会话。
|
|
30
|
+
|
|
31
|
+
### 2.2 话题 key 属于应用层
|
|
32
|
+
|
|
33
|
+
当前 payload 约定中的 `chat_id`、`thread_id` 是应用上下文,通常随 E2EE payload 传输,Message/Group 服务不解析它们,也不使用它们路由消息。
|
|
34
|
+
|
|
35
|
+
话题 key 由应用层生成并传给 SDK。SDK 只透传、保存和返回该 key,不生成、不规范化、不推断,也不负责验证它和加密 payload 的业务一致性。
|
|
36
|
+
|
|
37
|
+
### 2.3 Group ACK 也不是业务已读
|
|
38
|
+
|
|
39
|
+
Group 的 `message_seq` / `event_seq` 和 `group.ack_messages` / `group.ack_events` 继续承担消息、事件的投递和消费游标职责。业务已读状态需要独立保存。
|
|
40
|
+
|
|
41
|
+
## 3. 核心决策
|
|
42
|
+
|
|
43
|
+
1. 传输层继续使用现有 P2P/Group namespace,不按话题拆分 Pull 或 ACK。
|
|
44
|
+
2. 业务已读状态按“设备 + 话题”保存,状态以该设备该话题最后已读的 `message_id` 为核心,另持久化公共顺序和定向事实维度。
|
|
45
|
+
3. 对外查询只返回所有设备汇总后的最新 `message_id`,以及这个 `message_id` 映射到当前查询设备的本地 `seq`。
|
|
46
|
+
4. 查询不返回其它设备的已读进度。
|
|
47
|
+
5. 话题键由应用层约定,服务端只把 `topic_key` 当作不透明键。P2P 的键在同一 AID 内需区分不同对端,例如 `conversation:bob.agentid.pub:main`;不同对端都使用 `main` 会合并成同一话题。
|
|
48
|
+
6. 前端使用同一话题内的消息顺序计算未读数和已读展示。
|
|
49
|
+
|
|
50
|
+
## 4. 状态模型
|
|
51
|
+
|
|
52
|
+
服务端内部保存每设备状态:
|
|
53
|
+
|
|
54
|
+
```text
|
|
55
|
+
P2P: (owner_aid, device_id, topic_key, is_targeted)
|
|
56
|
+
Group: (owner_aid, device_id, group_id, topic_key)
|
|
57
|
+
-> last_read_message_id, last_read_order
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
字段含义:
|
|
61
|
+
|
|
62
|
+
- `owner_aid`:已读状态所属的 AID,由认证身份确定;
|
|
63
|
+
- `device_id`:上报设备,由认证连接确定;
|
|
64
|
+
- `scope`:`p2p` 或 `group`;
|
|
65
|
+
- `topic_key`:应用层传入的不透明话题键;Group 状态另带 `group_id`;
|
|
66
|
+
- `last_read_message_id`:该设备该话题最后已读消息 ID;
|
|
67
|
+
- `updated_at`:服务端更新时间。
|
|
68
|
+
|
|
69
|
+
汇总缓存保存 message ID 和公共顺序,不改变设备状态的事实来源;Message 设备缓存和待写队列包含定向维度以隔离共享事实。
|
|
70
|
+
|
|
71
|
+
## 5. AID 汇总规则
|
|
72
|
+
|
|
73
|
+
同一话题收到设备上报后,服务端根据消息的公共顺序比较各设备的最后已读消息,得到:
|
|
74
|
+
|
|
75
|
+
```text
|
|
76
|
+
account_last_read_message_id = 各设备最后已读消息中位置最新的消息
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
这里的“最新”不能通过比较不同设备的 `seq` 得出。服务端使用 Message 的公共逻辑创建时间或 Group 群内公共顺序,并以 message ID 为并列键。公共顺序持久化为 `last_read_order`,查询和事件携带同名提示供 SDK 防止乱序回退;它不能作为设备 seq 或投递 ACK。
|
|
80
|
+
|
|
81
|
+
上报规则:
|
|
82
|
+
|
|
83
|
+
- 新消息 ID 推进汇总状态;
|
|
84
|
+
- 相同消息 ID 幂等成功;
|
|
85
|
+
- 较旧消息 ID 不得让状态回退;
|
|
86
|
+
- 批量请求中未出现的话题保持不变。
|
|
87
|
+
|
|
88
|
+
服务端通过消息公共位置判定前进,不把本地设备 `seq` 当作业务已读位置,也不能把不同设备的 `seq` 互相比较。未知公共位置保留待解析事实,不能凭更新时间或消息 ID 猜测前进。
|
|
89
|
+
|
|
90
|
+
## 6. 查询接口
|
|
91
|
+
|
|
92
|
+
查询接口为:
|
|
93
|
+
|
|
94
|
+
- `message.read_state`:查询 P2P 已读汇总;
|
|
95
|
+
- `group.read_state`:查询 Group 已读汇总。
|
|
96
|
+
|
|
97
|
+
查询支持以下过滤方式:
|
|
98
|
+
|
|
99
|
+
- 只传认证 AID:返回该 AID 的全部对应状态;
|
|
100
|
+
- AID + `topic_key`:返回指定话题;
|
|
101
|
+
- AID + `group_id`:返回指定 Group;
|
|
102
|
+
- AID + `group_id` + `topic_key`:返回指定 Group 话题。
|
|
103
|
+
|
|
104
|
+
AID 仅可作为当前认证 AID 的等值查询选择器,不能读取其它 AID 的已读状态。P2P 状态仅由该身份的 home 域管理;Gateway 从认证连接绑定主体,拒绝 `message.read/read_state` 的 federation 入站与出站,不能接受调用方伪造的 `_auth.aid`。
|
|
105
|
+
|
|
106
|
+
响应只返回汇总状态和当前设备的 seq 映射:
|
|
107
|
+
|
|
108
|
+
```json
|
|
109
|
+
{
|
|
110
|
+
"scope": "p2p",
|
|
111
|
+
"topic_key": "app-topic-42",
|
|
112
|
+
"last_read_message_id": "m-100",
|
|
113
|
+
"current_device_seq": 317,
|
|
114
|
+
"mapping_status": "resolved"
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
`current_device_seq` 的含义是:汇总后的 `last_read_message_id` 在当前查询设备收件箱中的本地 seq。
|
|
119
|
+
|
|
120
|
+
如果公共消息位置可解析,但消息尚未投递到当前设备或当前设备的投递映射已清理,则返回:
|
|
121
|
+
|
|
122
|
+
```json
|
|
123
|
+
{
|
|
124
|
+
"current_device_seq": null,
|
|
125
|
+
"mapping_status": "not_received"
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
不得用其它设备的 seq 填充该字段。
|
|
130
|
+
|
|
131
|
+
公共位置未知或公共索引已清理时返回 `null/unresolved`。定向 P2P 设备事实不进入共享汇总。
|
|
132
|
+
|
|
133
|
+
## 7. 上报接口
|
|
134
|
+
|
|
135
|
+
上报接口为:
|
|
136
|
+
|
|
137
|
+
- `message.read`:上报 P2P 已读状态;
|
|
138
|
+
- `group.read`:上报 Group 已读状态。
|
|
139
|
+
|
|
140
|
+
请求支持顶层单条或批量状态;只有省略 `states` 时使用顶层单条,显式空数组、null 或非数组值拒绝。下例只对照两种状态字段,须分别提交到 Message/Group 方法,不能发送为混合 scope 请求:
|
|
141
|
+
|
|
142
|
+
```json
|
|
143
|
+
{
|
|
144
|
+
"states": [
|
|
145
|
+
{
|
|
146
|
+
"scope": "p2p",
|
|
147
|
+
"topic_key": "app-topic-42",
|
|
148
|
+
"last_read_message_id": "m-100"
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
"scope": "group",
|
|
152
|
+
"group_id": "group.example",
|
|
153
|
+
"topic_key": "topic-7",
|
|
154
|
+
"last_read_message_id": "gm-20"
|
|
155
|
+
}
|
|
156
|
+
]
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
请求中的 `device_id` 不由应用填写,服务端从当前认证连接取得。请求可以只携带部分会话,服务端只更新这些会话。
|
|
161
|
+
|
|
162
|
+
## 8. 消息事件与前端计算
|
|
163
|
+
|
|
164
|
+
当某话题的 AID 汇总状态前进时,Gateway 向该状态 owner AID 的在线设备定向分发已读变更事件。事件包含:
|
|
165
|
+
|
|
166
|
+
- `scope`;
|
|
167
|
+
- `group_id`(Group 时);
|
|
168
|
+
- `topic_key`;
|
|
169
|
+
- 汇总后的 `last_read_message_id`。
|
|
170
|
+
|
|
171
|
+
事件不携带统一 seq,因为各设备 seq 隔离。接收设备需要本地 seq 时,使用自己的消息索引完成映射。
|
|
172
|
+
|
|
173
|
+
前端按话题维护消息列表,并用汇总的 `last_read_message_id` 计算:
|
|
174
|
+
|
|
175
|
+
- 话题未读数;
|
|
176
|
+
- 消息是否已读;
|
|
177
|
+
- 会话列表上的未读标记。
|
|
178
|
+
|
|
179
|
+
消息列表不完整时,前端需要先通过现有 Pull/History 能力补齐对应话题的消息,再计算精确未读数。
|
|
180
|
+
|
|
181
|
+
## 9. P2P、Group 和定向消息边界
|
|
182
|
+
|
|
183
|
+
### 9.1 P2P
|
|
184
|
+
|
|
185
|
+
P2P 消息仍全部进入 `p2p:<owner_aid>` 传输 namespace。应用解密后按自己传入的 `topic_key` 归入话题。
|
|
186
|
+
|
|
187
|
+
第一期只有面向整个 AID 投递的消息进入共享已读汇总。`message.send.device_id` / `to_device_id` 定向消息形成独立设备事实,不改变共享汇总,也不发送共享已读变更;当前 `read_state` 不返回定向设备明细。
|
|
188
|
+
|
|
189
|
+
### 9.2 Group
|
|
190
|
+
|
|
191
|
+
Group 的会话范围由 `group_id` 确定,话题范围由应用传入的 `topic_key` 确定。Group 消息已读和 Group 事件已读可以分别建状态;第一期只要求消息已读,事件已读另行定义。
|
|
192
|
+
|
|
193
|
+
### 9.3 发送方已读回执
|
|
194
|
+
|
|
195
|
+
本方案只同步同一 AID 自己的跨设备已读状态,不向消息发送方暴露“对方已读”。发送方已读回执需要独立的授权、隐私和事件设计。
|
|
196
|
+
|
|
197
|
+
## 10. SDK 和 Gateway 改动范围
|
|
198
|
+
|
|
199
|
+
Python、TypeScript、JavaScript、Go 四端 SDK 增加统一的已读上报和状态查询门面,并缓存按话题索引的 AID 汇总状态。
|
|
200
|
+
|
|
201
|
+
SDK 必须:
|
|
202
|
+
|
|
203
|
+
- 接受应用传入的 `topic_key` 原值;
|
|
204
|
+
- 支持单条和批量上报;
|
|
205
|
+
- 支持按 AID、话题和 Group 查询;
|
|
206
|
+
- 处理已读变更事件,`scope` 严格匹配原事件,`owner_aid` 必须非空且等于当前身份;
|
|
207
|
+
- 重连后重新查询状态;
|
|
208
|
+
- 保留原始查询范围(包括空结果、全量及按群查询),通过 SDK 本地 `read_state_sync` 交付本轮补偿状态及逐项错误;
|
|
209
|
+
- 写入回执不进入共享汇总缓存,旧事件不交付应用,同位置事件保留设备映射;切换 AID/device 后按身份代际隔离旧缓存及补偿,A→B→A 也不能接收旧代际结果,原在途 RPC 仍可返回;
|
|
210
|
+
- TS/JS 的公共顺序与设备 seq 在安全范围内使用 number,超出时使用精确十进制字符串;网络仍使用 JSON 整数;
|
|
211
|
+
- 四个已读门面保留显式 `None/null/nil` 字段供服务端拒绝,不改变其他门面或现有 Pull、History、ACK 和 SeqTracker 语义。
|
|
212
|
+
|
|
213
|
+
Gateway 校验可信服务来源,已读变更即使指定显式 route 也只向状态 owner AID 的在线设备定向分发,不能扩大到其他成员或 AID;事件 ID 使用无歧义编码。离线设备通过状态查询补偿,不要求已读变更事件进入消息 Pull 队列。
|
|
214
|
+
|
|
215
|
+
Group 无 group ID 的查询覆盖当前 AID 在各所属域有权限的群。混合域批量按所属域逐域校验、提交;失败时整个 RPC 返回错误,已提交部分通过幂等重试收敛,不承诺跨域回滚。Group 历史别名解析为 canonical ID,权限按群版本跨实例同步。晚到公共位置条件回填仍为 order=0 的对应事实并持久化。数据库刷写批次内核实共享汇总前进,RPC 只等待本次依赖的提交句柄;失败日志不泄露 topic。
|
|
216
|
+
|
|
217
|
+
## 11. 实施阶段
|
|
218
|
+
|
|
219
|
+
### Phase 0:契约冻结
|
|
220
|
+
|
|
221
|
+
- 冻结 `scope`、`topic_key`、`group_id`、`last_read_message_id` 字段;
|
|
222
|
+
- 冻结 AID 汇总为“同一话题内位置最新的 message ID”;
|
|
223
|
+
- 冻结查询不返回其它设备进度;
|
|
224
|
+
- 冻结定向 P2P 消息的设备级边界。
|
|
225
|
+
|
|
226
|
+
### Phase 1:服务端状态和 RPC
|
|
227
|
+
|
|
228
|
+
- Message 服务增加设备级已读状态存储;
|
|
229
|
+
- Group 服务增加设备级已读状态存储;
|
|
230
|
+
- 实现单条/批量部分更新;
|
|
231
|
+
- 实现 message ID 公共顺序查询和当前设备 seq 映射;
|
|
232
|
+
- 实现按 AID、话题、Group 过滤查询。
|
|
233
|
+
|
|
234
|
+
### Phase 2:Gateway 事件
|
|
235
|
+
|
|
236
|
+
- 增加 P2P/Group 已读变更事件;
|
|
237
|
+
- 只在汇总状态前进时向状态 owner AID 定向分发;
|
|
238
|
+
- 验证事件丢失后的查询补偿路径。
|
|
239
|
+
|
|
240
|
+
### Phase 3:四端 SDK
|
|
241
|
+
|
|
242
|
+
- 增加 facade、类型和事件分发;
|
|
243
|
+
- 增加本地汇总状态缓存;
|
|
244
|
+
- 对齐单条、批量、部分更新和重连恢复行为。
|
|
245
|
+
|
|
246
|
+
### Phase 4:前端接入
|
|
247
|
+
|
|
248
|
+
- 应用传入并持久化 `topic_key`;
|
|
249
|
+
- 按话题保存消息索引;
|
|
250
|
+
- 按汇总 message ID 计算未读数;
|
|
251
|
+
- 处理 `current_device_seq=null` 的未投递和历史清理情况。
|
|
252
|
+
|
|
253
|
+
## 12. 验收测试
|
|
254
|
+
|
|
255
|
+
- 同一 P2P namespace 中交错投递多个对端、多个话题,已读状态按话题隔离;
|
|
256
|
+
- 两台设备按 message ID 上报同一话题,返回汇总 message ID 及各自投递索引的 seq;两端 seq 独立且数值允许相同;
|
|
257
|
+
- 查询另一台设备已读到的消息时,当前设备正确返回自己的 seq 或 `null`;
|
|
258
|
+
- 较旧上报不会让汇总状态回退;重复上报幂等;
|
|
259
|
+
- 批量上报只改变请求中包含的话题;
|
|
260
|
+
- 按 AID 全量查询、按 AID + 话题过滤、按 AID + Group 过滤结果一致;
|
|
261
|
+
- 话题 key 相同但作用域不同不会互相覆盖;
|
|
262
|
+
- 设备离线后重连能通过查询恢复汇总状态;
|
|
263
|
+
- Group 消息与 Group 事件状态保持独立;
|
|
264
|
+
- 定向 P2P 消息不会错误进入 AID 共享未读;
|
|
265
|
+
- 四端 SDK 的上报、查询、事件和缓存行为一致。
|
|
266
|
+
|
|
267
|
+
## 13. 明确不改变的接口
|
|
268
|
+
|
|
269
|
+
- `message.pull` / `message.history` 的 P2P namespace 和 seq 语义;
|
|
270
|
+
- `message.ack` 的设备/slot 消费游标语义;
|
|
271
|
+
- `group.pull` / `group.pull_events` 的消息、事件游标;
|
|
272
|
+
- `group.ack_messages` / `group.ack_events` 的消费确认语义;
|
|
273
|
+
- 现有 E2EE payload 中应用话题字段的加密边界。
|