@agentunion/fastaun 0.5.34 → 0.5.35

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 (78) hide show
  1. package/CHANGELOG.md +30 -1
  2. package/_packed_docs/CHANGELOG.md +30 -1
  3. package/_packed_docs/INDEX.md +11 -11
  4. package/_packed_docs/KITE_DOCS_GUIDE.md +9 -9
  5. package/_packed_docs/agent.md/SCHEMA.md +1 -1
  6. package/_packed_docs/aun/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md +2 -2
  7. package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2112.1.md +17 -17
  8. package/_packed_docs/protocol/echo-probe-gateway.md +99 -0
  9. package/_packed_docs/protocol/echo-probe-sdk.md +91 -0
  10. package/_packed_docs/sdk/0.5.35-SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +189 -0
  11. package/_packed_docs/sdk/0.5.35-SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +204 -0
  12. package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +2 -2
  13. package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +32 -6
  14. package/_packed_docs/sdk/09-group-rpc-manual.md +7 -7
  15. package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +13 -13
  16. package/_packed_docs/sdk/AUN/344/272/213/344/273/266/345/217/221/345/270/203/350/256/242/351/230/205/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +73 -3
  17. package/_packed_docs/sdk/AUN/344/272/213/344/273/266/345/217/221/345/270/203/350/256/242/351/230/205/345/274/200/345/217/221/347/247/273/346/244/215/346/214/207/345/215/227.md +50 -8
  18. package/_packed_docs/sdk/INDEX.md +18 -18
  19. package/_packed_docs/sdk/Ownership-SDK/347/274/226/346/216/222/346/214/207/345/215/2272.1.md +1 -1
  20. 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 +5 -5
  21. 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 +5 -5
  22. package/_packed_docs/sdk/Ownership2.1/350/257/246/347/273/206/350/256/276/350/256/241.md +9 -9
  23. package/_packed_docs//346/266/210/346/201/257/345/274/271/346/200/247/350/241/245/346/213/211/351/200/232/351/201/223/345/256/236/346/226/275/350/256/241/345/210/222.md +400 -0
  24. package/_packed_docs//346/266/210/346/201/257/345/274/271/346/200/247/350/241/245/346/213/211/351/200/232/351/201/223/346/226/271/346/241/210.md +485 -0
  25. package/dist/auth.js +31 -43
  26. package/dist/auth.js.map +1 -1
  27. package/dist/client/delivery.d.ts +7 -1
  28. package/dist/client/delivery.js +137 -16
  29. package/dist/client/delivery.js.map +1 -1
  30. package/dist/client/lifecycle.js +10 -1
  31. package/dist/client/lifecycle.js.map +1 -1
  32. package/dist/client/rpc-pipeline.d.ts +2 -0
  33. package/dist/client/rpc-pipeline.js +65 -7
  34. package/dist/client/rpc-pipeline.js.map +1 -1
  35. package/dist/client/sync-channels.d.ts +2 -1
  36. package/dist/client/sync-channels.js +33 -14
  37. package/dist/client/sync-channels.js.map +1 -1
  38. package/dist/client/v2-e2ee.d.ts +1 -0
  39. package/dist/client/v2-e2ee.js +19 -5
  40. package/dist/client/v2-e2ee.js.map +1 -1
  41. package/dist/client.d.ts +15 -0
  42. package/dist/client.js +310 -49
  43. package/dist/client.js.map +1 -1
  44. package/dist/events.d.ts +1 -0
  45. package/dist/events.js +5 -0
  46. package/dist/events.js.map +1 -1
  47. package/dist/index.d.ts +2 -0
  48. package/dist/index.js +1 -0
  49. package/dist/index.js.map +1 -1
  50. package/dist/mail-client.d.ts +101 -0
  51. package/dist/mail-client.js +369 -0
  52. package/dist/mail-client.js.map +1 -0
  53. package/dist/pubsub/binding.js +9 -2
  54. package/dist/pubsub/binding.js.map +1 -1
  55. package/dist/pubsub/channel.d.ts +11 -3
  56. package/dist/pubsub/channel.js +88 -7
  57. package/dist/pubsub/channel.js.map +1 -1
  58. package/dist/pubsub/client.d.ts +8 -2
  59. package/dist/pubsub/client.js +59 -17
  60. package/dist/pubsub/client.js.map +1 -1
  61. package/dist/pubsub/index.d.ts +2 -2
  62. package/dist/pubsub/index.js +1 -1
  63. package/dist/pubsub/index.js.map +1 -1
  64. package/dist/pubsub/service.d.ts +6 -0
  65. package/dist/pubsub/service.js +100 -9
  66. package/dist/pubsub/service.js.map +1 -1
  67. package/dist/pubsub/types.d.ts +11 -1
  68. package/dist/tools/cross-sdk-agent.d.ts +1 -1
  69. package/dist/transport.d.ts +4 -0
  70. package/dist/transport.js +39 -2
  71. package/dist/transport.js.map +1 -1
  72. package/dist/udp-standby.d.ts +89 -0
  73. package/dist/udp-standby.js +487 -0
  74. package/dist/udp-standby.js.map +1 -0
  75. package/dist/version.d.ts +1 -1
  76. package/dist/version.js +1 -1
  77. package/package.json +5 -1
  78. package/scripts/patch-imapflow-external.mjs +69 -0
@@ -0,0 +1,91 @@
1
+ # echo 探针协议 — SDK 实现规范
2
+
3
+ **状态**:v3.2,单 hop 全程递减规范
4
+ **日期**:2026-11-11
5
+ **适用**:本项目 fastaun SDK(Python / Go / TS / JS)
6
+ **配套文档**:[`echo-probe-gateway.md`](./echo-probe-gateway.md)
7
+
8
+ ## 1. 范围与节点
9
+
10
+ SDK 负责 `AUN-SDK.send`、`AUN-SDK.receive`。外部 Rust SDK 可采用 `AUN-RustSDK.send/receive`;其位置为 `D:/modelunion/acp-app`,与 App、EvolCore 一样不在本次代码修改范围内。
11
+
12
+ SDK、Gateway、Message、Group 每次实际处理 echo 都记录自己的节点。同名节点再次经过仍附行,不能按节点名或方向去重。业务内部阶段不额外消耗网络跳数。
13
+
14
+ ## 2. 前缀与正文
15
+
16
+ 只检查第一行,先移除该行前导空白,再按最长前缀、大小写不敏感识别。
17
+
18
+ | 前缀 | 后继边界 | SDK 行为 |
19
+ |---|---|---|
20
+ | `echo.ecdiag` | 空白或行尾 | 明文、计数、附行;允许网络节点记录 |
21
+ | `echo.ask` | 空白或行尾 | 与 echo 相同,业务端决定是否经过 agent |
22
+ | `echo` | 空白、`[` 或行尾 | 明文、计数、附行 |
23
+
24
+ ` echo hop=5`、`ECHO`、`echo[tag]` 均命中;`hello echo`、`echoes`、`echo.path` 不命中。正文和首行长度不参与识别。
25
+
26
+ 命中后只移除原始第一行的前导空白,保留其余首行内容、参数、正文和历史 trace 的顺序。所有新增 trace 从第二行开始,以换行追加。普通消息不因本规范改变正文。
27
+
28
+ 不做 trace 去重,不设 echo 专用字数或字节限制,不截断、不添加 `[TRUNCATED]`。通用传输大小限制仍由原传输层处理,失败必须如实报告,不能静默丢掉正文或 trace。
29
+
30
+ ## 3. Payload 与方向
31
+
32
+ | 字段 | 约定 |
33
+ |---|---|
34
+ | `type` | 固定 `text` |
35
+ | `text` | 第一行为原 echo,按第 2 节移除前导空白 |
36
+ | `hop` | 唯一跳数字段;去程、回程均递减,负数表示提前返程 |
37
+ | `dir` | 方向标记 `fwd` / `ret`,不是第二个跳数;缺省 fwd |
38
+
39
+ 删除旧诊断字段 `max_hop`、`rhop`,不再生成或使用。普通消息不自动携带 echo 的诊断字段。
40
+
41
+ 有效 `payload.hop` 优先于首行 `hop=N`;两者都没有时默认 **100**。四 SDK 和服务统一使用 JSON 安全整数范围 `[-9007199254740991, 9007199254740991]`,首行允许 `+` / `-` 符号;布尔值、字符串型 payload.hop 和非整数无效,按首行参数或默认值处理。整数超出范围或递减结果超出范围时必须明确报错,不能回退 100。首次发起用非负预算,负数表示已经提前返程,不能重置或归零。正文历史 trace 不作为控制字段的权威来源。
42
+
43
+ hop 为负数时必须按返程处理,即使旧方向字段仍为 fwd;其余情况优先读取有效 `payload.dir`,没有有效方向时兼容正文独立行 `[AUN-Echo.return]`。非负 hop 的显式 `dir=fwd` 优先于旧标记。不能只检查正文最后一行决定方向。
44
+
45
+ ## 4. 去程与回程
46
+
47
+ 每次去程处理:读取 hop,计算 `hop = max(hop - 1, 0)`,用所得值写 `dir=fwd` trace 并传递同一值。**减到 0 就必须停止前送,立即提前返程**;收到 hop=0 也立即返程,不减为负数。
48
+
49
+ 反射节点将 `dir` 改为 `ret`,继续把 hop 减 1,追加自己的回程行,然后返回完整正文。因此到 0 提前返程时,反射节点写 -1,上游依次写 -2、-3。正常业务终点尚未耗尽时可用 dir=ret 进入回程,每个回程节点同样先减 1 再附行。
50
+
51
+ 示例:初始 hop=3,SDK 去程写 2,Gateway 写 1,Message 写 0;Message 停止前送,回程写 -1,Gateway 写 -2,SDK 写 -3,最终交付应用。
52
+
53
+ 回程不再按去程预算停止,不因 hop=0 或负数丢弃结果,也不重新反射;已经在正常回程时跨过 0 仍继续递减。每个实际处理边界只执行一次计数;终点分别记录去程、回程。接收 SDK 自动回送时,不得因调用内部发送入口而重复记录同一个接收节点。
54
+
55
+ ## 5. 明文约束
56
+
57
+ 不支持加密 echo。SDK 必须在加密路由之前识别三种前缀,强制 `encrypt=false`,发送明文 `payload.type=text`,trace 固定 `enc=0`。
58
+
59
+ 收到仍为密文的消息时按普通消息处理,不解开密文寻找 echo,不修改密文。不能把仅端节点有行的加密消息当作本协议的成功验收。
60
+
61
+ ## 6. 提前回送与应用交付
62
+
63
+ SDK 本地去程减到 0 时不发网络请求,直接生成 `status=echo_return` 响应,包含完整 payload、message_id 和适用的 from/to/group_id,并追加本地回程行。
64
+
65
+ SDK 收到服务端 `echo_return` 时,先追加 `AUN-SDK.receive` 回程行,再通过现有 `message.received` 或 `group.message_created` 应用入口交付。本地提前返回同样使用 receive 回程行;不能只把回包作为 send 状态返回。
66
+
67
+ 接收 SDK 去程到 0 时停止交给业务端处理,自动明文回送原发送者。群聊回送定向原发送者,保留 group_id 诊断上下文,不再次广播全群。该定向发送是新的异步请求,不能据此宣称返回经过原同步调用栈或同一物理 Gateway。
68
+
69
+ 沿现有同步 RPC 原调用链返回时,完整响应不得被投递统计、空成功结果、业务去重或异步队列覆盖。投递不可继续时应完整提前回送或如实报错,不能把零投递、仅唤醒或写入失败报告为 sent。普通消息的持久化与投递策略保持原有约定。
70
+
71
+ ## 7. Trace 与处理耗时
72
+
73
+ `HH:MM:SS.mmm [node] dir=fwd|ret hop=N enc=0 ver=VERSION epoch=MS tz=±HH:MM [duration_ms=N] <节点字段>`
74
+
75
+ 每行必须包含墙钟前缀、节点名、dir、hop、enc、ver、epoch、tz;毫秒墙钟之后紧跟空格。ver 取包版本,无法取得时为 unknown。SDK 自定义字段保留 aid、conn_uptime。
76
+
77
+ 每条回程行必须包含本节点真实 `duration_ms`。同步获得 echo_return 时,用本地单调时钟测量从该请求入口到形成回程结果的时间,包含等待下游;新的定向返程请求没有原去程上下文,测从本节点本次请求或接收入口到向下一节点交接正文的处理时间。不得固定填 0,也不得用跨机器 epoch 相减代替。
78
+
79
+ 仅追加实际发生的节点记录,不删除历史行。RPC 级 `_trace`、`setTraceMode` 与正文探针相互独立。
80
+
81
+ ## 8. 实现入口与验收
82
+
83
+ | SDK | 现有入口 |
84
+ |---|---|
85
+ | Python | `src/aun_core/client.py`、`_client/rpc_pipeline.py`、`_client/delivery.py` |
86
+ | Go | `client.go`、`client_rpc_pipeline.go`、`client_delivery.go` |
87
+ | TS / JS | `src/client.ts`、`src/client/rpc-pipeline.ts`、`src/client/delivery.ts` |
88
+
89
+ 更新已有测试,覆盖三前缀、前导空白移除、首行与其余正文保留、长正文、同名节点重复附行、默认 hop=100、首行 hop 参数与 payload 优先级、仅一个跳数字段、去程递减到 0、回程继续递减及负 hop 识别、强制明文、真实耗时、本地无网络提前回送、服务回包进入应用事件、单聊与群定向回送。
90
+
91
+ 外部 App、Rust SDK、EvolCore 未升级时不得宣称外部完整链路验收通过。
@@ -0,0 +1,189 @@
1
+ # 0.5.35 SDK 应用集成指南
2
+
3
+ 本文面向从已发布 **0.5.34** 升级到 **0.5.35** 的应用,依据截至 2026-11-12 的工作区源码整理。0.5.35 为目标版本,本文不代表安装包已发布或部署验收已完成。
4
+
5
+ 首次接入先读 [快速开始](01-快速开始.md)、[连接与认证](04-连接与认证.md)和 [API 手册](06-API手册.md)。本版实现契约见 [开发与移植指南](0.5.35-SDK开发与移植指南.md),源码差异和比较基线见 [parity 0.5.35](../../python/CHANGES_FOR_PARITY.md#v0535)。0.5.34 的多角色、持久目录、durable outbox、Storage/Group FS/Service Proxy 接入见 [历史应用指南](0.5.34-SDK应用集成指南.md)。
6
+
7
+ ## 1. 升级范围与支持边界
8
+
9
+ | 能力 | Python | Node TypeScript | Browser JavaScript | Go |
10
+ | --- | --- | --- | --- | --- |
11
+ | EventService 通配订阅、发布者过滤、同 Client 本地收发 | 支持 | 支持 | 支持 | 支持 |
12
+ | 后台累计 ACK、同步恢复调整、Echo v3.2 | 支持 | 支持 | 支持 | 支持 |
13
+ | 原生 UDP 待机 | 支持 | 支持 | 无原生实现 | 支持 |
14
+ | 独立 SMTP/IMAP MailClient | 支持 | 支持 | 无原生实现 | 支持 |
15
+ | 本版语言专属变化 | Windows 路径、上传清理、闭环压测 CLI | 邮件信封与 ImapFlow 适配 | 浏览器交付与测试版本对齐 | 只读凭据身份、DNS 回退、SQLite 初始化 |
16
+
17
+ 本次发布按**配套服务单实例部署**支持。Redis 缓存的存在不代表同一服务已支持多副本共享状态;本版不据此扩大部署范围。独立 Mail 尚未完成整体端到端验收,核心 IM 接入不要求启用 Mail。
18
+
19
+ 四 SDK 的代码、项目配置和相关测试版本已统一为 `0.5.35`。应用需核对实际安装产物的版本,并使用支持对应能力的配套服务;客户端升级不会自动启用 Gateway 的 UDP 待机或部署独立 Mail。
20
+
21
+ ## 2. EventService:通配订阅和发布者过滤
22
+
23
+ ### 2.1 先授权具体事件,再按模式订阅
24
+
25
+ `subscribe`/`unsubscribe` 新增点分层通配:`task.*` 可匹配 `task.completed`,不能匹配 `task.worker.completed`。层内可使用 `*`,不支持 `**`、空层或正则表达式。
26
+
27
+ 授权申请、grant scope 和发布仍使用完整事件名。订阅模式只在**当前 Bus 目录与当前设备有效租约的授权交集**内展开,返回实际订阅的具体事件。目录后来新增事件时,应在取得相应授权后重新订阅;旧通配订阅不会自动扩展。
28
+
29
+ 没有目录命中返回 `UNKNOWN_EVENT`;有目录命中但无可用授权返回 `FORBIDDEN`。取消订阅按当前已订阅集合展开模式。
30
+
31
+ ### 2.2 按可信发布者信息筛选
32
+
33
+ | 过滤含义 | Python/线上字段 | TS/JS 选项 | Go 选项 |
34
+ | --- | --- | --- | --- |
35
+ | 发布者 AID | `publisher_aids` | `publisherAids` | `PublisherAIDs` |
36
+ | 名片主体类型 | `publisher_types` | `publisherTypes` | `PublisherTypes` |
37
+ | 名片扩展类型 | `publisher_exttypes` | `publisherExttypes` | `PublisherExtTypes` |
38
+
39
+ 同一字段内多个模式为 OR,不同字段之间为 AND;模式沿用上述分层 `*` 规则。省略字段表示该维度不限,显式空数组无效。每个字段允许 1–64 项,每个模式的 UTF-8 长度最多 256 字节。
40
+
41
+ 以下示例假定应用已启动 EventService、取得 Sub 授权,且当前目录包含允许订阅的 `task` 事件:
42
+
43
+ ```ts
44
+ await client.EventService.subscribe(busAid, ['task.*'], {
45
+ publisherTypes: ['worker-*'],
46
+ publisherExttypes: ['task.executor'],
47
+ });
48
+ ```
49
+
50
+ AID 来自认证发送者,`type`/`exttype` 来自验签通过的发布者 `agent.md`,不能通过事件正文自报身份。名片暂时无法读取或验证时,要求类型的订阅不会匹配;不设类型条件或仅按 AID 过滤的订阅不受该名片读取失败影响。
51
+
52
+ ### 2.3 修改、恢复与同进程投递
53
+
54
+ 再次订阅同一具体事件会**替换该事件的过滤条件**;省略过滤字段会恢复为不限。希望保留过滤时,更新订阅应再次传入完整条件。
55
+
56
+ SDK 持久化具体事件及其过滤条件,重新授权后逐事件恢复。Python/Node/Go 使用 SQLite,Browser 使用 IndexedDB;状态按身份、Bus、设备与 slot 隔离。修改条件或取消订阅后,排队和回调开始前会复核订阅代次,已经执行的业务操作仍由应用负责。
57
+
58
+ 同一个 Client 内,Bus 为本端 AID、device/slot 匹配的 Bus/Pub/Sub 可以直接在进程内投递。跨 Client、设备或 slot 仍走网络。本地路径继续执行角色授权、租约、TTL、去重和容量检查;容量满时等待容量或期限到达,不提供无限队列。
59
+
60
+ 完整角色配置、凭证和事件生命周期见 [事件应用集成专题](AUN事件发布订阅应用集成指南.md)。
61
+
62
+ ## 3. 消息 ACK、同步与连接恢复
63
+
64
+ 后台 Pull 在本地提交消息后调度累计 ACK,同一 namespace 的更高水位会合并,失败后重试。前台 Pull 继续等待 ACK。ACK 表示协议消费进度,不是应用的业务处理完成证明;业务入库、幂等和完成回执仍按应用自己的契约处理。
65
+
66
+ 关闭 Client 仅有限等待后台 ACK,不保证关闭前全部确认已抵达服务端。重连后的重复交付应继续使用现有消息标识去重,不能因后台 ACK 改为异步就删除应用幂等逻辑。
67
+
68
+ | 变化 | 升级时的应用影响 |
69
+ | --- | --- |
70
+ | 自动连接后恢复随机延后 30–60 秒 | 包含补拉及组织身份/待上传名片恢复;不要将认证成功等同于历史已补齐 |
71
+ | 关闭后台同步时不调度上述自动恢复 | 显式 Pull 和消息通知触发仍使用各自入口 |
72
+ | 移除 `history_bytes_per_second` 主动节流 | 不再依赖该项限制历史吞吐;并发、页字节上限、在途内存预留和背压仍有效 |
73
+ | 辅助连接 ready/退役时更新同步需求 | SDK 随实际连接状态调整;`gateway.sync_demand` 不计为业务活动 |
74
+ | TS/JS 减少重复 Pull/ACK、合并群补拉 | 应用继续按消息和同步生命周期消费,不依赖内部任务次数 |
75
+
76
+ 现有同步事件及 `source`、History、已读语义见 [API 手册](06-API手册.md)和 [最佳实践](08-最佳实践.md)。
77
+
78
+ ## 4. Echo v3.2:诊断请求与结果
79
+
80
+ SDK 去掉首行前导空白后,按最长前缀识别 `echo.ecdiag`、`echo.ask`、`echo`,大小写不敏感。这类诊断强制明文,应用不要把敏感正文放入 Echo 请求。
81
+
82
+ 控制字段统一为 `hop`,默认 100。payload 中有效整数优先于首行 `hop=N`;安全整数范围为 ±9007199254740991。去程递减到 0 后立即返程,负数表示回程并继续递减;旧 `max_hop`/`rhop` 不再控制转发。
83
+
84
+ 本版保留完整正文及重复经过节点的 trace,回程耗时采用本地单调时钟。本地提前返回和服务返回的 `echo_return` 都通过既有 `message.received`/`group.message_created` 入口交付;群接收端的提前返回定向原发送者。
85
+
86
+ | 结果字段 | 应用解释 |
87
+ | --- | --- |
88
+ | `reason=timeout`/`send_failed` | 本地发送超时或失败的诊断原因 |
89
+ | `delivery_state=not_delivered` | 该诊断判断未完成投递 |
90
+ | `delivery_state=unknown` | 远端是否收到尚不确定,不应直接判为丢失并盲目重发 |
91
+ | `echo_error` | 原发送错误的诊断信息 |
92
+
93
+ 认证错误和密文发送不转换为上述本地诊断;业务回调抛错不会重复生成一条诊断。完整格式见 [Echo SDK 规范](../protocol/echo-probe-sdk.md),本文不宣称外部 App/Rust/EvolCore 链路已完成验收。
94
+
95
+ ## 5. UDP 待机:何时可以释放 WebSocket
96
+
97
+ ### 5.1 协商与准入
98
+
99
+ Python、Node TS、Go 的主长连接只有在服务端同时返回严格布尔值 `negotiated_capabilities.udp_standby_v1=true`、`udp_standby.enabled=true` 时才启用。Gateway 缺省关闭;未协商、短连接、辅助连接及 Browser 保持原连接方式。Node 可通过 `connect({ udp_standby: false })` 退出该能力,此选项写法不要套用于其他语言。
100
+
101
+ 空闲准入仍受名片 `type=human`、未完成 RPC、ACK、同步/交付、关闭和迁移等状态约束。待机执行 prepare → UDP 路径验证 → commit,成功后释放 WebSocket;正常切换不作为应用离线。
102
+
103
+ ### 5.2 区分两套订阅
104
+
105
+ Gateway 的协议级 `event.subscribe` 控制连接接收哪些通知;`EventService.subscribe` 控制 Bus 事件订阅。两者不是同一套订阅。
106
+
107
+ 默认全事件、queue/stream 或不在待机白名单的订阅会保持 WebSocket;SDK 不会为省连接擅自缩窄订阅。只有应用确认无需其他通知时,才可显式设置所需集合,例如:
108
+
109
+ ```python
110
+ await client.call("event.subscribe", {
111
+ "events": ["message.received", "group.message_created"],
112
+ })
113
+ ```
114
+
115
+ SDK 使用 prepare 返回的 `event_subscriptions` 快照,在恢复认证时写回 `auth.connect.options.event_subscriptions`。应用修改通知集合时应重新核对待机资格。
116
+
117
+ ### 5.3 唤醒和心跳
118
+
119
+ 新业务请求、消息提示、ticket/lease/对账期限以及连续心跳故障会恢复 WebSocket。并发唤醒会合并,各请求保留自己的超时和取消边界;SDK 不自动重放已经发出的写请求。
120
+
121
+ UDP 只承载控制提示,不传消息正文;`WAKE_ACK` 不代表应用已消费,不延长业务 TTL,也不能唤醒已经退出的进程。
122
+
123
+ 三端均采用当前有效 `HEARTBEAT_ACK` 返回的新心跳周期,下一轮按新周期执行 ±20% 抖动。Python 在收包匹配当前请求后更新策略,再由心跳循环读取;默认策略约为 25 秒周期、5 秒等待,连续三次失败回到 WebSocket,实际以服务端协商策略为准。
124
+
125
+ ## 6. 原生 MailClient:独立接线和恢复
126
+
127
+ ### 6.1 与 Gateway 客户端分开配置
128
+
129
+ 原生 MailClient 直接连接独立 SMTP STARTTLS(默认 587)和 IMAPS(默认 993),默认主机为 `mail.{issuer}`。认证使用 AID 证书、TLS 和 SASL EXTERNAL,不使用 Gateway JWT。旧 `client.mail`/Go `Mail()` RPC 门面继续兼容,但不能用它代替原生客户端的部署与认证配置。
130
+
131
+ | SDK | 构造入口 | 凭据 provider 返回值 | 自定义服务端信任根 |
132
+ | --- | --- | --- | --- |
133
+ | Python | `aun_core.MailClient(issuer, credential_provider=...)` | `cert_pem`/`private_key_pem`,或 `cert_file`/`key_file` | `server_trust_bundle` 为 CA 文件路径 |
134
+ | Node TS | `new MailClient({ issuer, credentialProvider, serverTrustBundle })` | `{ cert, key }`,内容为 string/Buffer,可异步提供 | CA 内容,string/Buffer |
135
+ | Go | `NewMailClient(MailClientOptions{...})` | `func(context.Context) (tls.Certificate, error)` | `ServerTrustBundle []byte` |
136
+
137
+ 连接会准备 SMTP 和 IMAP 两条连接,默认连接超时 10 秒、命令超时 30 秒。provider 应提供当前身份凭据;轮换后客户端重建连接。关闭时使用各语言的客户端关闭入口,Browser 不提供这些原生网络能力。
138
+
139
+ ### 6.2 分开处理发送与 Sent 结果
140
+
141
+ 支持结构化 MIME、附件或 raw MIME,并返回逐收件人状态。`accepted` 仅代表 SMTP 接受,不能当作最终送达回执。
142
+
143
+ | 状态维度 | 返回值 | 应用处理 |
144
+ | --- | --- | --- |
145
+ | 发送 | `accepted`/`partial`/`rejected`/`accepted_unknown` | 按收件人结果展示;未知状态应先核对,避免重复邮件 |
146
+ | Sent 副本 | `not_requested`/`saved`/`failed`/`unknown` | 独立于发送结果;副本失败不代表 SMTP 一定未接受 |
147
+
148
+ Python raw MIME 保留原始头部和正文,仅补缺失的 Date/Message-ID;投递时剥离 Bcc 及折行,Sent 保留完整副本。Node 会复制并规范化信封、去重收件人,不修改调用者传入的信封。
149
+
150
+ ### 6.3 用 UIDVALIDITY/UID 恢复监听
151
+
152
+ IMAP 提供 UID 查询/修改、流式读取和 watch。检查点应连同邮箱保存 `UIDVALIDITY`/UID,不能在 UIDVALIDITY 变化后复用旧 UID。
153
+
154
+ | SDK | 监听入口与变化事件 | UIDVALIDITY 改变后的行为 |
155
+ | --- | --- | --- |
156
+ | Python | `watch(mailbox, uidvalidity, uid=0)` 异步迭代;`change` | 产出 `resync_required` 后结束迭代 |
157
+ | Node TS | `watch(mailbox, { uidValidity, uid }, signal?)` 异步生成器;`changes` | 产出 `resync_required`,重置 UID 后继续 |
158
+ | Go | `Watch(ctx, mailbox, checkpoint, consume)` 回调;初始 `sync`,后续 `changed` | 回调 `resync_required` 后结束当前 Watch,返回 nil |
159
+
160
+ 应用收到 `resync_required` 后重建该邮箱的索引和检查点,并按所在语言决定继续消费或重新启动 watch。流式读取对应 Python `fetch_stream`、Node `fetchStream`、Go `FetchStream`,按各自接口管理流的生命周期。
161
+
162
+ Mail SDK 能力不等于独立邮件服务整体可用。本次 Mail 端到端验收未完成,默认 Go 运行镜像 `AUN_INCLUDE_MAIL=0` 不启用 Mail;构建阶段仍编译 Mail,不能据此忽略其构建依赖。
163
+
164
+ ## 7. Python/Go 专属接入变化
165
+
166
+ Python 修正 Windows 长路径下的 pending 身份、名片缓存和 group index 写入,并在 HTTP 上传结束时清理会话。应用沿用原存储和上传入口,无需迁移数据格式。
167
+
168
+ Python CLI 新增闭环容量工具,区分 RPC 接受和完整投递的吞吐/P99,支持有限 pending、阶梯并发、持续复验及断点恢复。仅查看计划可运行:
169
+
170
+ ```bash
171
+ aun bench capacity closed-loop example.com --case p2p-plain --run-id sdk035 --dry-run
172
+ ```
173
+
174
+ 场景包括 `p2p-plain`、`p2p-online`、`group-plain`、`group-online`;实际压测前按 [测试运行指南](../aun测试运行指南.md)准备环境。命令存在不代表已测得服务器容量。
175
+
176
+ Go 新增 `NewReadOnlyIdentityClient`,接收已有 P-256 证书/私钥、明确的 WSS 地址、device/slot 和可选 CA,状态在实例内存中隔离。“只读”指凭据存储,不限制为只读 RPC。相同证书 PEM 首尾空白可接受,实际换证书会被拒绝;DNS IP 回退仍保留原域名和显式端口 Host。
177
+
178
+ ## 8. 升级验收清单
179
+
180
+ | 范围 | 应用验收重点 |
181
+ | --- | --- |
182
+ | 版本与服务 | 四 SDK 实际产物版本、配套服务版本、单实例部署边界 |
183
+ | EventService | 具体事件授权、通配展开、过滤替换、重启恢复、取消后的排队事件、本地与网络路径 |
184
+ | 消息恢复 | 后台 ACK 重试、重连重复消息幂等、30–60 秒恢复窗口、历史背压 |
185
+ | Echo | 明文、唯一 hop、现有回调入口、未知投递状态分支 |
186
+ | UDP(三端) | 未协商保持 WS、human/忙碌阻止入睡、订阅恢复、周期更新、故障唤醒 |
187
+ | Mail(按需) | 独立服务与凭据轮换、逐收件人和 Sent 状态、UIDVALIDITY 变化、断线恢复 |
188
+
189
+ 本文核对了当前接口和源码,并定向运行 Python UDP 心跳测试(11 项通过);未执行 Docker、公网、浏览器 live、跨域 E2E 或 Mail 整体验收。其余既有执行记录及限制见 [parity 的核验范围](../../python/CHANGES_FOR_PARITY.md#v0535),不能把文档清单当作完整发布测试报告。
@@ -0,0 +1,204 @@
1
+ # 0.5.35 SDK 开发与移植指南
2
+
3
+ 本文面向维护四 SDK、实现新语言 SDK 或核对兼容性的开发者,整理已发布 **0.5.34 → 目标 0.5.35** 的协议、状态机与实现增量,依据截至 2026-11-12 的工作区源码。
4
+
5
+ 应用调用和升级验收见 [应用集成指南](0.5.35-SDK应用集成指南.md),比较基线、四端落点和已有验证见 [parity 0.5.35](../../python/CHANGES_FOR_PARITY.md#v0535)。既有身份、E2EE、EventService durable outbox、Storage、Group FS 与 Service Proxy 契约继续参考 [0.5.34 开发与移植指南](0.5.34-SDK开发与移植指南.md),本文只展开本版增量。
6
+
7
+ ## 1. 实现范围与职责
8
+
9
+ | 范围 | Python | Node TypeScript | Browser JavaScript | Go |
10
+ | --- | --- | --- | --- | --- |
11
+ | EventService 模式、过滤、本地通道 | 支持;SQLite | 支持;SQLite | 支持;IndexedDB | 支持;SQLite |
12
+ | 累计 ACK、同步恢复、Echo v3.2 | 支持 | 支持 | 支持 | 支持 |
13
+ | UDP 待机 | 原生 UDP | 原生 UDP | 无原生实现 | 原生 UDP |
14
+ | 独立 MailClient | SMTP/IMAP | SMTP/IMAP | 无原生实现 | SMTP/IMAP |
15
+
16
+ SDK 负责本地状态、认证请求、投递调度和连接生命周期;Gateway/Message/Group/独立 Mail 服务负责对应服务端事实。本版配套服务按**单实例部署**支持,Redis 缓存不构成多实例一致性契约。移植时不能把 SDK 的进程内能力扩写为服务多副本承诺,也不能将 Mail 客户端实现视为邮件服务整体已验收。
17
+
18
+ ## 2. EventService 订阅契约
19
+
20
+ ### 2.1 模式先展开,再保存具体事件
21
+
22
+ `subscribe`/`unsubscribe` 的事件模式按 `.` 分层,层内 `*` 不跨点;拒绝空层和 `**`,不执行正则。授权申请、grant scope、publish 保持完整事件名。订阅执行顺序为:
23
+
24
+ 1. 校验过滤参数,检查当前 AID/device/slot 的有效租约。
25
+ 2. 逐模式在当前 Bus 目录展开,无命中返回 `UNKNOWN_EVENT`;与 Sub 授权求交,无可订阅事件返回 `FORBIDDEN`。
26
+ 3. 按具体事件替换订阅条件并持久化。
27
+ 4. 返回实际具体事件;以后新增目录项不会自动加入既有订阅。
28
+
29
+ 取消订阅的模式在当前订阅集合中匹配,不能按模式扩大或恢复租约授权。
30
+
31
+ ### 2.2 过滤 wire 与信任来源
32
+
33
+ | wire/Python | TS/JS | Go |
34
+ | --- | --- | --- |
35
+ | `publisher_aids` | `publisherAids` | `PublisherAIDs` |
36
+ | `publisher_types` | `publisherTypes` | `PublisherTypes` |
37
+ | `publisher_exttypes` | `publisherExttypes` | `PublisherExtTypes` |
38
+
39
+ 每字段为非空数组,1–64 项,每模式 UTF-8 最多 256 字节;省略不限。同字段 OR、跨字段 AND,沿用点分层通配。再次订阅同事件时替换过滤,不能把新条件与旧条件暗中合并。
40
+
41
+ 发布者 AID 取认证发送者;类型取验签通过的 `agent.md`,不信事件正文自报。仅在订阅要求类型时解析名片,缓存与并发均有界;读取或验签失败时,不匹配要求该类型的条件,不应连带拒绝仅 AID 或不限类型的订阅。
42
+
43
+ ### 2.3 持久化与订阅代次
44
+
45
+ Bus 端和客户端保存具体事件及完整过滤,重授权后逐事件恢复。状态应保持 AID、Bus、角色、device、slot 的既有隔离;Python/Node/Go 的 SQLite 与 Browser 的 IndexedDB 只改变存储介质,不改变过滤语义。
46
+
47
+ 修改条件、取消订阅或关闭会改变订阅代次。接收排队与开始回调前复核代次,旧代次事件不能继续按旧条件进入回调;不能据此保证回滚已执行的业务。
48
+
49
+ ### 2.4 同 Client 本地通道
50
+
51
+ 只有同一个 Client、Bus AID 为本端且 device/slot 匹配时允许本地收发;跨 Client/设备/slot 保持网络路径。投递复制消息后进入原接收处理,不能直接调用业务回调跳过授权与调度。
52
+
53
+ 本地路径保留角色授权、注册租约、TTL、去重、容量和关闭代际。容量满时等待容量或原期限,不建立无限队列;本地投递不重复扣网络接收速率。
54
+
55
+ | SDK | 主要落点 |
56
+ | --- | --- |
57
+ | Python | [pubsub/client.py](../../python/src/aun_core/pubsub/client.py)、[service.py](../../python/src/aun_core/pubsub/service.py)、[binding.py](../../python/src/aun_core/pubsub/binding.py) |
58
+ | Node TS | [pubsub/types.ts](../../ts/src/pubsub/types.ts)、[client.ts](../../ts/src/pubsub/client.ts)、[service.ts](../../ts/src/pubsub/service.ts)、[channel.ts](../../ts/src/pubsub/channel.ts) |
59
+ | Browser JS | [pubsub/client.ts](../../js/src/pubsub/client.ts)、[service.ts](../../js/src/pubsub/service.ts)、[channel.ts](../../js/src/pubsub/channel.ts) |
60
+ | Go | [pubsub_types.go](../../go/pubsub_types.go)、[pubsub_client.go](../../go/pubsub_client.go)、[pubsub_service.go](../../go/pubsub_service.go)、[pubsub_binding.go](../../go/pubsub_binding.go) |
61
+
62
+ 完整事件租约、凭证、fanout 和公平背压继续遵循 [事件开发移植专题](AUN事件发布订阅开发移植指南.md)。
63
+
64
+ ## 3. ACK、同步预算与恢复调度
65
+
66
+ ### 3.1 累计 ACK 的提交与收尾
67
+
68
+ 后台 Pull 在本地提交后异步调度累计 ACK。同 namespace 合并更高确认水位,失败重试;前台 Pull 保持等待 ACK。任务按 AID、device、slot、连接与 namespace 代际隔离,旧任务不能确认新会话的数据。
69
+
70
+ TS/JS 合并任务时同步更新待发送参数。任务成功结束前先移除当前 ACK job,使随后更高水位可创建新任务;最终清理须检查任务身份,不能把同 key 下的新 job 一并删除。
71
+
72
+ 关闭仅有限等待后台 ACK。协议确认不表示业务完成,重连重复投递和应用幂等仍遵循既有消息契约。
73
+
74
+ ### 3.2 历史同步与连接需求
75
+
76
+ 移除 `history_bytes_per_second` 主动字节速率节流,保留并发、`max_page_bytes`、在途内存预留和压力背压;volatile 继续独立游标,不与持久消息历史进度混用。TS/JS 避免重复补拉已被在线交付覆盖的范围,并合并群补拉责任。
77
+
78
+ 辅助连接 ready/退役后按实际可用连接重新发布同步需求。`gateway.sync_demand` 属于同步控制通知,不增加业务活动或因此阻止 UDP 空闲待机。
79
+
80
+ 自动连接后的补拉、组织身份和待上传名片恢复随机延后 30–60 秒。执行前复核身份和连接代际,关闭后台同步时不调度;显式 Pull 与消息通知仍由各自入口触发,不能将所有入口统一加延时。
81
+
82
+ 落点:Python [delivery.py](../../python/src/aun_core/_client/delivery.py)、[sync_channels.py](../../python/src/aun_core/_client/sync_channels.py)、[client.py](../../python/src/aun_core/client.py);TS/JS 各自 `src/client/{delivery,sync-channels,v2-e2ee,lifecycle}.ts`;Go [client_delivery.go](../../go/client_delivery.go)、[sync_channels.go](../../go/sync_channels.go)、[client_lifecycle.go](../../go/client_lifecycle.go)、[org_facades.go](../../go/org_facades.go)。
83
+
84
+ ## 4. Echo v3.2 处理契约
85
+
86
+ 完整 wire 以 [Echo SDK 规范](../protocol/echo-probe-sdk.md)为准。四端移植应保持以下行为:
87
+
88
+ | 环节 | 契约 |
89
+ | --- | --- |
90
+ | 识别 | 首行去前导空白,大小写不敏感,最长前缀顺序为 `echo.ecdiag`、`echo.ask`、`echo` |
91
+ | 传输 | 强制明文;认证错误和密文发送不转换成本地发送失败诊断 |
92
+ | hop | 唯一字段,默认 100;payload 有效安全整数优先于首行 `hop=N`;范围 ±9007199254740991 |
93
+ | 转向 | 去程递减到 0 立即返程,负数是回程并继续减;旧 `max_hop`/`rhop` 不控制状态 |
94
+ | trace | 保留完整正文和重复节点,携带版本、epoch、时区等;回程耗时取本地单调时钟 |
95
+ | 交付 | 本地提前返回和服务端 `echo_return` 复用 `message.received`/`group.message_created`;群提前返回定向原发送者 |
96
+ | 错误 | 本地发送失败/超时带 `reason=send_failed\|timeout`、`delivery_state=not_delivered\|unknown`、`echo_error` |
97
+
98
+ `unknown` 不能缩减为确定未投递。交付队列关闭后不为诊断重新打开,回调失败不能再次生成同一诊断;不能把四 SDK 的实现核对扩写为外部 App/Rust/EvolCore 全链路已通过。
99
+
100
+ ## 5. UDP 待机状态机与报文
101
+
102
+ ### 5.1 协商、prepare 与恢复
103
+
104
+ 能力只用于 Python/Node TS/Go 的主长连接,必须同时严格匹配两个布尔值:`negotiated_capabilities.udp_standby_v1=true` 与 `udp_standby.enabled=true`。缺省关闭或未协商则保持 WS;辅助连接和短连接不声明此能力。
105
+
106
+ 状态顺序为 active → preparing → UDP 验证 → commit → standby → waking → active。human 名片、未完成 RPC/ACK/同步/交付、迁移与关闭会阻止或中断入睡;不能使用注册意图替代当前名片类型判断。
107
+
108
+ prepare 返回的 `event_subscriptions` 必须保存为快照;恢复时写回 `auth.connect.options.event_subscriptions`。这是 Gateway 协议通知订阅,区别于 EventService 的 Bus 订阅。默认全事件、queue/stream 及非白名单订阅保持 WS,SDK 不擅自改变应用订阅。
109
+
110
+ 业务请求和 UDP 提示可合并触发恢复,但各请求的 deadline/cancel 独立。ticket/lease/对账期限和心跳连续失败也触发恢复;关闭和身份代际切换应终止旧等待,已经发出的写请求不自动重放。
111
+
112
+ ### 5.2 认证与心跳格式
113
+
114
+ UDP 报文使用 HMAC-SHA256,`c2s`/`s2c` 方向域分离;使用 64 位序号和 64 位窗口防重放,总包长最多 256 字节。UDP 只传控制信息;WAKE_ACK 不代表业务消费,也不改变消息 TTL。
115
+
116
+ `HEARTBEAT_ACK` 正文固定 **20 字节**,多字节整数使用网络字节序:
117
+
118
+ | 偏移 | 字段 | 类型 |
119
+ | --- | --- | --- |
120
+ | 0 | `lease_remaining_ms` | uint32 |
121
+ | 4 | `pending_generation` | uint64 |
122
+ | 12 | `reason_mask` | uint32 |
123
+ | 16 | `heartbeat_interval_ms` | uint32 |
124
+
125
+ 只接受当前 ticket/epoch、通过认证与重放检查、匹配当前未完成心跳且在期限内的 ACK。新周期决定下一轮调度并保留 ±20% 抖动;成功应重置连续失败计数,pending/reason 触发唤醒。默认策略约为 25 秒周期、5 秒等待、连续三次失败回 WS,实际以协商策略及租约/ticket/对账期限为准。
126
+
127
+ | SDK | 当前新周期处理与实现 |
128
+ | --- | --- |
129
+ | Python | [_receive](../../python/src/aun_core/_client/udp_standby.py) 在通过认证和请求匹配后更新 policy,心跳循环下一轮读取 |
130
+ | Node TS | [udp-standby.ts](../../ts/src/udp-standby.ts) 读取偏移 16,更新 policy 并重新安排 timer |
131
+ | Go | [udp_standby.go](../../go/udp_standby.go) 更新本轮策略并计算下一次心跳时间 |
132
+
133
+ 验证应通过实际收包入口进入循环,不能仅用绕过收包处理的 `_exchange` mock 判断周期更新。Python 现有回归覆盖当前/过期/已完成/已取消 ACK、零周期、下一轮新周期及抖动、成功后重新累计三次超时。
134
+
135
+ 编解码落点:Python [udp_standby_codec.py](../../python/src/aun_core/_client/udp_standby_codec.py)、Go [udp_standby_codec.go](../../go/udp_standby_codec.go);设计背景见 [AID 空闲 UDP 待机详细设计](../../../docs/AUN文档/AID空闲UDP待机详细设计.md)。
136
+
137
+ ## 6. 原生 MailClient 的三端移植
138
+
139
+ ### 6.1 认证、凭据和依赖
140
+
141
+ 原生客户端直连独立 SMTP STARTTLS/IMAPS,默认 `mail.{issuer}:587`/`mail.{issuer}:993`,使用 AID 证书、TLS 和 SASL EXTERNAL。连接准备 SMTP 与 IMAP,默认连接超时 10 秒、命令超时 30 秒;不能复用 Gateway JWT 替代证书认证。
142
+
143
+ | SDK | 凭据与信任根 | 实现/依赖 |
144
+ | --- | --- | --- |
145
+ | Python | provider 提供 PEM 或证书/私钥文件;`server_trust_bundle` 为 CA 文件路径 | [mail_client.py](../../python/src/aun_core/mail_client.py) |
146
+ | Node TS | provider 返回 `{cert, key}` string/Buffer;信任根是 CA 内容 | [mail-client.ts](../../ts/src/mail-client.ts);Nodemailer/ImapFlow |
147
+ | Go | provider 返回 `tls.Certificate`;信任根为 `[]byte` | [mail_client.go](../../go/mail_client.go);emersion SMTP/IMAP/SASL |
148
+
149
+ 每次使用当前 provider 快照,凭据变化后重建连接。Python 的 provider/文件读取失败归为 `identity_unavailable` 并断开旧连接;TLS、认证、协议错误保留原分层。Browser 没有原生 MailClient,旧 RPC 门面继续兼容。
150
+
151
+ ### 6.2 MIME、信封和发送结果
152
+
153
+ 结构化 MIME/附件与 raw MIME 都要保留独立信封、逐收件人结果和可选 Sent 副本。发送结果为 `accepted`/`partial`/`rejected`/`accepted_unknown`,Sent 状态独立为 `not_requested`/`saved`/`failed`/`unknown`;SMTP 接受不等于最终送达,Sent 失败不能回写成全部收件人拒绝。
154
+
155
+ Python raw MIME 保留原始头和正文字节,仅补缺失 Date/Message-ID;实际投递剥离 Bcc 及折行,Sent 保留完整副本。邮箱名使用 modified UTF-7,LIST 仅去一对协议引号。
156
+
157
+ Node 复制并规范化信封、去重收件人,不修改调用者对象。[ImapFlow 适配脚本](../../ts/scripts/patch-imapflow-external.mjs) 同时覆盖 ESM/CJS 的 SEARCH、COPY、MOVE、EXPUNGE、STORE;协议错误向上传播,合法空查询仍可成功,不能把查询失败吞成空结果。
158
+
159
+ ### 6.3 IMAP UID 与 watch 生命周期
160
+
161
+ UID 操作与流式读取应保持邮箱选择、UIDVALIDITY 和 UID 的关联。检查点字段、事件名和终止方式按各语言真实接口适配,不能只做大小写替换:
162
+
163
+ | SDK | watch 接口 | 变化事件/UIDVALIDITY 变化 |
164
+ | --- | --- | --- |
165
+ | Python | `watch(mailbox, uidvalidity, uid=0)` → AsyncIterator | `change`;`resync_required` 后结束迭代 |
166
+ | Node TS | `watch(mailbox, {uidValidity: bigint, uid: number}, signal?)` → AsyncGenerator | `changes`;`resync_required` 后重置 UID 并继续 |
167
+ | Go | `Watch(ctx, mailbox, MailWatchCheckpoint, consume)` | 初始 `sync`,后续 `changed`;`resync_required` 后内部结束 session,外层 Watch 返回 nil |
168
+
169
+ 收到 `resync_required` 应让应用重新建立该邮箱索引;不得跨 UIDVALIDITY 复用旧 UID。Python `fetch_stream`、Node `fetchStream`、Go `FetchStream` 的返回类型及关闭方式按源码接口实现。Go 回调错误向调用者返回,应用取消与内部重建信号不能误写为所有错误都无限重连。
170
+
171
+ 本版 Mail 整体验收尚未完成。默认 Go 运行镜像 `AUN_INCLUDE_MAIL=0` 未启用 Mail,但 build 阶段仍编译 Mail 代码,依赖裁剪不能只看运行镜像开关。
172
+
173
+ ## 7. 语言专属实现落点
174
+
175
+ | SDK | 本版内容 | 参考源码 |
176
+ | --- | --- | --- |
177
+ | Python | Windows extended path、pending 身份、名片缓存、group index 原子写入 | [keystore/_utils.py](../../python/src/aun_core/keystore/_utils.py)、[local_identity_store.py](../../python/src/aun_core/keystore/local_identity_store.py)、[agent_md.py](../../python/src/aun_core/agent_md.py)、[group_index.py](../../python/src/aun_core/group_index.py) |
178
+ | Python | HTTP 上传在 finally 关闭会话;闭环容量测试分离接受/投递指标并支持恢复 | [aid_store.py](../../python/src/aun_core/aid_store.py)、[bench_capacity.py](../../python/src/aun_cli/commands/bench_capacity.py)、[benchmark/runner.py](../../python/src/aun_cli/benchmark/runner.py) |
179
+ | Go | 已有 P-256 凭据只读接入、实例状态内存隔离;相同 PEM 空白变化可接受,实际换证书拒绝 | [client_readonly.go](../../go/client_readonly.go) |
180
+ | Go | DNS IP 回退保留原域名及显式端口 Host,HTTP/WS 自定义 CA | [transport.go](../../go/transport.go) |
181
+ | Go | busy timeout 先于 WAL 初始化;Ownership 归属恢复冲突详情 | [aid_db.go](../../go/keystore/aid_db.go)、[ownership_v21.go](../../go/ownership_v21.go) |
182
+
183
+ 只读身份的“只读”约束凭据存储,不限制网络 RPC 为只读。Python 闭环工具的阶梯上限、每路 pending、完整投递吞吐/P99、持续复验及 `--resume` 属于工具契约,不是 SDK 已证明的服务端容量。
184
+
185
+ ## 8. 定向回归与证据边界
186
+
187
+ | 范围 | 已有测试入口 | 核对重点 |
188
+ | --- | --- | --- |
189
+ | EventService | Python `tests/unit/test_pubsub_*.py`;TS/JS `tests/unit/pubsub-*.test.ts`;Go `pubsub_*_test.go` | 模式/授权求交、过滤替换与持久化、可信名片、本地容量/TTL、订阅代次 |
190
+ | ACK/同步 | Python `test_message_windows.py`/`test_client.py`;TS/JS client/delivery;Go `forward_commit_test.go` | 提交后 ACK、合并及失败重试、任务收尾、身份/连接隔离、背压和延迟恢复 |
191
+ | Echo | [规范](../protocol/echo-probe-sdk.md);TS/JS `tests/integration/echo.test.ts`;Go `integration_echo_test.go` | hop/trace、提前回送、未知投递、回调失败不重复诊断 |
192
+ | UDP | Python [test_udp_standby.py](../../python/tests/unit/test_udp_standby.py)/[codec 测试](../../python/tests/unit/test_udp_standby_codec.py);TS [udp-standby.test.ts](../../ts/tests/unit/udp-standby.test.ts);Go [udp_standby_test.go](../../go/udp_standby_test.go) | 严格协商、忙碌准入、订阅恢复、当前 ACK 新周期与抖动、超时和关闭 |
193
+ | Mail | Python [test_mail_client.py](../../python/tests/unit/test_mail_client.py);TS [mail-client.test.ts](../../ts/tests/unit/mail-client.test.ts);Go [mail_client_test.go](../../go/mail_client_test.go) | MIME/信封、逐收件人、Sent、错误分层、凭据轮换、UIDVALIDITY/watch |
194
+ | 语言专属 | Python `test_bench_capacity*.py` 及存储测试;Go [client_readonly_test.go](../../go/client_readonly_test.go) 与 transport 测试 | 工具指标/断点、Windows 路径、凭据只读、Host/CA、SQLite 初始化 |
195
+
196
+ 当前 Python 心跳行为可在 `aun-sdk-core/python/` 定向验证,临时输出应放在当前会话的临时目录:
197
+
198
+ ```powershell
199
+ python -X utf8 -m pytest tests/unit/test_udp_standby.py -k heartbeat -q -x -p no:cacheprovider --basetemp "$env:TMPDIR\pytest-udp-heartbeat"
200
+ ```
201
+
202
+ 本次上述命令 **11 项通过**,覆盖真实编解码收包路径下的周期更新。其余条目是现有测试入口,不表示本轮全部执行。此前版本一致性、JS conformance、Go E2EE 定向测试和 TS/JS 类型检查的记录见 [parity](../../python/CHANGES_FOR_PARITY.md#v0535),不能替代新功能全量验证。
203
+
204
+ 本次未启动 Kite/Docker,未运行公网、浏览器 live、跨域 E2E 或独立 Mail 整体验收;按单实例发布范围交付文档,不补做多实例实现或 Mail 服务功能。
@@ -230,7 +230,7 @@ print(client.can_connect, client.can_send, client.is_online)
230
230
 
231
231
  ## Agent Web / agent.md
232
232
 
233
- 以下普通主体示例以本 AID 主签和 token 上传。Group/Org 名片由实体私钥生成主签,当前 owner 以本人 token 上传,无需实体登录;可选 owner 附签,存在就必须验全部块。Group 旧实体单签及实体身份上传始终兼容,Org 不开放该旧入口。取消实体额外 V2 上传授权,保留当前 owner、生命周期、实体证书代际与 CAS 检查,见 [协议 §2.5](../protocol/Ownership协议定义2.1.md#25-grouporg-名片的实体主签与-owner-上传)。
233
+ 以下普通主体示例以本 AID 主签和 token 上传。现代 Group/Org 名片由实体私钥生成主签,当前 owner 以本人 token 上传,无需实体登录;使用与 owner.md 配对的 ownership.statement,主签不含 signer_aid,也不要求旧 entity_cert_fingerprint,可选 owner 附签,存在就必须验全部块。Group 旧实体单签及实体身份上传持续兼容,Org 不开放该旧入口;仍保留当前 owner、生命周期、实体证书代际与 CAS 检查,见 [协议 §2.5](../protocol/Ownership协议定义2.1.md#25-grouporg-名片的实体主签与-owner-上传)。
234
234
 
235
235
  发布自己的 agent.md:
236
236
 
@@ -263,7 +263,7 @@ downloaded = await store.download_agent_md("bob.agentid.pub")
263
263
  state = await store.check_agent_md("bob.agentid.pub", ttl_days=1)
264
264
  ```
265
265
 
266
- 新 SDK 全部 NS GET/HEAD/PUT(含 fresh)携带 `X-AUN-Signature-Format: multi-v1` 获取完整多签;实体 fresh GET 必须同时携带 `X-AUN-Ownership-Check: 1`、`Cache-Control: no-cache`、`X-AUN-Signature-Format: multi-v1`;NS 仅凭 Check 头绕过 LRU 并新鲜验证全部签块,仍返回 not_applicable,不进入通用 Ownership 账本。无头客户端读取首签投影,表示 ETag 与完整存储 CAS 的映射见 [协议 §3.3](../protocol/Ownership协议定义2.1.md#33-读取并发与接口)。
266
+ 新 SDK 全部 NS GET/HEAD/PUT(含 fresh)携带 `X-AUN-Signature-Format: multi-v1` 获取完整多签;实体 fresh GET 必须同时携带 `X-AUN-Ownership-Check: 1`、`Cache-Control: no-cache`、`X-AUN-Signature-Format: multi-v1`;NS 凭 Check 头绕过 LRU 并新鲜验证全部签块。现代 Group/Org 参与双文件共识,缺少 ownership 的历史实体仍返回 not_applicable;普通关系管理 Facade 仍走专属生命周期边界。无头客户端读取首签投影,表示 ETag 与完整存储 CAS 的映射见 [协议 §3.3](../protocol/Ownership协议定义2.1.md#33-读取并发与接口)。
267
267
 
268
268
  当前公开入口分工固定:
269
269
 
@@ -16,6 +16,7 @@
16
16
  - [E2EE 高级 API](#e2ee-高级-api)
17
17
  - [RPC 方法参考](#rpc-方法参考)
18
18
  - [Stream 使用指南](#stream-使用指南)
19
+ - [AID 空闲 UDP 待机(测试能力)](#aid-空闲-udp-待机测试能力)
19
20
 
20
21
  > **多语言命名约定**:Python 使用 `snake_case`(如 `aun_path`、`download_agent_md`),TS/JS 使用 `camelCase`(如 `aunPath`、`downloadAgentMd`),Go 使用 `PascalCase` 公开方法(如 `Load`、`Register`)。本手册表格中各列对应各语言的实际命名。
21
22
 
@@ -140,10 +141,10 @@ Python 兼容 `forceRefresh`、`skipAgentMd`、`timeoutMs`、`ownershipTtlMs`
140
141
  | `tags` | string[] | 标签 |
141
142
  | `owner` | string | 名片声明的 owner;普通主体需结合 2.1 共识状态判断;Group/Org 属于专属生命周期 |
142
143
  | `ownership` | object | 普通主体的声明容器,当前核心字段为 `statement` |
143
- | `entity_cert_fingerprint` | string | Group/Org 实体证书的 `sha256:<hex>` 指纹 |
144
+ | `entity_cert_fingerprint` | string | 历史 Group/Org 名片的实体证书 `sha256:<hex>` 指纹;现代 ownership.statement 格式不再要求此字段 |
144
145
  | 其它键 | 任意解析值 | 如 visibility、skills 或应用自定义字段,存在时原样返回 |
145
146
 
146
- 当前发布 Schema 要求 `aid/name/type/version`;读取接口兼容历史名片,因此类型声明中的标准字段可缺省,不补默认值,裸 Markdown 正文返回 `{}`。`ownership.statement` 保留六字段:`version=2`(整数)、`declaration_id`(小写 UUIDv4)、`owner_aid`、`sub_aid`、`relation_version`(正整数)和 `declared_at`(Unix 秒)。普通主体的 owner 与 statement 同时存在或同时缺省;human 禁止通用 ownership,Group/Org 不使用此声明容器。
147
+ 当前发布 Schema 要求 `aid/name/type/version`;读取接口兼容历史名片,因此类型声明中的标准字段可缺省,不补默认值,裸 Markdown 正文返回 `{}`。`ownership.statement` 保留六字段:`version=2`(整数)、`declaration_id`(小写 UUIDv4)、`owner_aid`、`sub_aid`、`relation_version`(正整数)和 `declared_at`(Unix 秒)。普通主体的 owner 与 statement 同时存在或同时缺省;human 禁止通用 ownership,现代 Group/Org 同样使用与 owner.md 配对的声明,历史无 ownership 实体名片继续兼容。
147
148
 
148
149
  字段定义与签名规则以 [当前协议 §1.3、§2](../protocol/Ownership协议定义2.1.md) 为准;[agent.md/SCHEMA.md](../agent.md/SCHEMA.md) 是 legacy Ownership 2.0。`frontmatter.owner` 和已有 `agent_md.verification.ownership_status` 都不替代下面的 2.1 共识结果。
149
150
 
@@ -175,7 +176,7 @@ Python 兼容 `forceRefresh`、`skipAgentMd`、`timeoutMs`、`ownershipTtlMs`
175
176
  | `single_sided` | 只有一方声明或待确认材料,关系尚未成立 |
176
177
  | `invalid` | 声明、签名或身份绑定等材料无效 |
177
178
  | `unavailable` | 网络、证书、服务能力、权威性或读取一致性不足,当前无法确认 |
178
- | `not_applicable` | Group/Org 等使用专属生命周期,不适用通用共识 |
179
+ | `not_applicable` | NS 对缺少 ownership 的历史 Group/Org 保留该标记;现代实体声明参与双文件共识,SDK 以实际返回 status 为准 |
179
180
 
180
181
  `owner_md` 包含 `owner_aid: string`、`bucket: "default"`、`path: "sub_aids/{aid}/owner.md"`、`exists: bool`、`content: string`(完整原文,含签名)、`sha256: string`、`observed_at: integer`(Storage 观察时间,Unix 秒)。确认不存在时 exists=false、content/sha256 为空串;存在快照不表示整体共识成功,须检查 status。
181
182
 
@@ -207,7 +208,7 @@ if result.Ok && result.Data.AgentMD != nil {
207
208
 
208
209
  ### 通用 sign / verify
209
210
 
210
- `format="raw"` 为默认,保留 detached Base64 签名/验签;`format="document"` 处理任意 UTF-8 文本文档的连续尾签,不要求 YAML 或 agent.md 业务字段。sign 返回 `signed` 全文、本次 `signature` 和实际 `signatures` 列表;verify 返回 `valid`、`status`、首块前 `payload` 及逐块 `signatures`。普通 agent.md 与 owner.md 主签不输出且业务不接受 signer_aid;实体主块显式 signer_aid=target,主签身份、Schema、资源及授权由调用者另验,SDK 文件流程和服务端均复用各自唯一同构签验 core。
211
+ `format="raw"` 为默认,保留 detached Base64 签名/验签;`format="document"` 处理任意 UTF-8 文本文档的连续尾签,不要求 YAML 或 agent.md 业务字段。sign 返回 `signed` 全文、本次 `signature` 和实际 `signatures` 列表;verify 返回 `valid`、`status`、首块前 `payload` 及逐块 `signatures`。普通 agent.md、owner.md 与现代 Group/Org agent.md 主签均不输出且业务不接受 signer_aid;历史实体主签格式另按兼容规则验证,主签身份、Schema、资源及授权由调用者另验,SDK 文件流程和服务端均复用各自唯一同构签验 core。
211
212
 
212
213
  `input_type="text"` 默认把输入视作内容;显式 `input_type="file"` 才读取文件;raw 按原始字节处理,包括非 UTF-8 文件,document 必须严格 UTF-8,raw/document 字符串均严格校验 Unicode 并编码为 UTF-8,无效 Unicode 拒绝。不识别 `file://` 或自动猜路径,缺失文件报错。`in_place=True` 仅适用于 file + document,签名成功后才写回;TS/JS 使用 `inputType/inPlace`,Go 使用 `InputType/Format/Tag/Append/InPlace`。浏览器 File/Blob 只读,FileSystemFileHandle 可原地写,不接受裸本地路径。
213
214
 
@@ -464,7 +465,7 @@ headers = client.get_protected_headers()
464
465
  - agent.md 上传、下载和检查入口都在 `AIDStore`;`AUNClient` 不再暴露上传入口。
465
466
  - 普通主体目标 AID 须有本地签名材料并使用自身 token;Group/Org 用实体主签、当前 owner token 上传,可选 owner 附签,不能以附签替换资源授权。agent.md 与 owner.md 统一调用 Store document 签验 core,再验证业务 Schema。
466
467
  - 实体上传默认使用名片当前 owner;显式指定上传者分别使用 Python `uploader_aid`、TS/JS `options.uploaderAid`、Go `AIDStoreUploadAgentMDOptions.UploaderAID`。追加 owner 附签分别使用 `co_signer_aid`、`options.coSignerAid`、`AIDStoreUploadAgentMDOptions.CoSignerAID`,值必须为当前 owner;Go 通过 `UploadAgentMDWithOptions` 传入选项。
467
- - SDK 全部 NS GET/HEAD/PUT 携带 `X-AUN-Signature-Format: multi-v1`;GET 同时发送 `Accept: text/markdown`;实体 fresh GET 另带 `X-AUN-Ownership-Check: 1` 与 `Cache-Control: no-cache` 以绕 LRU 全块验证,仍为 not_applicable、不进入通用账本;不主动发送 `If-None-Match` / `If-Modified-Since`。如果服务端异常返回 304,本地有内容则复用;无内容时再发一次无条件 GET。
468
+ - SDK 全部 NS GET/HEAD/PUT 携带 `X-AUN-Signature-Format: multi-v1`;GET 同时发送 `Accept: text/markdown`;实体 fresh GET 另带 `X-AUN-Ownership-Check: 1` 与 `Cache-Control: no-cache` 以绕 LRU 全块验证;现代实体参与双文件共识,NS 对缺少 ownership 的历史实体保留 not_applicable 标记;不主动发送 `If-None-Match` / `If-Modified-Since`。如果服务端异常返回 304,本地有内容则复用;无内容时再发一次无条件 GET。
468
469
  - SDK 会自动从 Gateway `_meta.agent_md_etags` 和信封 `agent_md` 观察远端版本;`requester`、`peer`、`group` 是标准角色键,`receiver`、`target`、`to`、`sender`、`from` 是兼容别名。
469
470
  - `Accept: text/markdown` 与 agent.md 的 YAML frontmatter + Markdown 格式兼容;agent.md 仍是 Markdown 媒体类型上的结构化约定。
470
471
 
@@ -519,7 +520,7 @@ Bus 宿主可查询全局;Pub 仅查看自身 AID、当前发布权限内的
519
520
  | Ownership 2.1 | 七个关系业务Facade:`bindOwneraid`、`bindSubaid`、`releaseSubaid`、`transferSubaid`、`acceptOwnershipRequest`、`rejectOwnershipRequest`、`checkOwnershipConsensus`;无参数任务管理Facade `resetOwnershipTask()`(Python `reset_ownership_task()`、Go `ResetOwnershipTask(ctx)`),批量清理当前AID发起的未完成任务,返回`{reset_operation_ids:[],skipped_operation_ids:[]}`(其余为TS/JS命名) | [应用集成](Ownership2.1-SDK应用集成指南.md#4-七个-facade-的参数角色与结果)、[开发与移植](Ownership2.1-SDK开发与移植指南.md) |
520
521
  | Credential/Org | Python/TS/JS:`client.credential`、`client.org`;Go:`client.Credential()`、`client.Org()`;普通与语义凭证共用 credential 入口,无 semantic 子层;包含签验、包/请求、消息证据及额度协调工具 | [应用集成](凭证与组织服务SDK应用集成指南.md)、[开发与移植](凭证与组织服务SDK开发与移植指南.md) |
521
522
 
522
- Ownership 2.1 关系须检查当前双文件共识;Group/Org 使用专属生命周期,通用关系操作返回 `not_applicable`。Credential/Org 的本地验签不代替当前状态和请求授权检查。
523
+ Ownership 2.1 关系须检查当前双文件共识;现代 Group/Org 同样发布配对声明并查询共识,但创建、绑定、转让和解散仍使用专属生命周期,普通 bind/release/transfer 关系管理 Facade 返回 `not_applicable`。Credential/Org 的本地验签不代替当前状态和请求授权检查。
523
524
 
524
525
  sub-first 绑定在 sub 同意后 Stage 暂存 `agent.md`;NS 的 `pending/unlocked` 对公共共识映射为 `single_sided`,不授予 owner 权限,Storage 名片副本待 fresh 验收清除 pending 后同步。owner-first 绑定先 fresh 确认无锁并条件发布 `owner.md`,sub fresh 验证后同意并直接条件提交 `agent.md`,不走 Stage 或 bind.accept/owner.ready 往返。已有 owner 锁时只能由当前 owner 发起解绑或转移;拒绝清理与 reset 的原件/CAS 边界见 [应用指南 §6.1](Ownership2.1-SDK应用集成指南.md#61-绑定按发起方分流)。普通消息建议连接前订阅,READY 后补订阅也会触发任务恢复。
525
526
 
@@ -1137,3 +1138,28 @@ info = await client.call("stream.get_info", {"stream_id": stream["stream_id"]})
1137
1138
  await client.call("stream.close", {"stream_id": stream["stream_id"]})
1138
1139
  ```
1139
1140
 
1141
+ ## AID 空闲 UDP 待机(测试能力)
1142
+
1143
+ Python、TypeScript/Node.js 与 Go SDK 支持独立 AID 实例的 UDP 待机,浏览器 JS 与本期暂不移植 UDP 的 Rust SDK 保持 WebSocket。具有待机控制器的主长连接通过 `auth.connect.capabilities.udp_standby_v1=true` 声明能力;Gateway 默认关闭,响应 `capabilities.udp_standby_v1` 表达当前实际可用性,只有 `negotiated_capabilities.udp_standby_v1` 与 `udp_standby.enabled` 均严格为布尔 `true` 才启用,缺失、`false` 或字符串 `"true"` 保持 WS;短连接和辅助连接不声明。客户端初次上线仍建立 WS 并正常认证、检查和补拉,通过双向空闲与在途任务门禁后才释放 WS。Node 使用内置 `node:dgram`,无需新增运行依赖。原定 Ubuntu Docker 单域 11 场景已分轮通过,新增动态心跳与 human 准入仍待增量实机验证,容量未验。
1144
+
1145
+ 准备阶段通过 `AIDStore.resolve(...)` 的 `agent_md.frontmatter.type` 识别自身 AgentMD 类型;已知 `human` 保持 WS,未知沿用原准入,不以注册意图替代本次 AgentMD 结果。UDP 心跳不查询 AgentMD。
1146
+
1147
+ `HEARTBEAT_ACK` 正文固定为 `lease_remaining_ms:uint32 + pending_generation:uint64 + reason_mask:uint32 + heartbeat_interval_ms:uint32`,共 20 字节,其他报文结构不变。Gateway 每次收到有效 HEARTBEAT 都返回当前周期 T 并按 4T 续租,SDK 按 T 的 ±20% 抖动安排下一次心跳;动态配置更新不追溯缩短已发租约。SDK 连续 3 次响应超时后恢复 WS;默认 T=25 秒、单次等待 5 秒,服务端正常窗口 100 秒,授权、票据或目录期限可更早到达。正常切换不产生应用离线回调,任一有效 WS/UDP 在线即保持应用在线;未新增切换完成回调或公开字段。
1148
+
1149
+ 应用仅在确定不依赖瞬时通知时,显式选择可恢复事件;以下示例改变服务端事件过滤,不适用于需要全事件的应用:
1150
+
1151
+ ```python
1152
+ await client.connect()
1153
+ await client.call("event.subscribe", {
1154
+ "events": ["message.received", "group.message_created"],
1155
+ })
1156
+ ```
1157
+
1158
+ TypeScript 使用 `client.call()`,Go 使用 `client.Call(ctx, ...)` 调用同一 RPC。`event.subscribe` / `event.unsubscribe` 的 `subscribed` 返回完整过滤集合;默认全事件、queue、流式和非白名单事件会话继续保持 WS,SDK 不自动缩窄已有订阅。TypeScript/Node 可通过 `connect({udp_standby: false})` 禁止本次连接声明该能力;不要将该选项直接套用到其他 SDK。
1159
+
1160
+ 进入待机后,`call`、消息发送、认证刷新等真实网络需求会立即恢复 WS,多个并发调用共享恢复过程,各自保留原 deadline 与取消语义;缓存命中的本地读取不唤醒。服务端消息提示也通过恢复 WS 后的原 Pull/ACK 流程处理。WAKE_ACK 不代表业务消费,UDP 不发送正文、不延长消息 TTL、不重放已经发出的写请求。
1161
+
1162
+ 为支持连续待机,SDK 使用 `gateway.standby.prepare` 返回的 `event_subscriptions` 权威快照,在恢复 `auth.connect.options.event_subscriptions` 中提交;Gateway 从新会话首次事件路由起应用该过滤。未传字段仍为原全事件语义,显式 `[]` 为无事件。应用不需要每次唤醒后手工重新订阅,也不应自行重放待机票据或认证签名。
1163
+
1164
+ `disconnect()` / `close()` 停止本实例的待机与自动恢复,不影响同主机其他 AID。UDP 验证失败、连续无应答、租约/票据到期或 Gateway 故障时回 WS 并遵守冷却与原重连限制;UDP 不能唤醒已退出的进程。协议、事件白名单、错误原因和部署方式见 [Gateway 连接模式 §3.10](../../../docs/AUN文档/AUN协议/03-Gateway-连接模式.md#310-aid-空闲-udp-待机测试能力),状态机与验收边界见 [详细设计](../../../docs/AUN文档/AID空闲UDP待机详细设计.md)。
1165
+