@agentunion/fastaun-browser 0.5.3 → 0.5.6

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.
Files changed (151) hide show
  1. package/CHANGELOG.md +130 -56
  2. package/_packed_docs/AUN/347/246/273/347/272/277/346/216/250/351/200/201/346/216/245/345/205/245/344/270/216/346/274/224/347/244/272/346/214/207/345/215/227.md +290 -0
  3. package/_packed_docs/AUN/347/246/273/347/272/277/346/216/250/351/200/201/346/234/215/345/212/241/346/236/266/346/236/204/344/270/216/350/257/246/347/273/206/345/256/236/347/216/260/350/256/241/345/210/222-codex.md +994 -0
  4. package/_packed_docs/AUN/347/246/273/347/272/277/346/216/250/351/200/201/346/234/215/345/212/241/350/277/220/347/273/264/344/270/216/345/217/221/345/270/203/346/214/207/345/215/227.md +144 -0
  5. package/_packed_docs/CHANGELOG.md +130 -56
  6. package/_packed_docs/INDEX.md +127 -69
  7. package/_packed_docs/KITE_DOCS_GUIDE.md +63 -29
  8. package/_packed_docs/audit/AUN/346/234/215/345/212/241Go/345/214/226/351/207/215/346/236/204/347/262/276/347/273/206/345/214/226/346/272/220/347/240/201/345/256/241/346/237/245-20260718.md +366 -0
  9. package/_packed_docs/aun/345/205/254/347/275/221/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md +227 -0
  10. 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 +1004 -0
  11. package/_packed_docs/aun/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md +999 -0
  12. package/_packed_docs/cli/CLI/346/211/213/345/206/214.md +6 -4
  13. package/_packed_docs/protocol/06-/346/234/215/345/212/241/345/215/217/350/256/256.md +58 -20
  14. package/_packed_docs/protocol/10-Group-/345/255/220/345/215/217/350/256/256.md +219 -247
  15. package/_packed_docs/protocol/12-Stream-/345/255/220/345/215/217/350/256/256.md +14 -14
  16. package/_packed_docs/protocol/13-Agent/350/241/214/344/270/272/350/247/204/350/214/203.md +3 -3
  17. package/_packed_docs/protocol/15-/347/246/273/347/272/277/346/216/250/351/200/201/351/200/232/347/237/245/345/215/217/350/256/256.md +165 -421
  18. package/_packed_docs/protocol/README.md +1 -0
  19. package/_packed_docs/protocol/aun-docs-guide.md +7 -4
  20. package/_packed_docs/protocol/index.md +25 -19
  21. package/_packed_docs/sdk/02-WebSocket/345/215/217/350/256/256.md +62 -17
  22. package/_packed_docs/sdk/03-/346/240/270/345/277/203/346/246/202/345/277/265.md +22 -0
  23. package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +67 -78
  24. package/_packed_docs/sdk/05-E2EE/345/212/240/345/257/206/351/200/232/344/277/241.md +6 -2
  25. package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +87 -41
  26. package/_packed_docs/sdk/08-/346/234/200/344/275/263/345/256/236/350/267/265.md +18 -5
  27. package/_packed_docs/sdk/09-group-rpc-manual.md +307 -267
  28. package/_packed_docs/sdk/09-message-rpc-manual.md +142 -103
  29. package/_packed_docs/sdk/09-payload-reference.md +1 -1
  30. package/_packed_docs/sdk/09-stream-rpc-manual.md +8 -8
  31. package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +26 -17
  32. package/_packed_docs/sdk/INDEX.md +42 -34
  33. package/_packed_docs/sdk/README.md +7 -6
  34. package/_packed_docs//346/266/210/346/201/257/345/220/214/346/255/245/344/270/216/345/216/206/345/217/262/346/213/211/345/217/226/344/273/243/347/240/201/345/256/241/346/237/245/351/227/256/351/242/230/346/270/205/345/215/225.md +229 -0
  35. package/_packed_docs//346/266/210/346/201/257/345/220/214/346/255/245/344/270/216/345/216/206/345/217/262/346/213/211/345/217/226/346/224/271/351/200/240/346/226/271/346/241/210.md +750 -0
  36. package/dist/agent-md.d.ts +4 -5
  37. package/dist/agent-md.d.ts.map +1 -1
  38. package/dist/agent-md.js +66 -43
  39. package/dist/agent-md.js.map +1 -1
  40. package/dist/aid-store.d.ts +2 -6
  41. package/dist/aid-store.d.ts.map +1 -1
  42. package/dist/aid-store.js +20 -14
  43. package/dist/aid-store.js.map +1 -1
  44. package/dist/auth.d.ts.map +1 -1
  45. package/dist/auth.js +30 -11
  46. package/dist/auth.js.map +1 -1
  47. package/dist/bundle.js +3399 -1069
  48. package/dist/client/delivery.d.ts +75 -8
  49. package/dist/client/delivery.d.ts.map +1 -1
  50. package/dist/client/delivery.js +1419 -245
  51. package/dist/client/delivery.js.map +1 -1
  52. package/dist/client/group-state.d.ts.map +1 -1
  53. package/dist/client/group-state.js +27 -24
  54. package/dist/client/group-state.js.map +1 -1
  55. package/dist/client/lifecycle.d.ts.map +1 -1
  56. package/dist/client/lifecycle.js +48 -4
  57. package/dist/client/lifecycle.js.map +1 -1
  58. package/dist/client/mention-mode.d.ts +7 -0
  59. package/dist/client/mention-mode.d.ts.map +1 -0
  60. package/dist/client/mention-mode.js +184 -0
  61. package/dist/client/mention-mode.js.map +1 -0
  62. package/dist/client/peers.d.ts +1 -1
  63. package/dist/client/peers.d.ts.map +1 -1
  64. package/dist/client/peers.js +26 -3
  65. package/dist/client/peers.js.map +1 -1
  66. package/dist/client/rpc-pipeline.d.ts +22 -1
  67. package/dist/client/rpc-pipeline.d.ts.map +1 -1
  68. package/dist/client/rpc-pipeline.js +309 -74
  69. package/dist/client/rpc-pipeline.js.map +1 -1
  70. package/dist/client/v2-e2ee.d.ts +25 -2
  71. package/dist/client/v2-e2ee.d.ts.map +1 -1
  72. package/dist/client/v2-e2ee.js +606 -97
  73. package/dist/client/v2-e2ee.js.map +1 -1
  74. package/dist/client.d.ts +24 -5
  75. package/dist/client.d.ts.map +1 -1
  76. package/dist/client.js +404 -215
  77. package/dist/client.js.map +1 -1
  78. package/dist/errors.d.ts.map +1 -1
  79. package/dist/errors.js +4 -1
  80. package/dist/errors.js.map +1 -1
  81. package/dist/facades.d.ts +6 -0
  82. package/dist/facades.d.ts.map +1 -1
  83. package/dist/facades.js +199 -104
  84. package/dist/facades.js.map +1 -1
  85. package/dist/group-index.d.ts +6 -1
  86. package/dist/group-index.d.ts.map +1 -1
  87. package/dist/group-index.js +44 -25
  88. package/dist/group-index.js.map +1 -1
  89. package/dist/index.d.ts +1 -1
  90. package/dist/index.d.ts.map +1 -1
  91. package/dist/index.js +1 -1
  92. package/dist/index.js.map +1 -1
  93. package/dist/keystore/index.d.ts +10 -5
  94. package/dist/keystore/index.d.ts.map +1 -1
  95. package/dist/keystore/indexeddb-identity-store.d.ts +0 -12
  96. package/dist/keystore/indexeddb-identity-store.d.ts.map +1 -1
  97. package/dist/keystore/indexeddb-identity-store.js +0 -60
  98. package/dist/keystore/indexeddb-identity-store.js.map +1 -1
  99. package/dist/keystore/indexeddb-shared.d.ts.map +1 -1
  100. package/dist/keystore/indexeddb-shared.js +9 -5
  101. package/dist/keystore/indexeddb-shared.js.map +1 -1
  102. package/dist/keystore/indexeddb-token-store.d.ts +3 -1
  103. package/dist/keystore/indexeddb-token-store.d.ts.map +1 -1
  104. package/dist/keystore/indexeddb-token-store.js +47 -2
  105. package/dist/keystore/indexeddb-token-store.js.map +1 -1
  106. package/dist/register-flow.d.ts.map +1 -1
  107. package/dist/register-flow.js +28 -3
  108. package/dist/register-flow.js.map +1 -1
  109. package/dist/seq-tracker.d.ts +28 -8
  110. package/dist/seq-tracker.d.ts.map +1 -1
  111. package/dist/seq-tracker.js +218 -61
  112. package/dist/seq-tracker.js.map +1 -1
  113. package/dist/storage/vfs.d.ts +1 -0
  114. package/dist/storage/vfs.d.ts.map +1 -1
  115. package/dist/storage/vfs.js +26 -2
  116. package/dist/storage/vfs.js.map +1 -1
  117. package/dist/tools/cross-sdk-agent.js +376 -10
  118. package/dist/tools/cross-sdk-agent.js.map +1 -1
  119. package/dist/transport.d.ts +12 -0
  120. package/dist/transport.d.ts.map +1 -1
  121. package/dist/transport.js +218 -101
  122. package/dist/transport.js.map +1 -1
  123. package/dist/v2/session/session.d.ts.map +1 -1
  124. package/dist/v2/session/session.js +3 -4
  125. package/dist/v2/session/session.js.map +1 -1
  126. package/dist/v2/state/commitment.d.ts.map +1 -1
  127. package/dist/v2/state/commitment.js +1 -2
  128. package/dist/v2/state/commitment.js.map +1 -1
  129. package/dist/version.d.ts +1 -1
  130. package/dist/version.js +1 -1
  131. package/package.json +6 -5
  132. package/dist/group-resources.d.ts +0 -98
  133. package/dist/group-resources.d.ts.map +0 -1
  134. package/dist/group-resources.js +0 -635
  135. package/dist/group-resources.js.map +0 -1
  136. package/dist/keystore/indexeddb.d.ts +0 -179
  137. package/dist/keystore/indexeddb.d.ts.map +0 -1
  138. package/dist/keystore/indexeddb.js +0 -2031
  139. package/dist/keystore/indexeddb.js.map +0 -1
  140. package/dist/namespaces/auth.d.ts +0 -98
  141. package/dist/namespaces/auth.d.ts.map +0 -1
  142. package/dist/namespaces/auth.js +0 -992
  143. package/dist/namespaces/auth.js.map +0 -1
  144. package/dist/namespaces/custody.d.ts +0 -51
  145. package/dist/namespaces/custody.d.ts.map +0 -1
  146. package/dist/namespaces/custody.js +0 -302
  147. package/dist/namespaces/custody.js.map +0 -1
  148. package/dist/namespaces/meta.d.ts +0 -109
  149. package/dist/namespaces/meta.d.ts.map +0 -1
  150. package/dist/namespaces/meta.js +0 -549
  151. package/dist/namespaces/meta.js.map +0 -1
@@ -0,0 +1,994 @@
1
+ # AUN 离线推送服务架构与详细实现计划
2
+
3
+ > 状态:架构方案已收敛,尚未实施
4
+ > 日期:2026-07-24
5
+ > 适用范围:AUN Gateway、Message、Group、独立 Push Service、aun-console 三中心、Python SDK 与测试用 Mock Push Server
6
+
7
+ ## 1. 背景与结论
8
+
9
+ P2P Message 和 Group Message 的接收者全部离线时,Gateway 无法通过 WebSocket RPC 或事件把在线通知送到移动端。需要增加一条独立于消息可靠存储的离线唤醒链路,由应用方 Push Server 调用 APNs、FCM 或其他厂商推送服务。
10
+
11
+ Push Service 必须是独立的 AUN Go 服务,并由 aun-console 的控制中心、注册中心和配置中心统一纳管。现有 `docs/protocol/15-离线推送通知协议.md` 只作为历史参考,其中把 PushAggregator、push_config 和 ACK 状态放在 Gateway 内的架构不再采用。
12
+
13
+ 最终边界如下:
14
+
15
+ - Gateway 只负责认证、可信上下文注入、服务路由、在线 Session 查询和事件投递。
16
+ - Message / Group 只在消息持久化成功后产生推送候选,不管理 token,不判断最终离线状态。
17
+ - Push Service 负责注册、持久化、聚合、频控、最终离线检查、批次、ACK、重试和恢复。
18
+ - Push Server 是普通 AUN 客户端 AID,负责验证 opaque token 并调用传统推送服务。
19
+ - Python SDK 只为测试和仓库外 Rust SDK 提供参考实现;Go、TypeScript、JavaScript SDK 本阶段不同步。
20
+
21
+ 本方案不新增 Kernel RPC、不新增 Kite 核心事件、不修改事件订阅机制,也不要求 Watchdog 增加特殊逻辑。
22
+
23
+ ## 2. 设计目标与非目标
24
+
25
+ ### 2.1 设计目标
26
+
27
+ - P2P 和 Group 的本域、跨域离线接收者均能收到传统移动推送。
28
+ - 消息主链不受 Push Service、Push Server、APNs 或 FCM 故障影响。
29
+ - Push Service 接收候选后具备持久化、幂等、有限重试和重启恢复能力。
30
+ - 支持一个 AID 的多设备、多 slot 和不同 push token。
31
+ - 支持三中心 desired-state 启停、配置下发、服务发现、状态展示和节点迁移。
32
+ - 所有推送 payload 均不包含消息正文或 E2EE 密文。
33
+ - 正常、错误、异常、竞态和超时路径均有明确状态与诊断日志。
34
+
35
+ ### 2.2 非目标
36
+
37
+ - Push Service 不直接集成 APNs、FCM 或厂商 SDK。
38
+ - Push Service 不读取 Message、Group 或客户端数据库。
39
+ - Push Service 不保证移动设备已经展示通知,只确认 Push Server 或第三方平台是否接受。
40
+ - 本阶段不解决多 Gateway 的集群级在线 Session 汇聚。
41
+ - 本阶段不为 Go、TypeScript、JavaScript SDK 增加 Push facade。
42
+ - 推送不是消息可靠传输通道,真实消息仍通过 `message.pull` 或 `group.pull` 获取。
43
+
44
+ ## 3. 总体架构
45
+
46
+ ```mermaid
47
+ flowchart LR
48
+ C[移动端 / Python 测试客户端]
49
+ MG[Message / Group]
50
+ GW[Gateway]
51
+ PS[Push Service\nGo / 三中心纳管]
52
+ DB[(aun_push)]
53
+ PNS[Push Server AID\nPython Mock / 真实服务]
54
+ PP[APNs / FCM / 厂商推送]
55
+ CC[AUN 三中心]
56
+ L[Launcher]
57
+
58
+ C -->|push.register / unregister| GW
59
+ MG -->|push.enqueue| GW
60
+ GW --> PS
61
+ PS --> DB
62
+ PS -->|gateway.query_sessions| GW
63
+ PS -->|event/push.offline_message| GW
64
+ GW --> PNS
65
+ PNS -->|push.ack| GW
66
+ GW --> PS
67
+ PNS --> PP
68
+ PP --> C
69
+
70
+ CC -->|配置 / desired-state / 服务发现| L
71
+ L -->|启动、停止、迁移、drain| PS
72
+ ```
73
+
74
+ 模块间仅通过环境变量、WebSocket JSON-RPC 2.0、HTTP 诊断接口和数据库格式约定协作。Push Service 不 import 其他 Kite/AUN 服务的代码,满足零共享代码依赖原则。
75
+
76
+ ## 4. 组件职责
77
+
78
+ | 组件 | 核心职责 | 明确禁止 |
79
+ | --- | --- | --- |
80
+ | Message / Group | 消息持久化成功后产生推送候选;本域目标分片;跨域目标域触发 | 读取 push token;等待 Push Server ACK;因推送失败而使 send 失败 |
81
+ | Push Service | 注册、加密存储、候选去重、聚合、在线确认、批次、ACK、重试、频控、恢复 | 读取消息正文;调用 APNs/FCM;信任客户端自报 AID/device |
82
+ | Gateway | 注入 `_auth` / `_caller_id`;路由 `push.*`;查询 Session;投递 Push 事件 | 存储 push_config;实现聚合器;解释 opaque token |
83
+ | Push Server | 验证 token;调用平台服务;按 delivery_id 幂等;逐项 ACK | 通过事件正文替代 AUN pull;ACK 不属于自己的批次 |
84
+ | aun-console 三中心 | desired-state、单实例策略、配置、服务发现、状态和诊断 | 直接参与业务消息或 Push batch 调度 |
85
+
86
+ ## 5. 对历史协议的重构
87
+
88
+ 历史协议保留以下原则:
89
+
90
+ - 推送由目标 AID 所属域负责。
91
+ - V1 中任意一个设备在线即不触发该 AID 的离线推送。
92
+ - `push_token` 对 AUN 服务不透明,由 Push Server 自签、自验。
93
+ - 推送只携带唤醒元数据,不携带消息正文和密文。
94
+
95
+ 需要删除或替换的设计:
96
+
97
+ | 历史设计 | 新设计 |
98
+ | --- | --- |
99
+ | `auth.login/auth.connect` 携带 push 字段 | 显式调用 `push.register` |
100
+ | Gateway 保存 `push_config` | Push Service 独立数据库保存 |
101
+ | Gateway 内置 PushAggregator | Push Service 内置调度器和持久化状态机 |
102
+ | 每个 AID 只有一条配置 | 每个 `(aid, device_id, slot_id)` 一条注册 |
103
+ | 最后一次连接覆盖所有设备 | 每个安装独立更新和注销 |
104
+ | 批次级 `push.ack(batch_id)` | 逐 `delivery_id` 返回标准化结果 |
105
+ | ACK 超时后直接放弃 | 有限重试,受候选 TTL 和最大尝试次数约束 |
106
+ | Gateway 决定 token 是否可用 | Push Server 返回 normalized result,Push Service按注册版本处理 |
107
+
108
+ ## 6. 对外协议
109
+
110
+ ### 6.1 `push.register`
111
+
112
+ 调用者为已完成 Gateway 认证的普通客户端。
113
+
114
+ 客户端参数:
115
+
116
+ - `push_server_aid`:负责该安装的 Push Server AID。
117
+ - `push_token`:由 Push Server 签发的 opaque token。
118
+ - `provider`:可选,仅用于诊断,例如 `mock`、`apns`、`fcm`。
119
+ - `app_id`:可选,标识应用或租户。
120
+
121
+ 可信身份只能使用 Gateway 注入的:
122
+
123
+ - `_auth.aid`
124
+ - `_auth.device_id`
125
+ - `_auth.slot_id`
126
+ - `_auth.connection_id`
127
+
128
+ 客户端参数中出现 `aid`、`device_id` 或 `slot_id` 时不得覆盖可信身份。
129
+
130
+ 处理规则:
131
+
132
+ 1. 校验已认证身份完整。
133
+ 2. 校验 `push_server_aid` 属于本域并存在于配置中心白名单。
134
+ 3. 校验 token 非空、长度不超过上限。
135
+ 4. 使用当前密钥版本加密 token。
136
+ 5. 按 `(aid, device_id, slot_id)` UPSERT,并递增注册版本。
137
+ 6. 返回 `registration_id`、`registration_version` 和更新时间,不返回 token。
138
+
139
+ 客户端应在首次获取 token、token 变化和应用重新激活时调用。重复注册相同 token 是幂等更新。
140
+
141
+ ### 6.2 `push.unregister`
142
+
143
+ 调用者为已认证普通客户端。默认只注销当前 `_auth.aid/device_id/slot_id` 对应安装。
144
+
145
+ 注销是幂等操作:不存在注册时仍返回成功。已经进入事件投递阶段的通知不可撤回;尚未 dispatch 的 delivery 在发送前再次检查注册版本并终止。
146
+
147
+ ### 6.3 `push.enqueue`
148
+
149
+ 调用者只能是 Message 或 Group 服务。Push Service 必须校验 Gateway 注入的 `_caller_id`:
150
+
151
+ - `_caller_id=message`
152
+ - `_caller_id=group`
153
+
154
+ 普通客户端或其他服务调用必须拒绝。
155
+
156
+ 每个候选项包含:
157
+
158
+ - `candidate_id`:确定性幂等键。
159
+ - `kind`:`p2p` 或 `group`。
160
+ - `target_aid`:目标 AID。
161
+ - `occurred_at_ms`:消息提交时间。
162
+ - `expires_at_ms`:候选过期时间。
163
+ - 可选的内部 trace 信息。
164
+
165
+ 禁止携带:
166
+
167
+ - 消息正文。
168
+ - E2EE 密文。
169
+ - push token。
170
+ - 客户端私钥、证书或认证 token。
171
+
172
+ 批量调用必须设置条数和总字节数上限。单个无效 item 不应阻塞其他合法 item,响应按 item 返回 accepted、duplicate 或 rejected。
173
+
174
+ Push Service 只有在数据库提交成功后才返回 accepted。Message / Group 使用同一 `candidate_id` 重试不会产生重复任务。
175
+
176
+ ### 6.4 `event/push.offline_message`
177
+
178
+ Push Service 通过 `gateway.dispatch_event` 向 Push Server AID 的一个确定 Session 投递事件,且固定设置 `federation_forward=false`。
179
+
180
+ 批次公共字段:
181
+
182
+ - `protocol_version`
183
+ - `batch_id`
184
+ - `expires_at_ms`
185
+ - `items`
186
+
187
+ 每个 item 包含:
188
+
189
+ - `delivery_id`
190
+ - `target_aid`
191
+ - `device_id`
192
+ - `slot_id`
193
+ - `push_token`
194
+ - `category`
195
+ - `new_count`
196
+ - `latest_at_ms`
197
+
198
+ V1 默认不包含 sender AID、message ID、group ID、消息正文和密文。Push Server 生成通用通知,移动端上线后再通过 AUN 拉取真实消息。
199
+
200
+ ### 6.5 `push.ack`
201
+
202
+ 调用者为 Push Server AID,使用 Gateway 注入的 `_auth.aid` 鉴权。`_auth.aid` 必须与 batch 所属 `push_server_aid` 一致。
203
+
204
+ 请求包含 `batch_id` 和逐项结果:
205
+
206
+ | status | 语义 | Push Service 行为 |
207
+ | --- | --- | --- |
208
+ | `accepted` | 第三方平台已接受 | delivery 进入成功终态 |
209
+ | `retryable_failure` | 临时失败 | 按 `retry_after_ms` 或服务退避规则重试 |
210
+ | `permanent_failure` | 永久失败 | delivery 进入永久失败终态 |
211
+
212
+ 只有 `permanent_failure` 且错误码为 `invalid_token` 或 `unregistered` 时才自动禁用注册。禁用前必须确认当前注册版本仍等于发送快照版本,避免旧 token 的迟到 ACK 禁用新 token。
213
+
214
+ ACK 规则:
215
+
216
+ - 重复 ACK 幂等。
217
+ - 部分 ACK 只终结已返回的 items。
218
+ - 未返回的 items 保持 inflight,等待 ACK 超时。
219
+ - ACK 中出现不属于该 batch 的 delivery 必须拒绝。
220
+ - batch 过期后的迟到 ACK 只返回当前终态,不重新激活任务。
221
+
222
+ ## 7. 正常业务流程
223
+
224
+ ### 7.1 注册流程
225
+
226
+ ```mermaid
227
+ sequenceDiagram
228
+ participant App as 移动端
229
+ participant PNS as Push Server
230
+ participant GW as Gateway
231
+ participant PS as Push Service
232
+ participant DB as aun_push
233
+
234
+ App->>PNS: 申请 opaque push_token
235
+ PNS-->>App: push_token
236
+ App->>GW: push.register(push_server_aid, push_token)
237
+ GW->>PS: 注入可信 _auth 后转发
238
+ PS->>PS: 白名单、域、长度校验
239
+ PS->>DB: 加密 UPSERT 注册并递增版本
240
+ DB-->>PS: commit
241
+ PS-->>App: registration_id/version
242
+ ```
243
+
244
+ ### 7.2 本域 P2P 离线推送
245
+
246
+ ```mermaid
247
+ sequenceDiagram
248
+ participant A as Sender
249
+ participant M as Message
250
+ participant PS as Push Service
251
+ participant GW as Gateway
252
+ participant PNS as Push Server
253
+ participant B as Target Mobile
254
+
255
+ A->>M: message.send(to=B)
256
+ M->>M: 持久化成功
257
+ M-->>A: message accepted
258
+ M->>PS: 异步 push.enqueue
259
+ PS->>PS: 去重、聚合、频控
260
+ PS->>GW: gateway.query_sessions(B)
261
+ GW-->>PS: online=false
262
+ PS->>GW: query Push Server sessions
263
+ GW-->>PS: 选择一个 connection_id
264
+ PS->>GW: event/push.offline_message
265
+ GW->>PNS: Push batch
266
+ PNS->>PNS: 验证 token、调用 APNs/FCM
267
+ PNS->>PS: push.ack
268
+ PNS-->>B: 传统推送通知
269
+ B->>M: 上线后 message.pull
270
+ ```
271
+
272
+ ### 7.3 Group 离线推送
273
+
274
+ Group 服务在持久化完成并确定本域成员后,按目标 AID 批量产生候选。Push Service 不读取群成员表,也不重新计算群成员。
275
+
276
+ Group 消息候选必须满足:
277
+
278
+ - 不为发送者自身产生候选。
279
+ - 本域 Group 只提交本域成员。
280
+ - federation 目标域 Group 只为目标域成员提交候选。
281
+ - federation 重放使用相同 `candidate_id`,由 Push Service 唯一约束去重。
282
+ - 单次 `push.enqueue` 按条数和总字节数分批,防止大群 fanout 占满 RPC 队列。
283
+
284
+ ### 7.4 跨域原则
285
+
286
+ P2P:
287
+
288
+ - 源域 Message 不为外域目标产生候选。
289
+ - 目标域 Message 在目标消息持久化成功后产生候选。
290
+ - 只有目标域 Push Service 可以读取目标域注册并向目标域 Push Server 投递。
291
+
292
+ Group:
293
+
294
+ - 源域 Group 为本域成员产生候选。
295
+ - 经过 `group.relay_event` 到达目标域后,目标域 Group 为本域成员产生候选。
296
+ - Push 事件本身不进行 federation 转发。
297
+
298
+ ## 8. 在线判断与竞态规则
299
+
300
+ ### 8.1 V1 在线策略
301
+
302
+ 注册按安装保存,但在线抑制按 AID 判断:目标 AID 只要存在任意在线 Session,本轮所有设备均不发送传统推送。
303
+
304
+ 选择该策略的原因:
305
+
306
+ - 避免同一用户正在一个终端使用时,其他终端持续产生通知噪音。
307
+ - 当前 Gateway 已能可靠回答 AID 是否存在 Session。
308
+ - 不需要在 Push Service 中复制 Session 目录。
309
+
310
+ ### 8.2 最终检查时点
311
+
312
+ Message / Group 不在 enqueue 时判断在线。Push Service 在真正 dispatch 前调用 `gateway.query_sessions`:
313
+
314
+ - `online=true`:候选进入 `suppressed_online`。
315
+ - `online=false`:进入注册查询和批次构建。
316
+ - RPC 错误、超时或响应非法:不得当作离线,候选进入 `retry_wait`。
317
+
318
+ ### 8.3 无法消除的竞态
319
+
320
+ - 目标在 query 之前上线:本轮推送被抑制。
321
+ - 目标在 query 之后、dispatch 之前上线:仍可能发送一条冗余通知。
322
+ - 目标在事件已到 Push Server 后上线:通知不可撤回。
323
+ - unregister 在 batch 构建前完成:终止 delivery。
324
+ - unregister 在事件已送达后完成:不撤回已经提交的平台推送。
325
+
326
+ 这些竞态只影响通知是否冗余,不影响 AUN 消息的唯一性、顺序或 pull 可靠性。
327
+
328
+ ## 9. 持久化模型
329
+
330
+ Push Service 使用独立 MySQL 数据库 `aun_push`,不与其他模块共享表或 ORM 代码。
331
+
332
+ ### 9.1 注册表
333
+
334
+ 关键约束:
335
+
336
+ - 唯一键:`(aid, device_id, slot_id)`。
337
+ - 保存 `registration_id`、`push_server_aid`、加密 token、密钥版本、注册版本、启用状态和更新时间。
338
+ - 保存 token 指纹用于幂等判断和诊断,但不得保存可逆明文或直接记录 token。
339
+ - token 永久失效只禁用与 delivery 快照版本一致的注册。
340
+
341
+ ### 9.2 候选表
342
+
343
+ 关键约束:
344
+
345
+ - `candidate_id` 唯一。
346
+ - 保存目标、类型、调度时间、过期时间、状态、尝试时间和诊断原因。
347
+ - 不保存消息正文、密文或 push token。
348
+ - terminal 候选按保留周期清理。
349
+
350
+ ### 9.3 批次和 delivery 表
351
+
352
+ 批次保存:
353
+
354
+ - `batch_id`
355
+ - `push_server_aid`
356
+ - 当前 dispatch attempt
357
+ - ACK deadline
358
+ - lease owner / lease deadline
359
+ - 批次状态
360
+
361
+ delivery 保存:
362
+
363
+ - `delivery_id`
364
+ - 关联候选和 batch
365
+ - 注册 ID 与注册版本快照
366
+ - item 状态、尝试次数、下次尝试时间
367
+ - normalized error code
368
+
369
+ ### 9.4 频控状态
370
+
371
+ 按 target AID 和 Push Server 维护:
372
+
373
+ - 最后成功 dispatch 时间。
374
+ - 下一允许 dispatch 时间。
375
+ - 当前 in-flight 数。
376
+ - 窗口计数。
377
+
378
+ 频控状态可以使用数据库作为恢复真源,内存只作为单实例运行时缓存。
379
+
380
+ ## 10. 状态机与并发控制
381
+
382
+ ```text
383
+ pending → ready → dispatching → inflight → accepted
384
+ ├→ suppressed_online
385
+ ├→ no_registration
386
+ ├→ retry_wait → ready
387
+ ├→ permanent_failed
388
+ └→ expired
389
+ ```
390
+
391
+ 并发规则:
392
+
393
+ - scheduler 使用数据库租约领取任务。
394
+ - 领取、状态变更和 attempt 增加必须使用事务或条件 UPDATE。
395
+ - ACK 可以在 dispatch RPC 返回前到达,ACK 写入的终态优先。
396
+ - dispatch worker 更新状态时必须附带旧状态条件,不能覆盖已经 ACK 的状态。
397
+ - 服务迁移时旧实例停止领取新任务,未完成租约到期后由新实例恢复。
398
+ - 多实例短暂重叠允许产生重复事件,但不允许产生不同 `delivery_id`。
399
+
400
+ ### 10.1 Gateway event_id 与 delivery_id
401
+
402
+ - `delivery_id` 在所有重试中保持不变,供 Push Server 端到端去重。
403
+ - Gateway `event_id` 按 `batch_id + dispatch_attempt` 生成。
404
+ - 同一次 dispatch 因响应丢失而重试时复用相同 event_id,利用 Gateway 去重。
405
+ - ACK 超时后开始新的 attempt,生成新 event_id,使 Push Server 能重新收到事件并重新 ACK。
406
+
407
+ ## 11. 可靠性边界
408
+
409
+ ### 11.1 Message / Group 到 Push Service
410
+
411
+ 该段为有界异步 best-effort:
412
+
413
+ - 消息持久化成功后立即向发送者返回成功。
414
+ - 推送候选进入本进程有界队列,按目标 AID 合并并保留最新提示。
415
+ - Push Service 暂时不可用时按候选 TTL 有限重试。
416
+ - Message / Group 进程在 Push Service 确认接收前崩溃,该通知提示丢失,但真实消息仍可 pull。
417
+ - 队列满或任务过期必须产生计数和诊断日志,不得静默吞掉。
418
+
419
+ 不采用跨服务分布式事务,也不把 Push outbox 写入失败升级为消息发送失败。
420
+
421
+ ### 11.2 Push Service 接收之后
422
+
423
+ 该段为持久化至少一次:
424
+
425
+ - `push.enqueue` 只有在数据库 commit 后才返回 accepted。
426
+ - 服务重启、Gateway 重连和三中心节点迁移后继续处理。
427
+ - dispatch、ACK 或响应丢失会重试。
428
+ - Push Server 必须按 `delivery_id` 幂等,并在收到重复事件时重新返回 ACK。
429
+
430
+ ### 11.3 Push Server 到传统推送平台
431
+
432
+ Push Server 将 AUN 的 normalized status 映射到具体平台结果。`accepted` 只表示平台接受请求,不表示移动设备已经联网、展示或点击通知。
433
+
434
+ ## 12. 异常和超时处理
435
+
436
+ | 场景 | 确定行为 |
437
+ | --- | --- |
438
+ | Gateway Session 查询超时 | 不发送,进入退避重试 |
439
+ | Gateway 返回非法在线状态 | 记录协议错误,不推断离线 |
440
+ | 目标存在任意在线 Session | 候选终止为 `suppressed_online` |
441
+ | 目标没有有效注册 | 候选终止为 `no_registration` |
442
+ | Push Server 没有在线 Session | 保持待发,直到恢复或候选过期 |
443
+ | `gateway.dispatch_event` delivered=0 | 不进入 inflight,退避重试 |
444
+ | dispatch 成功但响应丢失 | 同 attempt 重试,Gateway 和 Push Server 双层去重 |
445
+ | ACK 在 dispatch 响应前到达 | ACK 状态优先,dispatch worker 条件更新失败即停止覆盖 |
446
+ | ACK 只包含部分 items | 已 ACK 项终结,缺失项等待 ACK timeout |
447
+ | ACK timeout | 缺失项进入下一 attempt,直到次数或 TTL 耗尽 |
448
+ | token 永久失效 | 只禁用版本匹配的注册 |
449
+ | DB 启动时不可用 | 不报告 ready,启动失败或保持初始化状态 |
450
+ | DB 运行时不可用 | 暂停 scheduler,RPC 返回可重试错误,健康状态 degraded |
451
+ | Gateway 启动时不可用 | 状态为 `waiting_gateway`,不得伪装 ready |
452
+ | 发现多个 Gateway 实例 | 状态为 `blocked_gateway_multi_instance`,停止 Push dispatch |
453
+ | Push Service 正在 drain | 停止接收新业务和领取新任务,允许 ACK 与已进入处理的事务完成 |
454
+
455
+ ## 13. 安全设计
456
+
457
+ ### 13.1 Push Server 白名单
458
+
459
+ - 配置中心维护 `allowed_push_server_aids`。
460
+ - 白名单为空时 Push 功能处于禁用状态,不允许注册。
461
+ - `push_server_aid` 必须属于当前 issuer domain。
462
+ - 白名单移除某 AID 后,其现有注册被禁用,未发送 delivery 终止。
463
+
464
+ ### 13.2 token 加密
465
+
466
+ - 使用 AES-256-GCM。
467
+ - 配置中心通过 secret 或环境变量注入密钥环和 active key ID。
468
+ - 数据库保存 key ID、nonce、ciphertext 和认证 tag。
469
+ - 新注册使用 active key;旧 key 保留解密能力,支持逐步轮换。
470
+ - token 明文只在注册处理和事件构建的短生命周期内存在。
471
+
472
+ ### 13.3 日志脱敏
473
+
474
+ 日志中禁止出现:
475
+
476
+ - `push_token`
477
+ - 数据库 ciphertext
478
+ - AUN 登录 token
479
+ - 私钥或证书完整内容
480
+ - 消息正文或 E2EE payload
481
+
482
+ 允许记录:
483
+
484
+ - `candidate_id`
485
+ - `batch_id`
486
+ - `delivery_id`
487
+ - 注册版本
488
+ - token 指纹的短前缀
489
+ - AID 的安全摘要或按现有诊断规范允许的 AID
490
+
491
+ ### 13.4 ACK 授权
492
+
493
+ - ACK 使用 `_auth.aid`,不接受请求体自报的 Push Server AID。
494
+ - ACK AID 必须与 batch 所有者一致。
495
+ - delivery 必须属于指定 batch。
496
+ - Push Server 只能影响自身 batch 和注册版本快照。
497
+
498
+ ## 14. 推荐配置
499
+
500
+ 以下为默认运行参数,不属于不可变协议:
501
+
502
+ | 参数 | 默认值 | 说明 |
503
+ | --- | ---: | --- |
504
+ | `offline_grace_ms` | 2000 | 给在线事件和快速重连留出时间 |
505
+ | `aggregate_window_ms` | 5000 | 同一目标聚合窗口 |
506
+ | `target_cooldown_seconds` | 60 | 同一 AID 的最小通知间隔 |
507
+ | `candidate_ttl_seconds` | 600 | 过时通知不再发送 |
508
+ | `ack_timeout_seconds` | 30 | Push Server ACK 等待时间 |
509
+ | `max_delivery_attempts` | 5 | delivery 最大尝试次数 |
510
+ | `batch_size` | 100 | 单批最大 delivery 数 |
511
+ | `max_event_bytes` | 524288 | 单事件最大字节数 |
512
+ | `max_inflight_per_server` | 4 | 每 Push Server 并行批次 |
513
+ | `registration_ttl_days` | 90 | 长期未刷新注册自动失效 |
514
+ | `terminal_retention_days` | 7 | 终态诊断数据保留时间 |
515
+ | `rate_limit_per_server_per_minute` | 1000 | 单 Push Server 保护 |
516
+ | `rate_limit_global_per_minute` | 5000 | 服务整体保护 |
517
+
518
+ ## 15. 三中心纳管方案
519
+
520
+ ### 15.1 服务形态
521
+
522
+ - 服务名:`push`
523
+ - 目录:`extensions/services/push/`
524
+ - runtime:`binary`
525
+ - binary:`./go/bin/push-go`
526
+ - 默认状态:enabled,由 managed desired-state 最终决定
527
+ - 路由:Gateway service-plane `round_robin`,控制面限制集群单实例
528
+ - 数据库:`aun_push`
529
+
530
+ Push Service 只实现 Go 版本,不创建 `module.py.md` 或 Python Push Service。
531
+
532
+ ### 15.2 控制中心
533
+
534
+ - 加入 managed service 集合。
535
+ - 默认 `single_instance_required=true`。
536
+ - 加入 protected single-instance 集合,V1 不允许管理员切换为 multi-active。
537
+ - 按 `node_id + slot_id` desired-state 启停。
538
+ - 节点迁移使用现有单实例停止、等待、再启动流程;数据库租约负责恢复未完成任务。
539
+
540
+ ### 15.3 注册中心
541
+
542
+ - Push Service 通过注册中心发现唯一 Gateway 的 `business_ws`。
543
+ - 不依赖 Push 所在节点的本机 Gateway。
544
+ - 服务注册 actual state、健康状态、能力和可选 `diagnostics_http`。
545
+ - Gateway 不存在时报告 `waiting_gateway`。
546
+ - Gateway 数量大于 1 时报告 `blocked_gateway_multi_instance`。
547
+
548
+ ### 15.4 配置中心
549
+
550
+ 配置通过 `AUN_RESOLVED_CONFIG_JSON` 覆盖本地默认值,至少包含:
551
+
552
+ - `db.*`
553
+ - `enabled`
554
+ - `allowed_push_server_aids`
555
+ - token 密钥环和 active key ID
556
+ - 聚合、频控、TTL、ACK 和重试参数
557
+ - 诊断 HTTP 参数
558
+
559
+ 配置变更沿用现有 config revision 和 managed restart,不新增热更新协议。
560
+
561
+ ### 15.5 生命周期
562
+
563
+ 启动顺序:
564
+
565
+ 1. 读取 Launcher `boot_info`。
566
+ 2. 注册 Kernel 控制面能力。
567
+ 3. 解析 resolved config 并校验安全参数。
568
+ 4. 连接数据库并执行幂等 migration。
569
+ 5. 从注册中心发现 Gateway。
570
+ 6. 验证当前只有一个活动 Gateway。
571
+ 7. 连接 `/ws/service` 并完成 `service.attach`。
572
+ 8. 启动 RPC dispatcher、scheduler 和 diagnostics。
573
+ 9. 满足所有条件后发送 `module.ready`。
574
+
575
+ 停止顺序:
576
+
577
+ 1. 标记 draining,停止领取新候选和新 delivery。
578
+ 2. 调用 `service.drain` 使 Gateway 不再路由新 RPC。
579
+ 3. 等待当前 RPC、ACK 和数据库事务结束。
580
+ 4. 释放或缩短未完成租约。
581
+ 5. 停止 scheduler 和 diagnostics。
582
+ 6. 断开 Gateway、Kernel 和数据库。
583
+ 7. 上报 stop_ready。
584
+
585
+ ## 16. Gateway 改造
586
+
587
+ Go Gateway 与 Python Gateway 必须同步修改,避免默认 Go 与显式 Python 回退产生协议差异。
588
+
589
+ 改动范围:
590
+
591
+ - 将 `push` 加入业务 namespace。
592
+ - 将 `push` 加入客户端默认直连 namespace。
593
+ - 将 `push` 纳入通用业务背压和指标分类。
594
+ - 允许 Push Service 调用 `gateway.query_sessions`。
595
+ - 继续允许 Push Service 调用 `gateway.dispatch_event`、`gateway.health`、`gateway.status`。
596
+ - 允许 `source_module=push` 发布 `push.offline_message`。
597
+ - 校验 Push Service attach 声明了所调用的 Gateway methods。
598
+ - 增加 Go/Python Gateway 契约对齐测试。
599
+
600
+ Gateway 不增加 push 数据表、不解释 ACK、不解析 token、不实现 scheduler。
601
+
602
+ ## 17. Message / Group 接入
603
+
604
+ ### 17.1 接入点
605
+
606
+ 候选只能在消息已经形成可靠事实后产生:
607
+
608
+ - P2P:目标域 Message 持久化成功后。
609
+ - Group:Group 消息持久化并确定本域成员后。
610
+ - Federation Group:目标域 relay 已通过可信 federation 校验后。
611
+
612
+ 验证失败、权限失败、限流拒绝、数据库回滚和 WAL 失败均不得产生候选。
613
+
614
+ ### 17.2 异步候选队列
615
+
616
+ Message 和 Group 各自独立实现,不共享代码:
617
+
618
+ - 有界容量。
619
+ - 按 target AID 合并,保留最新唤醒提示。
620
+ - 使用确定性 `candidate_id`。
621
+ - Push Service 不可用时指数退避。
622
+ - 超过候选 TTL 后终止。
623
+ - queue depth、drop、retry、accepted 和 expired 均有指标。
624
+ - 关闭时只做有界 drain,不阻塞服务无限退出。
625
+
626
+ ### 17.3 服务端 runtime 一致性
627
+
628
+ Push Service 本身只实现 Go。Message / Group 的生产 Go runtime 必须接入候选;Python 服务端回退也应增加同一候选 adapter,防止切换 runtime 后离线推送功能无声消失。Python adapter 不是 Python Push Service。
629
+
630
+ ## 18. Python SDK 改造
631
+
632
+ ### 18.1 API
633
+
634
+ 新增 `PushFacade`:
635
+
636
+ - `client.push.register(...)`
637
+ - `client.push.unregister(...)`
638
+ - `client.push.ack(...)`
639
+
640
+ facade 只做参数整理和 `push.*` RPC 调用,不签发或解释 push token。
641
+
642
+ ### 18.2 事件
643
+
644
+ Python transport 会把非 `app.*` 协议事件发布为 `_raw.<event>`。客户端增加内部订阅:
645
+
646
+ ```text
647
+ _raw.push.offline_message → push.offline_message
648
+ ```
649
+
650
+ Push Server 应通过公共事件注册回调。事件回调处理和 ACK 不能阻塞 transport WebSocket reader;必须沿用异步 dispatcher。
651
+
652
+ ### 18.3 其他 SDK
653
+
654
+ Go、TypeScript 和 JavaScript SDK 本阶段不增加 facade、事件别名或测试。协议文档为仓库外 Rust SDK 提供实现依据。
655
+
656
+ ## 19. Python Mock Push Server
657
+
658
+ Mock Push Server 是普通 AUN 客户端,不是三中心 managed service。
659
+
660
+ ### 19.1 功能
661
+
662
+ - 使用固定 Push Server AID 连接目标 Gateway。
663
+ - 订阅 `push.offline_message`。
664
+ - 提供测试 HTTP 接口签发 HMAC mock token。
665
+ - 验证 token 中的 target AID、device ID、slot ID 和签名。
666
+ - 按 `delivery_id` 持久化去重。
667
+ - 记录不含 token 的通知 JSONL。
668
+ - 调用 `client.push.ack`。
669
+
670
+ ### 19.2 故障注入
671
+
672
+ HTTP 控制面支持:
673
+
674
+ - ACK 延迟。
675
+ - 完全丢弃 ACK。
676
+ - 指定 delivery 临时失败。
677
+ - 指定 delivery 永久失败。
678
+ - invalid token。
679
+ - partial ACK。
680
+ - 主动断开 AUN 连接。
681
+ - 模拟重复事件。
682
+
683
+ ### 19.3 测试数据保护
684
+
685
+ - Mock AID 使用独立固定 identity 目录。
686
+ - 不得在测试脚本中自动执行 `setup_aids.py`。
687
+ - 不得删除或重建既有 key、cert、key.json 或本地数据库。`key.json` 的解密 seed 在构造 `AIDStore`(或对应语言的 SDK 身份存储)时显式传入;身份目录不保存 seed,也不从 seed 文件恢复。
688
+ - 首次需要新增 Mock AID 时必须获得明确授权后一次性创建。
689
+ - Mock 只进入 Tester Compose,不进入生产 Compose。
690
+
691
+ ## 20. 可观测性
692
+
693
+ ### 20.1 状态
694
+
695
+ Push Service 状态至少包括:
696
+
697
+ - `initializing`
698
+ - `waiting_database`
699
+ - `waiting_gateway`
700
+ - `blocked_gateway_multi_instance`
701
+ - `ready`
702
+ - `degraded`
703
+ - `draining`
704
+
705
+ ### 20.2 指标
706
+
707
+ 至少提供:
708
+
709
+ - 注册总数、启用数、过期数。
710
+ - candidates accepted、deduped、rejected、expired。
711
+ - online suppressed、no registration。
712
+ - ready、dispatching、inflight、retry、permanent failed 数量。
713
+ - backlog depth 和 oldest age。
714
+ - Session query timeout/error。
715
+ - dispatch delivered/deduped/failed。
716
+ - ACK accepted、partial、duplicate、timeout、unauthorized。
717
+ - 按 Push Server 的 in-flight、速率和最近在线时间。
718
+ - token invalid 自动禁用次数。
719
+
720
+ ### 20.3 诊断日志
721
+
722
+ 每个状态转换记录:
723
+
724
+ - trace ID。
725
+ - candidate / batch / delivery ID。
726
+ - 旧状态、新状态和原因。
727
+ - attempt、next retry 和 elapsed time。
728
+ - Gateway RPC 方法、结果类别和错误码。
729
+
730
+ 不得用 `except Exception: pass` 或等价方式静默吞异常。降级路径至少记录带上下文的 warning/error 和计数。
731
+
732
+ ## 21. 详细实施计划
733
+
734
+ 所有阶段使用 TDD:先建立失败测试,再实现最小功能,最后重构和运行受影响回归。不得先写完整实现再补测试。
735
+
736
+ ### Phase 0:协议与契约冻结
737
+
738
+ 目标:先把跨组件契约变成可执行测试,避免各模块自行理解字段。
739
+
740
+ 任务:
741
+
742
+ 1. 建立 `push.register/unregister/enqueue/ack` 请求和响应 fixtures。
743
+ 2. 建立 `event/push.offline_message` fixtures。
744
+ 3. 建立错误、部分 ACK、重复 ACK、token 失效 fixtures。
745
+ 4. 建立敏感字段扫描测试。
746
+ 5. 建立 Go Gateway、Python Gateway、Go Push、Python SDK 的契约加载测试。
747
+
748
+ 出口:
749
+
750
+ - 所有新契约测试先红。
751
+ - 字段、状态、幂等键和 normalized error 不再存在开放项。
752
+
753
+ ### Phase 1:Go Push Service 骨架与持久化
754
+
755
+ 目标:不接真实 Gateway,完成可单测的核心状态机。
756
+
757
+ 任务:
758
+
759
+ 1. 创建 `extensions/services/push/module.md` 和独立 Go module。
760
+ 2. 实现 boot_info、配置解析、日志和状态模型。
761
+ 3. 实现 MySQL schema 与幂等 migration。
762
+ 4. 实现注册、加密、版本更新和注销 repository。
763
+ 5. 实现 candidate 幂等入库。
764
+ 6. 实现 batch/delivery/lease 状态机。
765
+ 7. 使用 fake clock、fake Gateway 和内存 repository 测试 scheduler。
766
+ 8. 实现 token 日志脱敏测试。
767
+
768
+ 重点竞态测试:
769
+
770
+ - 并发 register 版本递增。
771
+ - unregister 与 batch 构建并发。
772
+ - ACK 先于 dispatch 响应。
773
+ - lease 过期恢复。
774
+ - 旧 token invalid ACK 与新注册并发。
775
+
776
+ 出口:
777
+
778
+ - 核心单元测试通过。
779
+ - race test 通过。
780
+ - 数据库 migration 可重复执行。
781
+
782
+ ### Phase 2:Gateway 路由与 ACL
783
+
784
+ 目标:客户端和服务均能通过现有 Gateway business plane 到达 Push Service。
785
+
786
+ 任务:
787
+
788
+ 1. Go Gateway 增加 namespace、直连、背压、ACL 和事件白名单测试。
789
+ 2. 实现 Go Gateway 最小修改。
790
+ 3. Python Gateway 增加同构测试和实现。
791
+ 4. 测试客户端不能伪造 `_caller_id`。
792
+ 5. 测试 Push Service 未声明 method 时 Gateway 拒绝调用。
793
+ 6. 测试 Push 事件只能由 attached module `push` 发布。
794
+ 7. 测试定向 connection ID 投递和 delivered=0。
795
+
796
+ 出口:
797
+
798
+ - Go/Python Gateway 契约一致。
799
+ - 现有 Message/Group/Storage/Service Proxy 路由回归通过。
800
+
801
+ ### Phase 3:Push Service 垂直闭环
802
+
803
+ 目标:通过真实 Gateway 完成注册、离线检查、事件和 ACK。
804
+
805
+ 任务:
806
+
807
+ 1. 实现 Gateway discovery,验证活动 Gateway 数量。
808
+ 2. 实现 `service.attach`、heartbeat、reconnect 和 drain。
809
+ 3. 实现客户端 RPC dispatcher。
810
+ 4. 实现 `gateway.query_sessions` 调用和保守失败策略。
811
+ 5. 实现 Push Server Session 选择。
812
+ 6. 实现 event dispatch、attempt event ID 和 ACK timeout。
813
+ 7. 实现并发 reader/pending response demux,避免 ACK 与 outgoing RPC 死锁。
814
+ 8. 实现 private diagnostics HTTP。
815
+
816
+ 出口:
817
+
818
+ - 进程内 fake Gateway E2E 通过。
819
+ - Gateway 断开重连后 backlog 继续处理。
820
+ - shutdown/drain 无未关闭 goroutine 和悬挂租约。
821
+
822
+ ### Phase 4:Message / Group 候选接入
823
+
824
+ 目标:不改变消息主链结果的前提下产生正确候选。
825
+
826
+ 任务:
827
+
828
+ 1. 为 P2P V1/V2 写持久化成功/失败候选测试。
829
+ 2. 为 Group V1/V2 写本域成员 fanout 测试。
830
+ 3. 为 P2P federation 写“源域不 enqueue、目标域 enqueue”测试。
831
+ 4. 为 Group relay 写目标域成员 enqueue 和重放去重测试。
832
+ 5. 实现 Go Message / Group 异步候选队列。
833
+ 6. 实现 Python Message / Group 回退 adapter。
834
+ 7. 增加队列满、Push Service 不可用、退出 drain 和 TTL 测试。
835
+
836
+ 出口:
837
+
838
+ - `message.send/group.send` 在 Push Service 不可用时仍按原契约成功。
839
+ - 数据库回滚、权限失败和 validation error 不产生候选。
840
+ - 每个目标每条逻辑消息只产生一个确定性 candidate。
841
+
842
+ ### Phase 5:Python SDK 与 Mock Push Server
843
+
844
+ 目标:形成可自动验证的端到端测试工具。
845
+
846
+ 任务:
847
+
848
+ 1. 先写 `PushFacade` API 单测。
849
+ 2. 写 `_raw.push.offline_message` 到公共事件的测试。
850
+ 3. 实现 facade 和 `client.push`。
851
+ 4. 实现 Mock token HTTP API。
852
+ 5. 实现事件处理、delivery 去重和 ACK。
853
+ 6. 实现故障注入和通知查询接口。
854
+ 7. 验证事件回调中调用 ACK 不阻塞 transport reader。
855
+
856
+ 出口:
857
+
858
+ - Python SDK 单测通过。
859
+ - Mock 的正常、partial、timeout、invalid token 和重复事件测试通过。
860
+
861
+ ### Phase 6:三中心纳管与 Docker
862
+
863
+ 目标:Push Service 能在默认、三节点、双域和线上镜像中被一致管理。
864
+
865
+ 任务:
866
+
867
+ 1. aun-console 加入 managed service、显示名称、business RPC 和配置 schema。
868
+ 2. 将 Push 设为 protected single-instance。
869
+ 3. Launcher 增加 Push 的 AUN drain 和 diagnostics credential 注入。
870
+ 4. Dockerfile 编译、复制并授权 `push-go`。
871
+ 5. `init.sql` 增加 `aun_push`。
872
+ 6. 默认 Compose、分布式 Compose、federation Compose 增加安全配置和 desired-state。
873
+ 7. 更新默认 Go manifest 测试,明确 Push 是 Go-only 服务。
874
+ 8. 更新服务端公网验收和部署材料校验列表。
875
+
876
+ 出口:
877
+
878
+ - Compose config 静态检查通过。
879
+ - 镜像内存在可执行 `push-go`。
880
+ - aun-console 能展示、启停、迁移和读取 Push 状态。
881
+
882
+ ### Phase 7:单实例集成测试
883
+
884
+ 测试矩阵:
885
+
886
+ 1. 注册、重复注册、token 更新、注销。
887
+ 2. P2P V1 离线通知。
888
+ 3. P2P V2 离线通知。
889
+ 4. Group V1/V2 离线通知。
890
+ 5. 目标在线不通知。
891
+ 6. 多设备中任意设备在线不通知。
892
+ 7. 全部离线时为每个有效注册产生 delivery。
893
+ 8. 目标在聚合窗口内上线。
894
+ 9. Push Server 离线后恢复。
895
+ 10. partial ACK、ACK timeout 和 duplicate ACK。
896
+ 11. invalid token 禁用与重新注册。
897
+ 12. Push Service 重启恢复。
898
+ 13. Gateway 重启恢复。
899
+ 14. 通知 payload 无消息正文和密文。
900
+ 15. 目标重连后能 pull 到真实消息。
901
+
902
+ 每轮必须收集 Gateway、Push、Message/Group、Mock 和 Python SDK 日志,按 candidate/batch/delivery ID 串起调用链。
903
+
904
+ ### Phase 8:三节点与双域测试
905
+
906
+ 三节点:
907
+
908
+ - Push desired-state 只在一个 node/slot 运行。
909
+ - Push、Gateway、Message/Group 位于不同节点时链路正常。
910
+ - 迁移 Push 节点后 backlog 继续处理。
911
+ - Gateway 未启动时 Push 不进入 ready。
912
+ - 多 Gateway 配置下 Push 明确阻断 dispatch。
913
+
914
+ 双域:
915
+
916
+ - A 域发往 B 域的 P2P 只由 B 域 Push Server 收到。
917
+ - B 域发往 A 域同理。
918
+ - 跨域 Group 的每个域只处理本域成员。
919
+ - 源域无法读取或收到目标域 token。
920
+ - federation relay 重放不产生重复 delivery。
921
+
922
+ ### Phase 9:公网与负载验收
923
+
924
+ 公网:
925
+
926
+ - Python Mock 使用测试 AID 连接公网 WSS。
927
+ - 全链路启用 TLS 校验。
928
+ - P2P、Group、注册、ACK 和重连均只通过公网协议完成。
929
+ - 不读取远端 MySQL,不操作远端 Docker,不重启远端服务。
930
+
931
+ 负载与安全:
932
+
933
+ - 大群 fanout 分批和公平性。
934
+ - 慢 Push Server 不阻塞其他 Push Server。
935
+ - backlog 上限、老化和清理。
936
+ - 数据库连接耗尽和恢复。
937
+ - Gateway query/dispatch 超时风暴。
938
+ - token 和消息内容日志扫描。
939
+ - Go race、vet、gofmt 和跨平台构建。
940
+
941
+ ### Phase 10:文档、灰度和回滚
942
+
943
+ 文档:
944
+
945
+ - 重写 `docs/protocol/15-离线推送通知协议.md`。
946
+ - 更新 Python SDK API/RPC 手册。
947
+ - 更新默认、分布式和公网测试指南。
948
+ - 同步根级和 SDK 文档索引。
949
+
950
+ 灰度顺序:
951
+
952
+ 1. 先部署 Gateway namespace、ACL 和事件兼容层。
953
+ 2. 部署 Push Service,保持 `enabled=false` 或白名单为空。
954
+ 3. 验证数据库、三中心纳管、status 和 diagnostics。
955
+ 4. 配置 Mock Push Server 白名单,验证注册和手工 enqueue。
956
+ 5. 启用 Message 候选。
957
+ 6. 启用 Group 候选。
958
+ 7. 观察 backlog、ACK、timeout 和 token invalid 指标。
959
+ 8. 再加入真实 Push Server AID。
960
+
961
+ 回滚:
962
+
963
+ - 关闭 Message / Group 的 `offline_push_enabled`。
964
+ - 通过 desired-state 停止 Push Service。
965
+ - 保留 `aun_push` 数据库、注册、固定身份和加密密钥。
966
+ - Gateway namespace/ACL 和 Python SDK facade 均为加法变更,无需删除。
967
+ - 严禁通过清库、删除身份或重建 AID 完成回滚。
968
+
969
+ ## 22. 实施审批点
970
+
971
+ 正式编码前需要明确授权:
972
+
973
+ 1. 新增 `push.register`、`push.unregister`、`push.enqueue`、`push.ack`。
974
+ 2. 新增 `event/push.offline_message`。
975
+ 3. 修改 Go/Python Gateway 的业务 namespace、路由和 ACL。
976
+ 4. 修改 Launcher 的 AUN drain 与 diagnostics 服务集合。
977
+ 5. 首次创建 Mock Push Server 固定 AID 身份材料。
978
+
979
+ 上述授权不扩大到删除身份、清理数据库、重写 Git 历史或启动 Kite 测试环境。
980
+
981
+ ## 23. 关键源码与文档索引
982
+
983
+ - 历史协议:`docs/protocol/15-离线推送通知协议.md`
984
+ - 默认测试指南:`docs/aun测试运行指南.md`
985
+ - 分布式测试指南:`docs/aun分布式测试运行指南.md`
986
+ - 公网测试指南:`docs/aun公网测试运行指南.md`
987
+ - Go Gateway:`../extensions/services/gateway/go/internal/httpserver/server.go`
988
+ - Python Gateway:`../extensions/services/gateway/service_plane.py`、`../extensions/services/gateway/ws_server.py`
989
+ - Message Go:`../extensions/services/message/go/`
990
+ - Group Go:`../extensions/services/group/go/`
991
+ - aun-console:`../extensions/services/aun_console/`
992
+ - Launcher:`../launcher/entry.py`、`../launcher/process_manager.py`
993
+ - Python SDK:`python/src/aun_core/client.py`、`python/src/aun_core/facades.py`、`python/src/aun_core/transport.py`
994
+ - Docker:`../docker-deploy/Dockerfile`、`../docker-deploy/docker-compose.yml`、`../docker-deploy/docker-compose.distributed.yml`