@agentunion/fastaun-browser 0.4.2 → 0.4.4
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 +190 -164
- package/_packed_docs/0.4.0_/345/267/256/345/274/202/346/240/270/345/256/236/345/206/263/347/255/226/350/256/260/345/275/225.md +302 -0
- package/_packed_docs/AUN_SDK_0.4.0_/350/256/276/350/256/241/345/257/271/346/257/224/345/210/206/346/236/220.md +194 -0
- package/_packed_docs/AUN_SDK_/351/207/215/346/236/204/345/256/236/346/226/275/350/256/241/345/210/222.md +596 -596
- package/_packed_docs/AUN_SDK_/351/207/215/346/236/204/350/256/276/350/256/241/346/226/271/346/241/210_v3.md +1698 -1697
- package/_packed_docs/CHANGELOG.md +190 -164
- package/_packed_docs/INDEX.md +17 -17
- package/_packed_docs/KITE_DOCS_GUIDE.md +11 -11
- package/_packed_docs/agent.md/SCHEMA.md +49 -49
- package/_packed_docs/agent.md/examples/signed-openclaw-lobster.md +22 -22
- 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 +327 -327
- package/_packed_docs/cli/AUN-CLI/350/256/276/350/256/241/346/226/207/346/241/243.md +686 -686
- package/_packed_docs/design/2026-05-22-aun-rpc-trace-enhancement.md +542 -542
- package/_packed_docs/design/E2EE_V2/347/256/200/345/214/226/344/270/2721DH/345/212/240Per-AID_Wrap/346/226/271/346/241/210.md +124 -124
- package/_packed_docs/design//350/267/250/350/257/255/350/250/200/345/256/271/345/231/250E2E/346/265/213/350/257/225/346/226/271/346/241/210.md +665 -665
- 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 +2 -2
- package/_packed_docs/protocol/14-/344/272/244/344/272/222/346/234/272/345/210/266-/345/223/215/345/272/224/346/250/241/345/274/217/344/270/216/350/207/252/344/270/273/346/250/241/345/274/217.md +170 -170
- 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 +419 -419
- package/_packed_docs/protocol/README.md +1 -1
- package/_packed_docs/protocol/aun-docs-guide.md +1 -1
- package/_packed_docs/protocol//351/231/204/345/275/225A-/346/234/257/350/257/255/350/241/250.md +15 -15
- package/_packed_docs/protocol//351/231/204/345/275/225B-/346/211/251/345/261/225/346/200/247/346/214/207/345/215/227.md +4 -4
- package/_packed_docs/protocol//351/231/204/345/275/225J-/345/256/242/346/210/267/347/253/257/346/216/245/345/205/245/347/244/272/344/276/213.md +98 -98
- package/_packed_docs/protocol//351/231/204/345/275/225M-JWT/350/256/244/350/257/201/345/256/236/347/216/260/346/214/207/345/215/227.md +46 -46
- package/_packed_docs/protocol//351/231/204/345/275/225N-/345/210/206/345/270/203/345/274/217Trace/345/215/217/350/256/256.md +257 -257
- package/_packed_docs/python-sdk-v2-only-changelog.md +189 -189
- package/_packed_docs/sdk/01-/345/277/253/351/200/237/345/274/200/345/247/213.md +7 -3
- 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 +3 -1
- 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 +63 -15
- package/_packed_docs/sdk/09-payload-reference.md +13 -13
- package/_packed_docs/sdk/E2EE_V2/346/266/210/346/201/257/351/200/232/344/277/241/346/227/266/345/272/217/345/233/276.md +171 -171
- package/_packed_docs/sdk/README.md +5 -5
- package/dist/aid-store.d.ts.map +1 -1
- package/dist/aid-store.js +5 -6
- package/dist/aid-store.js.map +1 -1
- package/dist/aid.d.ts +2 -1
- package/dist/aid.d.ts.map +1 -1
- package/dist/aid.js +7 -6
- package/dist/aid.js.map +1 -1
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +4 -0
- package/dist/auth.js.map +1 -1
- package/dist/bundle.js +292 -188
- package/dist/client.d.ts +13 -17
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +275 -190
- package/dist/client.js.map +1 -1
- package/dist/config.d.ts +4 -7
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +18 -1
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/keystore/indexeddb.js +5 -5
- package/dist/keystore/indexeddb.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
|
@@ -1,596 +1,596 @@
|
|
|
1
|
-
# AUN SDK 重构实施计划
|
|
2
|
-
|
|
3
|
-
基于《AUN SDK 重构设计方案 v4.0》,本文档细化各阶段具体改动。
|
|
4
|
-
|
|
5
|
-
**目标版本**:0.4.0
|
|
6
|
-
**旧 API 策略**:不保留兼容层,直接替换
|
|
7
|
-
|
|
8
|
-
---
|
|
9
|
-
|
|
10
|
-
## 决策记录
|
|
11
|
-
|
|
12
|
-
| # | 决策 | 结论 |
|
|
13
|
-
|---|------|------|
|
|
14
|
-
| 1 | Python 错误处理风格 | AIDStore/AID 可失败方法返回 Result 字典;AUNClient 的 connect/call 保留异常 |
|
|
15
|
-
| 2 | CustodyNamespace | 保留 HTTP 直连方式,从 AUNClient 上移除,独立为 CustodyClient |
|
|
16
|
-
| 3 | sendV2 等方法 | SDK 内部方法,不对外暴露 |
|
|
17
|
-
| 4 | protected_headers 附加范围 | 只附加到消息类(message.send/group.send)和 thought 类(message.thought.put/group.thought.put) |
|
|
18
|
-
| 5 | 旧 API 保留 | 不保留,直接替换 |
|
|
19
|
-
| 6 | exists() PKI 端点 | URL 不变(与 GET 证书同一端点),服务端增加 HEAD 方法支持 |
|
|
20
|
-
|
|
21
|
-
---
|
|
22
|
-
|
|
23
|
-
## 阶段 0:冻结基线
|
|
24
|
-
|
|
25
|
-
**目标**:确保重构有回归基准,明确改动影响面
|
|
26
|
-
|
|
27
|
-
| 步骤 | 动作 | 产出 |
|
|
28
|
-
|------|------|------|
|
|
29
|
-
| 0.1 | 跑 `python -X utf8 -m pytest tests/unit/ -v --tb=short`,记录通过数 | 基线通过率 |
|
|
30
|
-
| 0.2 | 导出 `__init__.py` 的 `__all__` + AUNClient 所有 public 方法签名 | API 快照 |
|
|
31
|
-
| 0.3 | grep 调用点:`client.auth.`、`client.meta.`、`client.custody.`、`connect(auth`、`list_identities`、`set_agent_md_path`、`FileKeyStore` | 影响面清单 |
|
|
32
|
-
| 0.4 | 盘点 CLI (`aun_cli/__init__.py`) 对旧 API 的依赖 | CLI 改动范围 |
|
|
33
|
-
| 0.5 | 确认 PKI 证书下载端点 URL 格式(从 `AuthFlow.fetch_peer_cert` 提取) | exists() 实现依据 |
|
|
34
|
-
|
|
35
|
-
---
|
|
36
|
-
|
|
37
|
-
## 阶段 1:基础类型层(零破坏)
|
|
38
|
-
|
|
39
|
-
**目标**:新增 Result / AID / AIDStore 骨架,可独立使用,现有代码不动
|
|
40
|
-
|
|
41
|
-
### 1.1 新增 `result.py`
|
|
42
|
-
|
|
43
|
-
| 项 | 内容 |
|
|
44
|
-
|----|------|
|
|
45
|
-
| `Result` | 泛型 dataclass:`ok: bool`、`data: T | None`、`error: ErrorInfo | None` |
|
|
46
|
-
| `ErrorInfo` | dataclass:`code: str`、`message: str`、`cause: Exception | None` |
|
|
47
|
-
| `result_ok(data)` | 工厂函数 |
|
|
48
|
-
| `result_err(code, message, cause=None)` | 工厂函数 |
|
|
49
|
-
|
|
50
|
-
### 1.2 新增 `error_codes.py`
|
|
51
|
-
|
|
52
|
-
按设计方案 2.10 定义字符串常量:
|
|
53
|
-
|
|
54
|
-
- 加载阶段:`CERT_NOT_FOUND` / `CERT_PARSE_ERROR` / `CERT_EXPIRED` / `CERT_NOT_YET_VALID` / `CERT_CHAIN_BROKEN` / `KEYPAIR_MISMATCH` / `PRIVATE_KEY_PARSE_ERROR`
|
|
55
|
-
- 注册阶段:`IDENTITY_CONFLICT` / `INVALID_AID_FORMAT` / `NETWORK_ERROR` / `SERVER_ERROR`
|
|
56
|
-
- agent.md 阶段:`AGENTMD_NOT_FOUND` / `AGENTMD_PARSE_ERROR` / `SIGNATURE_NOT_FOUND` / `SIGNATURE_INVALID` / `CERT_FINGERPRINT_MISMATCH`
|
|
57
|
-
- 证书运维:`CERT_RENEWAL_FAILED` / `REKEY_FAILED` / `PRIVATE_KEY_REQUIRED`
|
|
58
|
-
- 密码学操作:`SIGNATURE_OPERATION_ERROR` / `VERIFICATION_OPERATION_ERROR` / `CERT_NOT_VALID` / `PRIVATE_KEY_NOT_VALID`
|
|
59
|
-
|
|
60
|
-
### 1.3 新增 `aid.py` — AID 值对象
|
|
61
|
-
|
|
62
|
-
**构造(内部)**:`_AID(aid, aun_path, cert_pem, cert_obj, private_key_obj?, cert_valid, pk_valid)` — 外部不可直接 new
|
|
63
|
-
|
|
64
|
-
**只读属性**:
|
|
65
|
-
|
|
66
|
-
| 属性 | 来源 |
|
|
67
|
-
|------|------|
|
|
68
|
-
| `aid: str` | 传入 |
|
|
69
|
-
| `aun_path: str` | 传入 |
|
|
70
|
-
| `cert_pem: str` | 传入 |
|
|
71
|
-
| `public_key: str` | `cert_obj.public_key()` → DER → base64 |
|
|
72
|
-
| `cert_subject: str` | `cert_obj.subject` CN |
|
|
73
|
-
| `cert_not_before: datetime` | `cert_obj.not_valid_before_utc` |
|
|
74
|
-
| `cert_not_after: datetime` | `cert_obj.not_valid_after_utc` |
|
|
75
|
-
| `cert_issuer: str` | `cert_obj.issuer` CN |
|
|
76
|
-
| `cert_fingerprint: str` | `sha256:` + hex |
|
|
77
|
-
|
|
78
|
-
**方法**:
|
|
79
|
-
|
|
80
|
-
| 方法 | 前置条件 | 返回 | 实现来源 |
|
|
81
|
-
|------|---------|------|---------|
|
|
82
|
-
| `is_cert_valid()` | — | `bool` | 构造时计算并缓存 |
|
|
83
|
-
| `is_private_key_valid()` | — | `bool` | 构造时计算并缓存 |
|
|
84
|
-
| `sign(payload: bytes)` | `is_private_key_valid()` | `Result[{signature: str}]` | ECDSA P-256 SHA-256,base64 输出 |
|
|
85
|
-
| `verify(payload: bytes, signature: str)` | `is_cert_valid()` | `Result[{valid: bool}]` | ECDSA verify |
|
|
86
|
-
| `sign_agent_md(content: str)` | `is_private_key_valid()` | `Result[{signed: str}]` | 从 `auth_namespace.py` 提取签名块拼接 |
|
|
87
|
-
| `verify_agent_md(content: str)` | `is_cert_valid()` | `Result[VerifyResult]` | 从 `_parse_agent_md_tail_signature` + ECDSA 提取 |
|
|
88
|
-
|
|
89
|
-
**需从现有代码提取的纯函数**(放 `_cert_utils.py`):
|
|
90
|
-
- `_parse_agent_md_tail_signature()` — 来自 `auth_namespace.py:40`
|
|
91
|
-
- `_verify_signature()` — 来自 `auth.py`
|
|
92
|
-
- `_build_signature_block()` — 来自 `auth_namespace.py` 签名拼接逻辑
|
|
93
|
-
- `_validate_cert_chain()` — 从 `AuthFlow` 提取链验证为独立函数
|
|
94
|
-
|
|
95
|
-
### 1.4 新增 `aid_store.py` — AIDStore 骨架(仅离线方法)
|
|
96
|
-
|
|
97
|
-
**构造**:`AIDStore(aun_path, encryption_seed, device_id=None, slot_id='default')`
|
|
98
|
-
|
|
99
|
-
内部创建:`FileKeyStore`、`GatewayDiscovery`、`DnsResilientNet`
|
|
100
|
-
|
|
101
|
-
**阶段 1 实现的方法**:
|
|
102
|
-
|
|
103
|
-
| 方法 | 联网 | 实现要点 |
|
|
104
|
-
|------|:----:|---------|
|
|
105
|
-
| `load(aid) → Result[{aid: AID}]` | 否 | 读 cert + 读 private key + 链验证 + 签名自检 → 构造 AID |
|
|
106
|
-
| `list() → Result[{identities: list[AIDInfo]}]` | 否 | 扫描 `{aun_path}/AIDs/`,过滤有私钥的 |
|
|
107
|
-
| `change_seed(old_seed, new_seed) → Result[{changed, count}]` | 否 | 复用 FileKeyStore seed migration |
|
|
108
|
-
|
|
109
|
-
**`load()` 内部流程**(对应设计方案 2.11):
|
|
110
|
-
|
|
111
|
-
```
|
|
112
|
-
1. 读 {aun_path}/AIDs/{aid}/public/certs/ → 无文件 → CERT_NOT_FOUND
|
|
113
|
-
2. 解析 PEM → 失败 → CERT_PARSE_ERROR
|
|
114
|
-
3. 有效期检查 → 过期 → CERT_EXPIRED / 未生效 → CERT_NOT_YET_VALID
|
|
115
|
-
4. 链验证(需根证书)→ 失败 → CERT_CHAIN_BROKEN
|
|
116
|
-
5. 读 {aun_path}/AIDs/{aid}/private/key.pem → 无文件 → 返回 PeerOnly AID
|
|
117
|
-
6. 解析私钥 → 失败 → PRIVATE_KEY_PARSE_ERROR
|
|
118
|
-
7. 签名自检(sign + verify)→ 失败 → KEYPAIR_MISMATCH
|
|
119
|
-
8. 返回完整 AID(is_private_key_valid()=True)
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
### 1.5 导出
|
|
123
|
-
|
|
124
|
-
`__init__.py` 新增导出:`AIDStore`、`AID`、`Result`、`ErrorInfo`、`result_ok`、`result_err`
|
|
125
|
-
|
|
126
|
-
### 1.6 单测
|
|
127
|
-
|
|
128
|
-
| 文件 | 覆盖 |
|
|
129
|
-
|------|------|
|
|
130
|
-
| `tests/unit/test_result.py` | Result 构造、ok/err 判断 |
|
|
131
|
-
| `tests/unit/test_aid.py` | sign/verify/sign_agent_md/verify_agent_md、is_cert_valid/is_private_key_valid |
|
|
132
|
-
| `tests/unit/test_aid_store_offline.py` | load 成功/各种失败、list、change_seed |
|
|
133
|
-
|
|
134
|
-
---
|
|
135
|
-
|
|
136
|
-
## 阶段 2:AIDStore 联网方法
|
|
137
|
-
|
|
138
|
-
**目标**:AIDStore 完整可用,覆盖注册、存在性检查、对端解析、agent.md、证书运维
|
|
139
|
-
|
|
140
|
-
### 2.1 gateway 发现提取
|
|
141
|
-
|
|
142
|
-
从 `AuthNamespace._resolve_gateway()` (`auth_namespace.py:120-175`) 提取到 `AIDStore._resolve_gateway(aid)`:
|
|
143
|
-
- 内部持有 `GatewayDiscovery` 实例
|
|
144
|
-
- 缓存逻辑(keystore metadata)保留
|
|
145
|
-
- 不再依赖 `self._client`
|
|
146
|
-
|
|
147
|
-
### 2.2 联网方法
|
|
148
|
-
|
|
149
|
-
| 方法 | 内部复用 | 关键改动 |
|
|
150
|
-
|------|---------|---------|
|
|
151
|
-
| `register(aid) → Result[{registered: True}]` | `AuthFlow.register_aid(gateway_url, aid)` | AIDStore 内部持有 AuthFlow 实例 |
|
|
152
|
-
| `exists(aid) → Result[{exists: bool}]` | **新实现** | HEAD PKI 证书端点(URL 与 GET 相同),200→存在,404→不存在 |
|
|
153
|
-
| `resolve(aid, opts?) → Result[ResolveData]` | `AuthFlow.fetch_peer_cert()` + agent.md 下载 + `AID.verify_agent_md()` | 组合调用 |
|
|
154
|
-
| `fetch_agent_md(aid) → Result[AgentMdFetchData]` | 从 `auth_namespace.py:428` 提取 HTTP GET + 验签 | 下载 + 拉证书 + 验签 |
|
|
155
|
-
| `head_agent_md(aid) → Result[{etag, last_modified, content_length}]` | 新实现 HEAD 请求 | 判断名片是否发布 |
|
|
156
|
-
| `check_agent_md(aid, ttl_days?) → Result[{needs_update, ...}]` | 从 `client.py:862` 提取 etag 比对 | 本地 vs 远端 |
|
|
157
|
-
| `renew_cert(aid) → Result[{renewed, new_cert_not_after}]` | AuthFlow 续签逻辑 | 需 gateway + 私钥签名 |
|
|
158
|
-
| `rekey(aid) → Result[{rekeyed, new_fingerprint}]` | AuthFlow 换钥逻辑 | 生成新 keypair → 服务端换证书 |
|
|
159
|
-
| `diagnose(aid) → Result[DiagnoseData]` | `load()` + `exists()` 组合 | 本地+远端对比 |
|
|
160
|
-
|
|
161
|
-
### 2.3 `exists()` 实现
|
|
162
|
-
|
|
163
|
-
```python
|
|
164
|
-
async def exists(self, aid: str) -> Result:
|
|
165
|
-
url = self._pki_cert_url(aid) # 与 fetch_peer_cert 用同一个 URL
|
|
166
|
-
try:
|
|
167
|
-
async with aiohttp.ClientSession() as session:
|
|
168
|
-
async with session.head(url, ssl=self._ssl_context, timeout=10) as resp:
|
|
169
|
-
if resp.status == 200:
|
|
170
|
-
return result_ok({"exists": True})
|
|
171
|
-
elif resp.status == 404:
|
|
172
|
-
return result_ok({"exists": False})
|
|
173
|
-
else:
|
|
174
|
-
return result_err("NETWORK_ERROR", f"unexpected status {resp.status}")
|
|
175
|
-
except Exception as e:
|
|
176
|
-
return result_err("NETWORK_ERROR", str(e), cause=e)
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
### 2.4 `resolve()` 流程
|
|
180
|
-
|
|
181
|
-
```
|
|
182
|
-
1. 检查本地证书缓存(load(aid))
|
|
183
|
-
2. 缓存未命中或 force_refresh → 下载证书(GET PKI 端点)
|
|
184
|
-
3. 验证证书 + 落盘缓存
|
|
185
|
-
4. 若 skip_agent_md=True → 返回(仅证书)
|
|
186
|
-
5. 下载 agent.md(GET https://{aid}/agent.md)
|
|
187
|
-
6. 验证 agent.md 签名(用证书公钥)
|
|
188
|
-
7. 返回 ResolveData
|
|
189
|
-
```
|
|
190
|
-
|
|
191
|
-
### 2.5 服务端配合
|
|
192
|
-
|
|
193
|
-
`exists()` 需要服务端 PKI 端点支持 HEAD 方法。URL 不变,只加 HEAD 路由。
|
|
194
|
-
|
|
195
|
-
### 2.6 测试
|
|
196
|
-
|
|
197
|
-
| 文件 | 覆盖 |
|
|
198
|
-
|------|------|
|
|
199
|
-
| `tests/unit/test_aid_store_network.py` | mock HTTP 测试 register/exists/resolve/fetch_agent_md |
|
|
200
|
-
| `tests/integration_test_aid_store.py` | Docker 单域:register → load → exists → resolve |
|
|
201
|
-
|
|
202
|
-
---
|
|
203
|
-
|
|
204
|
-
## 阶段 3:AUNClient 状态机重构
|
|
205
|
-
|
|
206
|
-
**目标**:新构造方式 + 9 态状态机,不保留旧 API
|
|
207
|
-
|
|
208
|
-
### 3.1 状态枚举替换
|
|
209
|
-
|
|
210
|
-
`types.py` 中 `ConnectionState` 改为:
|
|
211
|
-
|
|
212
|
-
```python
|
|
213
|
-
class ConnectionState(str, Enum):
|
|
214
|
-
NO_IDENTITY = "no_identity"
|
|
215
|
-
STANDBY = "standby"
|
|
216
|
-
AUTHENTICATED = "authenticated"
|
|
217
|
-
CONNECTING = "connecting"
|
|
218
|
-
READY = "ready"
|
|
219
|
-
RETRY_BACKOFF = "retry_backoff"
|
|
220
|
-
RECONNECTING = "reconnecting"
|
|
221
|
-
CONNECTION_FAILED = "connection_failed"
|
|
222
|
-
CLOSED = "closed"
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
**旧 → 新映射**:
|
|
226
|
-
- `idle` → `no_identity`(无身份)/ `standby`(有身份)
|
|
227
|
-
- `connecting` / `authenticating` → `connecting`(authenticating 不再对外暴露)
|
|
228
|
-
- `connected` → `ready`
|
|
229
|
-
- `disconnected` → `standby`
|
|
230
|
-
- `reconnecting` → `reconnecting` / `retry_backoff`
|
|
231
|
-
- `terminal_failed` → `connection_failed`
|
|
232
|
-
- `closed` → `closed`
|
|
233
|
-
|
|
234
|
-
### 3.2 构造函数
|
|
235
|
-
|
|
236
|
-
```python
|
|
237
|
-
class AUNClient:
|
|
238
|
-
def __init__(
|
|
239
|
-
self,
|
|
240
|
-
aid: AID | None = None,
|
|
241
|
-
*,
|
|
242
|
-
debug: bool = False,
|
|
243
|
-
protected_headers: dict[str, str] | None = None,
|
|
244
|
-
):
|
|
245
|
-
```
|
|
246
|
-
|
|
247
|
-
- 传入有效本地 AID(`is_private_key_valid() == True`)→ 进入 `standby`
|
|
248
|
-
- 不传 → 进入 `no_identity`
|
|
249
|
-
- 内部从 `aid.aun_path` 获取路径,创建 `RPCTransport`、`EventDispatcher`
|
|
250
|
-
- 内部持有 `AuthFlow` 实例(用于 authenticate/connect)
|
|
251
|
-
- 不再创建 `AuthNamespace` / `MetaNamespace` / `CustodyNamespace`
|
|
252
|
-
|
|
253
|
-
### 3.3 状态推进方法
|
|
254
|
-
|
|
255
|
-
| 方法 | 前置状态 | 目标状态 | 实现要点 |
|
|
256
|
-
|------|---------|---------|---------|
|
|
257
|
-
| `load_identity(aid: AID)` | no_identity / closed | standby | 校验 `aid.is_private_key_valid()`,设置 `_current_aid`,重建 AuthFlow |
|
|
258
|
-
| `authenticate()` | standby | authenticated | 调 `AuthFlow.authenticate()`,存 token |
|
|
259
|
-
| `connect(opts?)` | standby / authenticated / retry_backoff / connection_failed | connecting → ready | standby 时内部先 authenticate |
|
|
260
|
-
| `disconnect()` | authenticated 及以上 | standby | 关 WS,取消重连 task,清 token |
|
|
261
|
-
| `close()` | 任意 | closed | 关 WS,取消所有 task,清身份 |
|
|
262
|
-
|
|
263
|
-
### 3.4 Capability getters
|
|
264
|
-
|
|
265
|
-
| getter | 实现 |
|
|
266
|
-
|--------|------|
|
|
267
|
-
| `has_identity` | `state not in (NO_IDENTITY, CLOSED)` |
|
|
268
|
-
| `can_sign` | `has_identity and _current_aid.is_private_key_valid()` |
|
|
269
|
-
| `can_connect` | `has_identity and state != CLOSED` |
|
|
270
|
-
| `can_send` | `state == READY` |
|
|
271
|
-
| `is_ready` | `state == READY` |
|
|
272
|
-
| `is_online` | `state in (READY, RETRY_BACKOFF, RECONNECTING)` |
|
|
273
|
-
| `is_closed` | `state == CLOSED` |
|
|
274
|
-
| `current_aid` | `_current_aid if has_identity else None` |
|
|
275
|
-
| `aun_path` | `_current_aid.aun_path if has_identity else None` |
|
|
276
|
-
| `next_retry_at` | `_next_retry_at if state == RETRY_BACKOFF else None` |
|
|
277
|
-
| `next_retry_in_seconds` | `max(0, _next_retry_at - time.time()) if ... else None` |
|
|
278
|
-
| `retry_attempt` | `_retry_attempt` |
|
|
279
|
-
| `retry_max_attempts` | `_retry_max_attempts` |
|
|
280
|
-
| `last_error` | `_last_error` |
|
|
281
|
-
| `last_error_code` | `_last_error_code` |
|
|
282
|
-
| `gateway_health` | `_gateway_health` |
|
|
283
|
-
|
|
284
|
-
### 3.5 重连逻辑改造
|
|
285
|
-
|
|
286
|
-
当前 `_reconnect_loop` 改为:
|
|
287
|
-
|
|
288
|
-
```
|
|
289
|
-
网络断开 → state = RETRY_BACKOFF, 记录 _next_retry_at
|
|
290
|
-
↓ 退避到期
|
|
291
|
-
state = RECONNECTING, 尝试连接
|
|
292
|
-
↓ 成功 → state = READY
|
|
293
|
-
↓ 失败且有次数 → state = RETRY_BACKOFF(递增退避)
|
|
294
|
-
↓ 失败且耗尽 → state = CONNECTION_FAILED, 记录 _last_error/_last_error_code
|
|
295
|
-
```
|
|
296
|
-
|
|
297
|
-
- 用户在 `RETRY_BACKOFF` 调 `connect()` → 跳过退避,立即 `RECONNECTING`
|
|
298
|
-
- 用户在 `CONNECTION_FAILED` 调 `connect()` → 重置计数器,进入 `CONNECTING`
|
|
299
|
-
|
|
300
|
-
### 3.6 `call()` 方法
|
|
301
|
-
|
|
302
|
-
```python
|
|
303
|
-
async def call(self, method: str, params: dict | None = None, *, trace: str | None = None) -> Any:
|
|
304
|
-
if self._state != ConnectionState.READY:
|
|
305
|
-
raise StateError(f"call not allowed in state {self._state}")
|
|
306
|
-
if method in _INTERNAL_ONLY_METHODS:
|
|
307
|
-
raise PermissionError(f"method is internal_only: {method}")
|
|
308
|
-
|
|
309
|
-
merged = dict(params or {})
|
|
310
|
-
# 只在消息类和 thought 类附加 protected_headers
|
|
311
|
-
if self._instance_protected_headers and method in _PROTECTED_HEADERS_METHODS:
|
|
312
|
-
existing = merged.get("protected_headers") or {}
|
|
313
|
-
merged["protected_headers"] = {**self._instance_protected_headers, **existing}
|
|
314
|
-
|
|
315
|
-
return await self._transport.call(method, merged, timeout=..., trace=trace)
|
|
316
|
-
```
|
|
317
|
-
|
|
318
|
-
```python
|
|
319
|
-
_PROTECTED_HEADERS_METHODS = frozenset({
|
|
320
|
-
"message.send", "group.send",
|
|
321
|
-
"message.thought.put", "group.thought.put",
|
|
322
|
-
})
|
|
323
|
-
```
|
|
324
|
-
|
|
325
|
-
### 3.7 `set_protected_headers` / `get_protected_headers`
|
|
326
|
-
|
|
327
|
-
```python
|
|
328
|
-
def set_protected_headers(self, headers: dict[str, str] | None) -> None:
|
|
329
|
-
self._instance_protected_headers = dict(headers) if headers else None
|
|
330
|
-
|
|
331
|
-
def get_protected_headers(self) -> dict[str, str] | None:
|
|
332
|
-
return dict(self._instance_protected_headers) if self._instance_protected_headers else None
|
|
333
|
-
```
|
|
334
|
-
|
|
335
|
-
### 3.8 对端管理
|
|
336
|
-
|
|
337
|
-
| 方法 | 实现 |
|
|
338
|
-
|------|------|
|
|
339
|
-
| `lookup_peer(aid)` | 查内存缓存 → 无则调 `self._aid_store.resolve(aid)` → 缓存 → 返回 AID |
|
|
340
|
-
| `get_peer(aid)` | 仅查内存缓存,无则返回 None |
|
|
341
|
-
| `cache_peer(aid: AID)` | 加入内存缓存 |
|
|
342
|
-
| `peers()` | 返回缓存中所有 AID 列表 |
|
|
343
|
-
|
|
344
|
-
### 3.9 V2 E2EE 内部化
|
|
345
|
-
|
|
346
|
-
- 加密/解密逻辑保留为 AUNClient 内部方法
|
|
347
|
-
- `call('message.send', ...)` 时 SDK 内部自动处理 V2 加密
|
|
348
|
-
- 收到消息时内部自动解密,通过 `message.received` 事件交付明文
|
|
349
|
-
- 不再对外暴露 `sendV2()` / `pullV2()` / `ackV2()` 等方法名
|
|
350
|
-
|
|
351
|
-
### 3.10 事件
|
|
352
|
-
|
|
353
|
-
| 事件 | 数据 |
|
|
354
|
-
|------|------|
|
|
355
|
-
| `state-change` | `{"from": str, "to": str}` |
|
|
356
|
-
| `message.received` | `{"from", "payload", "protected_headers?", "context?", ...}` |
|
|
357
|
-
| `group.message_created` | `{"from", "group_id", "payload", "protected_headers?", ...}` |
|
|
358
|
-
| `message.recalled` | `{"message_id", "from", ...}` |
|
|
359
|
-
| `message.undecryptable` | `{"from", "seq", "_decrypt_error", "protected_headers?", ...}` |
|
|
360
|
-
| `group.message_undecryptable` | `{"from", "group_id", "seq", "_decrypt_error", "protected_headers?", ...}` |
|
|
361
|
-
| `token.refreshed` | `{"expires_at"}` |
|
|
362
|
-
| `gateway.disconnect` | `{"reason", "code"}` |
|
|
363
|
-
| `connection.error` | `{"error", "code"}` |
|
|
364
|
-
|
|
365
|
-
### 3.11 测试
|
|
366
|
-
|
|
367
|
-
| 文件 | 覆盖 |
|
|
368
|
-
|------|------|
|
|
369
|
-
| `tests/unit/test_client_state_machine.py` | 所有状态转换路径 |
|
|
370
|
-
| `tests/unit/test_client_capability.py` | 各状态下 getter 返回值 |
|
|
371
|
-
| `tests/unit/test_client_protected_headers.py` | 实例级 headers 合并逻辑 |
|
|
372
|
-
|
|
373
|
-
---
|
|
374
|
-
|
|
375
|
-
## 阶段 4:调用方全量迁移
|
|
376
|
-
|
|
377
|
-
**目标**:CLI、tests、docs 全部迁移到新 API
|
|
378
|
-
|
|
379
|
-
### 4.1 CLI 迁移 (`aun_cli/__init__.py`)
|
|
380
|
-
|
|
381
|
-
所有命令改为新模式:
|
|
382
|
-
|
|
383
|
-
```python
|
|
384
|
-
store = AIDStore(aun_path=..., encryption_seed=...)
|
|
385
|
-
result = await store.register(aid)
|
|
386
|
-
if not result.ok:
|
|
387
|
-
print(f"注册失败: {result.error.code}")
|
|
388
|
-
return
|
|
389
|
-
load_result = await store.load(aid)
|
|
390
|
-
me = load_result.data["aid"]
|
|
391
|
-
client = AUNClient(me, debug=debug)
|
|
392
|
-
await client.connect()
|
|
393
|
-
```
|
|
394
|
-
|
|
395
|
-
### 4.2 单元测试迁移
|
|
396
|
-
|
|
397
|
-
| 旧调用 | 新调用 |
|
|
398
|
-
|--------|--------|
|
|
399
|
-
| `client.auth.register_aid({"aid": aid})` | `store.register(aid)` |
|
|
400
|
-
| `client.auth.load_identity({"aid": aid})` | `store.load(aid)` |
|
|
401
|
-
| `client.auth.authenticate({"aid": aid})` | `client.authenticate()` |
|
|
402
|
-
| `client.connect(auth, opts)` | `client.connect(opts)` |
|
|
403
|
-
| `client.list_identities()` | `store.list()` |
|
|
404
|
-
| `client.auth.sign_agent_md(content)` | `aid.sign_agent_md(content)` |
|
|
405
|
-
| `client.auth.verify_agent_md(content)` | `peer.verify_agent_md(content)` |
|
|
406
|
-
| `client.meta.ping()` | `client.call('meta.ping')` |
|
|
407
|
-
| `client.meta.status()` | `client.call('meta.status')` |
|
|
408
|
-
| `client.fetch_agent_md(aid)` | `store.fetch_agent_md(aid)` |
|
|
409
|
-
| `client.check_agent_md(aid, ttl)` | `store.check_agent_md(aid, ttl_days)` |
|
|
410
|
-
| 状态断言 `ConnectionState.CONNECTED` | `ConnectionState.READY` |
|
|
411
|
-
| 状态断言 `ConnectionState.IDLE` | `ConnectionState.NO_IDENTITY` / `STANDBY` |
|
|
412
|
-
|
|
413
|
-
### 4.3 集成测试 / E2E 测试
|
|
414
|
-
|
|
415
|
-
`tests/integration_test_*.py` 和 `tests/e2e_test_*.py`:
|
|
416
|
-
- 开头创建 `AIDStore`
|
|
417
|
-
- `store.load(aid)` 获取 AID
|
|
418
|
-
- `AUNClient(aid)` 构造
|
|
419
|
-
- `client.connect()` 连接
|
|
420
|
-
- 业务操作用 `client.call('message.send', ...)`
|
|
421
|
-
|
|
422
|
-
### 4.4 双域测试
|
|
423
|
-
|
|
424
|
-
`docker-deploy/federation-test/tests/` 下的脚本同步改。
|
|
425
|
-
|
|
426
|
-
### 4.5 CustodyNamespace 处理
|
|
427
|
-
|
|
428
|
-
从 AUNClient 上移除,独立为 `CustodyClient` 类:
|
|
429
|
-
|
|
430
|
-
```python
|
|
431
|
-
from aun_core.custody import CustodyClient
|
|
432
|
-
|
|
433
|
-
custody = CustodyClient(aid="alice.aid.pub", verify_ssl=False)
|
|
434
|
-
await custody.bind_phone(params)
|
|
435
|
-
```
|
|
436
|
-
|
|
437
|
-
保留 HTTP 直连方式,不走 gateway RPC。
|
|
438
|
-
|
|
439
|
-
### 4.6 MetaNamespace 方法迁移
|
|
440
|
-
|
|
441
|
-
| 旧 | 新 |
|
|
442
|
-
|----|-----|
|
|
443
|
-
| `client.meta.ping()` | `client.call('meta.ping')` |
|
|
444
|
-
| `client.meta.status()` | `client.call('meta.status')` |
|
|
445
|
-
| `client.meta.trust_roots()` | `client.call('meta.trust_roots')` |
|
|
446
|
-
| `client.meta.download_trust_roots(...)` | AIDStore 内部自动处理 |
|
|
447
|
-
| `client.meta.verify_trust_roots(...)` | AIDStore.load() 内部链验证 |
|
|
448
|
-
| `client.meta.import_trust_roots(...)` | AIDStore 构造时自动导入 |
|
|
449
|
-
| `client.meta.refresh_trust_roots(...)` | AIDStore 内部按需刷新 |
|
|
450
|
-
|
|
451
|
-
---
|
|
452
|
-
|
|
453
|
-
## 阶段 5:清理 + 跨语言同步
|
|
454
|
-
|
|
455
|
-
**目标**:删除旧代码,跨语言对齐,发布 0.4.0
|
|
456
|
-
|
|
457
|
-
### 5.1 删除文件
|
|
458
|
-
|
|
459
|
-
| 文件 | 说明 |
|
|
460
|
-
|------|------|
|
|
461
|
-
| `namespaces/auth_namespace.py` | 全部功能已迁移到 AIDStore/AID/AUNClient |
|
|
462
|
-
| `namespaces/meta_namespace.py` | ping/status/trust_roots 改 call();信任根管理内化 |
|
|
463
|
-
| `namespaces/custody_namespace.py` | 独立为 `custody.py` |
|
|
464
|
-
| `namespaces/__init__.py` | 目录可删 |
|
|
465
|
-
|
|
466
|
-
### 5.2 AUNClient 内部清理
|
|
467
|
-
|
|
468
|
-
| 删除项 | 原位置 |
|
|
469
|
-
|--------|--------|
|
|
470
|
-
| `self.auth = AuthNamespace(self)` | 构造函数 |
|
|
471
|
-
| `self.meta = MetaNamespace(self)` | 构造函数 |
|
|
472
|
-
| `self.custody = CustodyNamespace(self)` | 构造函数 |
|
|
473
|
-
| `set_agent_md_path()` / `SetAgentMDPath()` | `client.py:562-572` |
|
|
474
|
-
| `list_identities()` | `client.py:1133` |
|
|
475
|
-
| `check_gateway_health()` | `client.py:1067` |
|
|
476
|
-
| `ping()` / `status()` / `trust_roots()` | `client.py:1661-1667` |
|
|
477
|
-
| `fetch_agent_md()` / `check_agent_md()` | `client.py:862-972` |
|
|
478
|
-
| 旧 `connect(auth, opts)` 签名 | `client.py:1071` |
|
|
479
|
-
|
|
480
|
-
### 5.3 `__init__.py` 最终导出
|
|
481
|
-
|
|
482
|
-
```python
|
|
483
|
-
__all__ = [
|
|
484
|
-
"AIDStore", "AID", "AUNClient",
|
|
485
|
-
"CustodyClient",
|
|
486
|
-
"Result", "ErrorInfo", "result_ok", "result_err",
|
|
487
|
-
"ProtectedHeaders", "ConnectionState", "get_device_id",
|
|
488
|
-
# 异常类(AUNClient connect/call 仍抛异常)
|
|
489
|
-
"AUNError", "AuthError", "ConnectionError", "TimeoutError",
|
|
490
|
-
"StateError", "E2EEError", "GroupError", ...
|
|
491
|
-
]
|
|
492
|
-
```
|
|
493
|
-
|
|
494
|
-
### 5.4 跨语言同步
|
|
495
|
-
|
|
496
|
-
| SDK | 改动范围 | 优先级 |
|
|
497
|
-
|-----|---------|--------|
|
|
498
|
-
| TS (`ts/`) | 新增 `AIDStore`/`AID` 类,重构 `AUNClient` 构造和状态机 | 高 |
|
|
499
|
-
| JS (`js/`) | 同 TS,但 `AIDStore.load` 从 IndexedDB/内存加载,无文件 I/O | 中 |
|
|
500
|
-
| Go (`go/`) | `AIDStore`/`AID` 用 struct + method,Result 用 `(T, error)` 惯用法 | 中 |
|
|
501
|
-
|
|
502
|
-
### 5.5 文档 + 版本
|
|
503
|
-
|
|
504
|
-
- 版本号:`0.4.0`
|
|
505
|
-
- 更新 `docs/sdk/06-API手册.md`
|
|
506
|
-
- 运行 `python/sync_docs.py` 同步到 skill 目录
|
|
507
|
-
- CHANGELOG 记录所有破坏性变更
|
|
508
|
-
|
|
509
|
-
---
|
|
510
|
-
|
|
511
|
-
## 接口迁移对照表
|
|
512
|
-
|
|
513
|
-
| 旧 API | 新 API | 归属 |
|
|
514
|
-
|--------|--------|------|
|
|
515
|
-
| `AUNClient(config, debug)` | `AUNClient(aid, debug=, protected_headers=)` | AUNClient |
|
|
516
|
-
| `client.auth.register_aid({"aid": x})` | `store.register(x)` | AIDStore |
|
|
517
|
-
| `client.auth.load_identity({"aid": x})` | `store.load(x)` | AIDStore |
|
|
518
|
-
| `client.auth.authenticate({"aid": x})` | `client.authenticate()` | AUNClient |
|
|
519
|
-
| `client.connect(auth, opts)` | `client.connect(opts)` | AUNClient |
|
|
520
|
-
| `client.disconnect()` | `client.disconnect()` | AUNClient(不变) |
|
|
521
|
-
| `client.close()` | `client.close()` | AUNClient(不变) |
|
|
522
|
-
| `client.call(method, params)` | `client.call(method, params)` | AUNClient(不变) |
|
|
523
|
-
| `client.on(event, handler)` | `client.on(event, handler)` | AUNClient(不变) |
|
|
524
|
-
| `client.list_identities()` | `store.list()` | AIDStore |
|
|
525
|
-
| `client.fetch_agent_md(aid)` | `store.fetch_agent_md(aid)` | AIDStore |
|
|
526
|
-
| `client.check_agent_md(aid, ttl)` | `store.check_agent_md(aid, ttl_days)` | AIDStore |
|
|
527
|
-
| `client.publish_agent_md()` | `client.publish_agent_md(content?)` | AUNClient |
|
|
528
|
-
| `client.auth.upload_agent_md(content)` | `client.upload_agent_md(content)` | AUNClient |
|
|
529
|
-
| `client.auth.sign_agent_md(content)` | `aid.sign_agent_md(content)` | AID |
|
|
530
|
-
| `client.auth.verify_agent_md(content)` | `peer.verify_agent_md(content)` | AID |
|
|
531
|
-
| `client.auth.fetch_peer_cert({"aid": x})` | `store.resolve(x)` | AIDStore |
|
|
532
|
-
| `client.auth.check_aid({"aid": x})` | `store.diagnose(x)` | AIDStore |
|
|
533
|
-
| `client.auth.renew_cert()` | `store.renew_cert(aid)` | AIDStore |
|
|
534
|
-
| `client.auth.rekey()` | `store.rekey(aid)` | AIDStore |
|
|
535
|
-
| `client.meta.ping()` | `client.call('meta.ping')` | RPC 透传 |
|
|
536
|
-
| `client.meta.status()` | `client.call('meta.status')` | RPC 透传 |
|
|
537
|
-
| `client.meta.trust_roots()` | `client.call('meta.trust_roots')` | RPC 透传 |
|
|
538
|
-
| `client.custody.bind_phone(p)` | `CustodyClient(aid).bind_phone(p)` | CustodyClient |
|
|
539
|
-
| `FileKeyStore.change_seed(...)` | `store.change_seed(old, new)` | AIDStore |
|
|
540
|
-
| `client.set_agent_md_path(path)` | 删除(构造参数或 AIDStore 配置) | — |
|
|
541
|
-
| `client.check_gateway_health(url)` | `client.gateway_health` getter | AUNClient |
|
|
542
|
-
| `client.state` | `client.state` | AUNClient(值变更) |
|
|
543
|
-
| `client.aid` | `client.current_aid` | AUNClient |
|
|
544
|
-
|
|
545
|
-
---
|
|
546
|
-
|
|
547
|
-
## 文件变更汇总
|
|
548
|
-
|
|
549
|
-
### 新增文件
|
|
550
|
-
|
|
551
|
-
| 文件 | 说明 |
|
|
552
|
-
|------|------|
|
|
553
|
-
| `python/src/aun_core/result.py` | Result / ErrorInfo / result_ok / result_err |
|
|
554
|
-
| `python/src/aun_core/error_codes.py` | 错误码字符串常量 |
|
|
555
|
-
| `python/src/aun_core/aid.py` | AID 值对象 |
|
|
556
|
-
| `python/src/aun_core/aid_store.py` | AIDStore 管理器 |
|
|
557
|
-
| `python/src/aun_core/_cert_utils.py` | 从现有代码提取的证书/签名纯函数 |
|
|
558
|
-
| `python/src/aun_core/custody.py` | CustodyClient 独立类 |
|
|
559
|
-
| `tests/unit/test_result.py` | Result 单测 |
|
|
560
|
-
| `tests/unit/test_aid.py` | AID 单测 |
|
|
561
|
-
| `tests/unit/test_aid_store_offline.py` | AIDStore 离线单测 |
|
|
562
|
-
| `tests/unit/test_aid_store_network.py` | AIDStore 联网单测 |
|
|
563
|
-
| `tests/unit/test_client_state_machine.py` | 状态机单测 |
|
|
564
|
-
| `tests/unit/test_client_capability.py` | Capability getter 单测 |
|
|
565
|
-
| `tests/unit/test_client_protected_headers.py` | protected_headers 单测 |
|
|
566
|
-
| `tests/integration_test_aid_store.py` | AIDStore 集成测试 |
|
|
567
|
-
|
|
568
|
-
### 修改文件
|
|
569
|
-
|
|
570
|
-
| 文件 | 改动 |
|
|
571
|
-
|------|------|
|
|
572
|
-
| `python/src/aun_core/__init__.py` | 新增导出,移除旧 namespace 导出 |
|
|
573
|
-
| `python/src/aun_core/types.py` | ConnectionState 枚举值替换 |
|
|
574
|
-
| `python/src/aun_core/client.py` | 重构构造函数、状态机、connect、删除 namespace 引用 |
|
|
575
|
-
| `python/src/aun_core/auth.py` | 提取纯函数到 `_cert_utils.py`,AuthFlow 保留供 AIDStore/AUNClient 内部使用 |
|
|
576
|
-
| `python/src/aun_core/keystore/file.py` | 无大改,被 AIDStore 内部调用 |
|
|
577
|
-
| `python/src/aun_cli/__init__.py` | CLI 全量迁移到新 API |
|
|
578
|
-
| `tests/unit/test_auth.py` | 改为测试 AIDStore |
|
|
579
|
-
| `tests/unit/test_client.py` | 改为新构造 + 新状态 |
|
|
580
|
-
| `tests/unit/test_connection_kind.py` | 状态枚举值更新 |
|
|
581
|
-
| `tests/integration_test_*.py` | 全量迁移 |
|
|
582
|
-
| `tests/e2e_test_*.py` | 全量迁移 |
|
|
583
|
-
|
|
584
|
-
### 删除文件
|
|
585
|
-
|
|
586
|
-
| 文件 | 说明 |
|
|
587
|
-
|------|------|
|
|
588
|
-
| `python/src/aun_core/namespaces/auth_namespace.py` | 功能迁移到 AIDStore/AID/AUNClient |
|
|
589
|
-
| `python/src/aun_core/namespaces/meta_namespace.py` | 功能迁移到 call() + AIDStore 内部 |
|
|
590
|
-
| `python/src/aun_core/namespaces/custody_namespace.py` | 独立为 custody.py |
|
|
591
|
-
| `python/src/aun_core/namespaces/__init__.py` | 目录删除 |
|
|
592
|
-
|
|
593
|
-
---
|
|
594
|
-
|
|
595
|
-
**文档版本**:v1.0
|
|
596
|
-
**最后更新**:2026-05-28
|
|
1
|
+
# AUN SDK 重构实施计划
|
|
2
|
+
|
|
3
|
+
基于《AUN SDK 重构设计方案 v4.0》,本文档细化各阶段具体改动。
|
|
4
|
+
|
|
5
|
+
**目标版本**:0.4.0
|
|
6
|
+
**旧 API 策略**:不保留兼容层,直接替换
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 决策记录
|
|
11
|
+
|
|
12
|
+
| # | 决策 | 结论 |
|
|
13
|
+
|---|------|------|
|
|
14
|
+
| 1 | Python 错误处理风格 | AIDStore/AID 可失败方法返回 Result 字典;AUNClient 的 connect/call 保留异常 |
|
|
15
|
+
| 2 | CustodyNamespace | 保留 HTTP 直连方式,从 AUNClient 上移除,独立为 CustodyClient |
|
|
16
|
+
| 3 | sendV2 等方法 | SDK 内部方法,不对外暴露 |
|
|
17
|
+
| 4 | protected_headers 附加范围 | 只附加到消息类(message.send/group.send)和 thought 类(message.thought.put/group.thought.put) |
|
|
18
|
+
| 5 | 旧 API 保留 | 不保留,直接替换 |
|
|
19
|
+
| 6 | exists() PKI 端点 | URL 不变(与 GET 证书同一端点),服务端增加 HEAD 方法支持 |
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## 阶段 0:冻结基线
|
|
24
|
+
|
|
25
|
+
**目标**:确保重构有回归基准,明确改动影响面
|
|
26
|
+
|
|
27
|
+
| 步骤 | 动作 | 产出 |
|
|
28
|
+
|------|------|------|
|
|
29
|
+
| 0.1 | 跑 `python -X utf8 -m pytest tests/unit/ -v --tb=short`,记录通过数 | 基线通过率 |
|
|
30
|
+
| 0.2 | 导出 `__init__.py` 的 `__all__` + AUNClient 所有 public 方法签名 | API 快照 |
|
|
31
|
+
| 0.3 | grep 调用点:`client.auth.`、`client.meta.`、`client.custody.`、`connect(auth`、`list_identities`、`set_agent_md_path`、`FileKeyStore` | 影响面清单 |
|
|
32
|
+
| 0.4 | 盘点 CLI (`aun_cli/__init__.py`) 对旧 API 的依赖 | CLI 改动范围 |
|
|
33
|
+
| 0.5 | 确认 PKI 证书下载端点 URL 格式(从 `AuthFlow.fetch_peer_cert` 提取) | exists() 实现依据 |
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## 阶段 1:基础类型层(零破坏)
|
|
38
|
+
|
|
39
|
+
**目标**:新增 Result / AID / AIDStore 骨架,可独立使用,现有代码不动
|
|
40
|
+
|
|
41
|
+
### 1.1 新增 `result.py`
|
|
42
|
+
|
|
43
|
+
| 项 | 内容 |
|
|
44
|
+
|----|------|
|
|
45
|
+
| `Result` | 泛型 dataclass:`ok: bool`、`data: T | None`、`error: ErrorInfo | None` |
|
|
46
|
+
| `ErrorInfo` | dataclass:`code: str`、`message: str`、`cause: Exception | None` |
|
|
47
|
+
| `result_ok(data)` | 工厂函数 |
|
|
48
|
+
| `result_err(code, message, cause=None)` | 工厂函数 |
|
|
49
|
+
|
|
50
|
+
### 1.2 新增 `error_codes.py`
|
|
51
|
+
|
|
52
|
+
按设计方案 2.10 定义字符串常量:
|
|
53
|
+
|
|
54
|
+
- 加载阶段:`CERT_NOT_FOUND` / `CERT_PARSE_ERROR` / `CERT_EXPIRED` / `CERT_NOT_YET_VALID` / `CERT_CHAIN_BROKEN` / `KEYPAIR_MISMATCH` / `PRIVATE_KEY_PARSE_ERROR`
|
|
55
|
+
- 注册阶段:`IDENTITY_CONFLICT` / `INVALID_AID_FORMAT` / `NETWORK_ERROR` / `SERVER_ERROR`
|
|
56
|
+
- agent.md 阶段:`AGENTMD_NOT_FOUND` / `AGENTMD_PARSE_ERROR` / `SIGNATURE_NOT_FOUND` / `SIGNATURE_INVALID` / `CERT_FINGERPRINT_MISMATCH`
|
|
57
|
+
- 证书运维:`CERT_RENEWAL_FAILED` / `REKEY_FAILED` / `PRIVATE_KEY_REQUIRED`
|
|
58
|
+
- 密码学操作:`SIGNATURE_OPERATION_ERROR` / `VERIFICATION_OPERATION_ERROR` / `CERT_NOT_VALID` / `PRIVATE_KEY_NOT_VALID`
|
|
59
|
+
|
|
60
|
+
### 1.3 新增 `aid.py` — AID 值对象
|
|
61
|
+
|
|
62
|
+
**构造(内部)**:`_AID(aid, aun_path, cert_pem, cert_obj, private_key_obj?, cert_valid, pk_valid)` — 外部不可直接 new
|
|
63
|
+
|
|
64
|
+
**只读属性**:
|
|
65
|
+
|
|
66
|
+
| 属性 | 来源 |
|
|
67
|
+
|------|------|
|
|
68
|
+
| `aid: str` | 传入 |
|
|
69
|
+
| `aun_path: str` | 传入 |
|
|
70
|
+
| `cert_pem: str` | 传入 |
|
|
71
|
+
| `public_key: str` | `cert_obj.public_key()` → DER → base64 |
|
|
72
|
+
| `cert_subject: str` | `cert_obj.subject` CN |
|
|
73
|
+
| `cert_not_before: datetime` | `cert_obj.not_valid_before_utc` |
|
|
74
|
+
| `cert_not_after: datetime` | `cert_obj.not_valid_after_utc` |
|
|
75
|
+
| `cert_issuer: str` | `cert_obj.issuer` CN |
|
|
76
|
+
| `cert_fingerprint: str` | `sha256:` + hex |
|
|
77
|
+
|
|
78
|
+
**方法**:
|
|
79
|
+
|
|
80
|
+
| 方法 | 前置条件 | 返回 | 实现来源 |
|
|
81
|
+
|------|---------|------|---------|
|
|
82
|
+
| `is_cert_valid()` | — | `bool` | 构造时计算并缓存 |
|
|
83
|
+
| `is_private_key_valid()` | — | `bool` | 构造时计算并缓存 |
|
|
84
|
+
| `sign(payload: bytes)` | `is_private_key_valid()` | `Result[{signature: str}]` | ECDSA P-256 SHA-256,base64 输出 |
|
|
85
|
+
| `verify(payload: bytes, signature: str)` | `is_cert_valid()` | `Result[{valid: bool}]` | ECDSA verify |
|
|
86
|
+
| `sign_agent_md(content: str)` | `is_private_key_valid()` | `Result[{signed: str}]` | 从 `auth_namespace.py` 提取签名块拼接 |
|
|
87
|
+
| `verify_agent_md(content: str)` | `is_cert_valid()` | `Result[VerifyResult]` | 从 `_parse_agent_md_tail_signature` + ECDSA 提取 |
|
|
88
|
+
|
|
89
|
+
**需从现有代码提取的纯函数**(放 `_cert_utils.py`):
|
|
90
|
+
- `_parse_agent_md_tail_signature()` — 来自 `auth_namespace.py:40`
|
|
91
|
+
- `_verify_signature()` — 来自 `auth.py`
|
|
92
|
+
- `_build_signature_block()` — 来自 `auth_namespace.py` 签名拼接逻辑
|
|
93
|
+
- `_validate_cert_chain()` — 从 `AuthFlow` 提取链验证为独立函数
|
|
94
|
+
|
|
95
|
+
### 1.4 新增 `aid_store.py` — AIDStore 骨架(仅离线方法)
|
|
96
|
+
|
|
97
|
+
**构造**:`AIDStore(aun_path, encryption_seed, device_id=None, slot_id='default')`
|
|
98
|
+
|
|
99
|
+
内部创建:`FileKeyStore`、`GatewayDiscovery`、`DnsResilientNet`
|
|
100
|
+
|
|
101
|
+
**阶段 1 实现的方法**:
|
|
102
|
+
|
|
103
|
+
| 方法 | 联网 | 实现要点 |
|
|
104
|
+
|------|:----:|---------|
|
|
105
|
+
| `load(aid) → Result[{aid: AID}]` | 否 | 读 cert + 读 private key + 链验证 + 签名自检 → 构造 AID |
|
|
106
|
+
| `list() → Result[{identities: list[AIDInfo]}]` | 否 | 扫描 `{aun_path}/AIDs/`,过滤有私钥的 |
|
|
107
|
+
| `change_seed(old_seed, new_seed) → Result[{changed, count}]` | 否 | 复用 FileKeyStore seed migration |
|
|
108
|
+
|
|
109
|
+
**`load()` 内部流程**(对应设计方案 2.11):
|
|
110
|
+
|
|
111
|
+
```
|
|
112
|
+
1. 读 {aun_path}/AIDs/{aid}/public/certs/ → 无文件 → CERT_NOT_FOUND
|
|
113
|
+
2. 解析 PEM → 失败 → CERT_PARSE_ERROR
|
|
114
|
+
3. 有效期检查 → 过期 → CERT_EXPIRED / 未生效 → CERT_NOT_YET_VALID
|
|
115
|
+
4. 链验证(需根证书)→ 失败 → CERT_CHAIN_BROKEN
|
|
116
|
+
5. 读 {aun_path}/AIDs/{aid}/private/key.pem → 无文件 → 返回 PeerOnly AID
|
|
117
|
+
6. 解析私钥 → 失败 → PRIVATE_KEY_PARSE_ERROR
|
|
118
|
+
7. 签名自检(sign + verify)→ 失败 → KEYPAIR_MISMATCH
|
|
119
|
+
8. 返回完整 AID(is_private_key_valid()=True)
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### 1.5 导出
|
|
123
|
+
|
|
124
|
+
`__init__.py` 新增导出:`AIDStore`、`AID`、`Result`、`ErrorInfo`、`result_ok`、`result_err`
|
|
125
|
+
|
|
126
|
+
### 1.6 单测
|
|
127
|
+
|
|
128
|
+
| 文件 | 覆盖 |
|
|
129
|
+
|------|------|
|
|
130
|
+
| `tests/unit/test_result.py` | Result 构造、ok/err 判断 |
|
|
131
|
+
| `tests/unit/test_aid.py` | sign/verify/sign_agent_md/verify_agent_md、is_cert_valid/is_private_key_valid |
|
|
132
|
+
| `tests/unit/test_aid_store_offline.py` | load 成功/各种失败、list、change_seed |
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## 阶段 2:AIDStore 联网方法
|
|
137
|
+
|
|
138
|
+
**目标**:AIDStore 完整可用,覆盖注册、存在性检查、对端解析、agent.md、证书运维
|
|
139
|
+
|
|
140
|
+
### 2.1 gateway 发现提取
|
|
141
|
+
|
|
142
|
+
从 `AuthNamespace._resolve_gateway()` (`auth_namespace.py:120-175`) 提取到 `AIDStore._resolve_gateway(aid)`:
|
|
143
|
+
- 内部持有 `GatewayDiscovery` 实例
|
|
144
|
+
- 缓存逻辑(keystore metadata)保留
|
|
145
|
+
- 不再依赖 `self._client`
|
|
146
|
+
|
|
147
|
+
### 2.2 联网方法
|
|
148
|
+
|
|
149
|
+
| 方法 | 内部复用 | 关键改动 |
|
|
150
|
+
|------|---------|---------|
|
|
151
|
+
| `register(aid) → Result[{registered: True}]` | `AuthFlow.register_aid(gateway_url, aid)` | AIDStore 内部持有 AuthFlow 实例 |
|
|
152
|
+
| `exists(aid) → Result[{exists: bool}]` | **新实现** | HEAD PKI 证书端点(URL 与 GET 相同),200→存在,404→不存在 |
|
|
153
|
+
| `resolve(aid, opts?) → Result[ResolveData]` | `AuthFlow.fetch_peer_cert()` + agent.md 下载 + `AID.verify_agent_md()` | 组合调用 |
|
|
154
|
+
| `fetch_agent_md(aid) → Result[AgentMdFetchData]` | 从 `auth_namespace.py:428` 提取 HTTP GET + 验签 | 下载 + 拉证书 + 验签 |
|
|
155
|
+
| `head_agent_md(aid) → Result[{etag, last_modified, content_length}]` | 新实现 HEAD 请求 | 判断名片是否发布 |
|
|
156
|
+
| `check_agent_md(aid, ttl_days?) → Result[{needs_update, ...}]` | 从 `client.py:862` 提取 etag 比对 | 本地 vs 远端 |
|
|
157
|
+
| `renew_cert(aid) → Result[{renewed, new_cert_not_after}]` | AuthFlow 续签逻辑 | 需 gateway + 私钥签名 |
|
|
158
|
+
| `rekey(aid) → Result[{rekeyed, new_fingerprint}]` | AuthFlow 换钥逻辑 | 生成新 keypair → 服务端换证书 |
|
|
159
|
+
| `diagnose(aid) → Result[DiagnoseData]` | `load()` + `exists()` 组合 | 本地+远端对比 |
|
|
160
|
+
|
|
161
|
+
### 2.3 `exists()` 实现
|
|
162
|
+
|
|
163
|
+
```python
|
|
164
|
+
async def exists(self, aid: str) -> Result:
|
|
165
|
+
url = self._pki_cert_url(aid) # 与 fetch_peer_cert 用同一个 URL
|
|
166
|
+
try:
|
|
167
|
+
async with aiohttp.ClientSession() as session:
|
|
168
|
+
async with session.head(url, ssl=self._ssl_context, timeout=10) as resp:
|
|
169
|
+
if resp.status == 200:
|
|
170
|
+
return result_ok({"exists": True})
|
|
171
|
+
elif resp.status == 404:
|
|
172
|
+
return result_ok({"exists": False})
|
|
173
|
+
else:
|
|
174
|
+
return result_err("NETWORK_ERROR", f"unexpected status {resp.status}")
|
|
175
|
+
except Exception as e:
|
|
176
|
+
return result_err("NETWORK_ERROR", str(e), cause=e)
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### 2.4 `resolve()` 流程
|
|
180
|
+
|
|
181
|
+
```
|
|
182
|
+
1. 检查本地证书缓存(load(aid))
|
|
183
|
+
2. 缓存未命中或 force_refresh → 下载证书(GET PKI 端点)
|
|
184
|
+
3. 验证证书 + 落盘缓存
|
|
185
|
+
4. 若 skip_agent_md=True → 返回(仅证书)
|
|
186
|
+
5. 下载 agent.md(GET https://{aid}/agent.md)
|
|
187
|
+
6. 验证 agent.md 签名(用证书公钥)
|
|
188
|
+
7. 返回 ResolveData
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### 2.5 服务端配合
|
|
192
|
+
|
|
193
|
+
`exists()` 需要服务端 PKI 端点支持 HEAD 方法。URL 不变,只加 HEAD 路由。
|
|
194
|
+
|
|
195
|
+
### 2.6 测试
|
|
196
|
+
|
|
197
|
+
| 文件 | 覆盖 |
|
|
198
|
+
|------|------|
|
|
199
|
+
| `tests/unit/test_aid_store_network.py` | mock HTTP 测试 register/exists/resolve/fetch_agent_md |
|
|
200
|
+
| `tests/integration_test_aid_store.py` | Docker 单域:register → load → exists → resolve |
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## 阶段 3:AUNClient 状态机重构
|
|
205
|
+
|
|
206
|
+
**目标**:新构造方式 + 9 态状态机,不保留旧 API
|
|
207
|
+
|
|
208
|
+
### 3.1 状态枚举替换
|
|
209
|
+
|
|
210
|
+
`types.py` 中 `ConnectionState` 改为:
|
|
211
|
+
|
|
212
|
+
```python
|
|
213
|
+
class ConnectionState(str, Enum):
|
|
214
|
+
NO_IDENTITY = "no_identity"
|
|
215
|
+
STANDBY = "standby"
|
|
216
|
+
AUTHENTICATED = "authenticated"
|
|
217
|
+
CONNECTING = "connecting"
|
|
218
|
+
READY = "ready"
|
|
219
|
+
RETRY_BACKOFF = "retry_backoff"
|
|
220
|
+
RECONNECTING = "reconnecting"
|
|
221
|
+
CONNECTION_FAILED = "connection_failed"
|
|
222
|
+
CLOSED = "closed"
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
**旧 → 新映射**:
|
|
226
|
+
- `idle` → `no_identity`(无身份)/ `standby`(有身份)
|
|
227
|
+
- `connecting` / `authenticating` → `connecting`(authenticating 不再对外暴露)
|
|
228
|
+
- `connected` → `ready`
|
|
229
|
+
- `disconnected` → `standby`
|
|
230
|
+
- `reconnecting` → `reconnecting` / `retry_backoff`
|
|
231
|
+
- `terminal_failed` → `connection_failed`
|
|
232
|
+
- `closed` → `closed`
|
|
233
|
+
|
|
234
|
+
### 3.2 构造函数
|
|
235
|
+
|
|
236
|
+
```python
|
|
237
|
+
class AUNClient:
|
|
238
|
+
def __init__(
|
|
239
|
+
self,
|
|
240
|
+
aid: AID | None = None,
|
|
241
|
+
*,
|
|
242
|
+
debug: bool = False,
|
|
243
|
+
protected_headers: dict[str, str] | None = None,
|
|
244
|
+
):
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
- 传入有效本地 AID(`is_private_key_valid() == True`)→ 进入 `standby`
|
|
248
|
+
- 不传 → 进入 `no_identity`
|
|
249
|
+
- 内部从 `aid.aun_path` 获取路径,创建 `RPCTransport`、`EventDispatcher`
|
|
250
|
+
- 内部持有 `AuthFlow` 实例(用于 authenticate/connect)
|
|
251
|
+
- 不再创建 `AuthNamespace` / `MetaNamespace` / `CustodyNamespace`
|
|
252
|
+
|
|
253
|
+
### 3.3 状态推进方法
|
|
254
|
+
|
|
255
|
+
| 方法 | 前置状态 | 目标状态 | 实现要点 |
|
|
256
|
+
|------|---------|---------|---------|
|
|
257
|
+
| `load_identity(aid: AID)` | no_identity / closed | standby | 校验 `aid.is_private_key_valid()`,设置 `_current_aid`,重建 AuthFlow |
|
|
258
|
+
| `authenticate()` | standby | authenticated | 调 `AuthFlow.authenticate()`,存 token |
|
|
259
|
+
| `connect(opts?)` | standby / authenticated / retry_backoff / connection_failed | connecting → ready | standby 时内部先 authenticate |
|
|
260
|
+
| `disconnect()` | authenticated 及以上 | standby | 关 WS,取消重连 task,清 token |
|
|
261
|
+
| `close()` | 任意 | closed | 关 WS,取消所有 task,清身份 |
|
|
262
|
+
|
|
263
|
+
### 3.4 Capability getters
|
|
264
|
+
|
|
265
|
+
| getter | 实现 |
|
|
266
|
+
|--------|------|
|
|
267
|
+
| `has_identity` | `state not in (NO_IDENTITY, CLOSED)` |
|
|
268
|
+
| `can_sign` | `has_identity and _current_aid.is_private_key_valid()` |
|
|
269
|
+
| `can_connect` | `has_identity and state != CLOSED` |
|
|
270
|
+
| `can_send` | `state == READY` |
|
|
271
|
+
| `is_ready` | `state == READY` |
|
|
272
|
+
| `is_online` | `state in (READY, RETRY_BACKOFF, RECONNECTING)` |
|
|
273
|
+
| `is_closed` | `state == CLOSED` |
|
|
274
|
+
| `current_aid` | `_current_aid if has_identity else None` |
|
|
275
|
+
| `aun_path` | `_current_aid.aun_path if has_identity else None` |
|
|
276
|
+
| `next_retry_at` | `_next_retry_at if state == RETRY_BACKOFF else None` |
|
|
277
|
+
| `next_retry_in_seconds` | `max(0, _next_retry_at - time.time()) if ... else None` |
|
|
278
|
+
| `retry_attempt` | `_retry_attempt` |
|
|
279
|
+
| `retry_max_attempts` | `_retry_max_attempts` |
|
|
280
|
+
| `last_error` | `_last_error` |
|
|
281
|
+
| `last_error_code` | `_last_error_code` |
|
|
282
|
+
| `gateway_health` | `_gateway_health` |
|
|
283
|
+
|
|
284
|
+
### 3.5 重连逻辑改造
|
|
285
|
+
|
|
286
|
+
当前 `_reconnect_loop` 改为:
|
|
287
|
+
|
|
288
|
+
```
|
|
289
|
+
网络断开 → state = RETRY_BACKOFF, 记录 _next_retry_at
|
|
290
|
+
↓ 退避到期
|
|
291
|
+
state = RECONNECTING, 尝试连接
|
|
292
|
+
↓ 成功 → state = READY
|
|
293
|
+
↓ 失败且有次数 → state = RETRY_BACKOFF(递增退避)
|
|
294
|
+
↓ 失败且耗尽 → state = CONNECTION_FAILED, 记录 _last_error/_last_error_code
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
- 用户在 `RETRY_BACKOFF` 调 `connect()` → 跳过退避,立即 `RECONNECTING`
|
|
298
|
+
- 用户在 `CONNECTION_FAILED` 调 `connect()` → 重置计数器,进入 `CONNECTING`
|
|
299
|
+
|
|
300
|
+
### 3.6 `call()` 方法
|
|
301
|
+
|
|
302
|
+
```python
|
|
303
|
+
async def call(self, method: str, params: dict | None = None, *, trace: str | None = None) -> Any:
|
|
304
|
+
if self._state != ConnectionState.READY:
|
|
305
|
+
raise StateError(f"call not allowed in state {self._state}")
|
|
306
|
+
if method in _INTERNAL_ONLY_METHODS:
|
|
307
|
+
raise PermissionError(f"method is internal_only: {method}")
|
|
308
|
+
|
|
309
|
+
merged = dict(params or {})
|
|
310
|
+
# 只在消息类和 thought 类附加 protected_headers
|
|
311
|
+
if self._instance_protected_headers and method in _PROTECTED_HEADERS_METHODS:
|
|
312
|
+
existing = merged.get("protected_headers") or {}
|
|
313
|
+
merged["protected_headers"] = {**self._instance_protected_headers, **existing}
|
|
314
|
+
|
|
315
|
+
return await self._transport.call(method, merged, timeout=..., trace=trace)
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
```python
|
|
319
|
+
_PROTECTED_HEADERS_METHODS = frozenset({
|
|
320
|
+
"message.send", "group.send",
|
|
321
|
+
"message.thought.put", "group.thought.put",
|
|
322
|
+
})
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
### 3.7 `set_protected_headers` / `get_protected_headers`
|
|
326
|
+
|
|
327
|
+
```python
|
|
328
|
+
def set_protected_headers(self, headers: dict[str, str] | None) -> None:
|
|
329
|
+
self._instance_protected_headers = dict(headers) if headers else None
|
|
330
|
+
|
|
331
|
+
def get_protected_headers(self) -> dict[str, str] | None:
|
|
332
|
+
return dict(self._instance_protected_headers) if self._instance_protected_headers else None
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
### 3.8 对端管理
|
|
336
|
+
|
|
337
|
+
| 方法 | 实现 |
|
|
338
|
+
|------|------|
|
|
339
|
+
| `lookup_peer(aid)` | 查内存缓存 → 无则调 `self._aid_store.resolve(aid)` → 缓存 → 返回 AID |
|
|
340
|
+
| `get_peer(aid)` | 仅查内存缓存,无则返回 None |
|
|
341
|
+
| `cache_peer(aid: AID)` | 加入内存缓存 |
|
|
342
|
+
| `peers()` | 返回缓存中所有 AID 列表 |
|
|
343
|
+
|
|
344
|
+
### 3.9 V2 E2EE 内部化
|
|
345
|
+
|
|
346
|
+
- 加密/解密逻辑保留为 AUNClient 内部方法
|
|
347
|
+
- `call('message.send', ...)` 时 SDK 内部自动处理 V2 加密
|
|
348
|
+
- 收到消息时内部自动解密,通过 `message.received` 事件交付明文
|
|
349
|
+
- 不再对外暴露 `sendV2()` / `pullV2()` / `ackV2()` 等方法名
|
|
350
|
+
|
|
351
|
+
### 3.10 事件
|
|
352
|
+
|
|
353
|
+
| 事件 | 数据 |
|
|
354
|
+
|------|------|
|
|
355
|
+
| `state-change` | `{"from": str, "to": str}` |
|
|
356
|
+
| `message.received` | `{"from", "payload", "protected_headers?", "context?", ...}` |
|
|
357
|
+
| `group.message_created` | `{"from", "group_id", "payload", "protected_headers?", ...}` |
|
|
358
|
+
| `message.recalled` | `{"message_id", "from", ...}` |
|
|
359
|
+
| `message.undecryptable` | `{"from", "seq", "_decrypt_error", "protected_headers?", ...}` |
|
|
360
|
+
| `group.message_undecryptable` | `{"from", "group_id", "seq", "_decrypt_error", "protected_headers?", ...}` |
|
|
361
|
+
| `token.refreshed` | `{"expires_at"}` |
|
|
362
|
+
| `gateway.disconnect` | `{"reason", "code"}` |
|
|
363
|
+
| `connection.error` | `{"error", "code"}` |
|
|
364
|
+
|
|
365
|
+
### 3.11 测试
|
|
366
|
+
|
|
367
|
+
| 文件 | 覆盖 |
|
|
368
|
+
|------|------|
|
|
369
|
+
| `tests/unit/test_client_state_machine.py` | 所有状态转换路径 |
|
|
370
|
+
| `tests/unit/test_client_capability.py` | 各状态下 getter 返回值 |
|
|
371
|
+
| `tests/unit/test_client_protected_headers.py` | 实例级 headers 合并逻辑 |
|
|
372
|
+
|
|
373
|
+
---
|
|
374
|
+
|
|
375
|
+
## 阶段 4:调用方全量迁移
|
|
376
|
+
|
|
377
|
+
**目标**:CLI、tests、docs 全部迁移到新 API
|
|
378
|
+
|
|
379
|
+
### 4.1 CLI 迁移 (`aun_cli/__init__.py`)
|
|
380
|
+
|
|
381
|
+
所有命令改为新模式:
|
|
382
|
+
|
|
383
|
+
```python
|
|
384
|
+
store = AIDStore(aun_path=..., encryption_seed=...)
|
|
385
|
+
result = await store.register(aid)
|
|
386
|
+
if not result.ok:
|
|
387
|
+
print(f"注册失败: {result.error.code}")
|
|
388
|
+
return
|
|
389
|
+
load_result = await store.load(aid)
|
|
390
|
+
me = load_result.data["aid"]
|
|
391
|
+
client = AUNClient(me, debug=debug)
|
|
392
|
+
await client.connect()
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
### 4.2 单元测试迁移
|
|
396
|
+
|
|
397
|
+
| 旧调用 | 新调用 |
|
|
398
|
+
|--------|--------|
|
|
399
|
+
| `client.auth.register_aid({"aid": aid})` | `store.register(aid)` |
|
|
400
|
+
| `client.auth.load_identity({"aid": aid})` | `store.load(aid)` |
|
|
401
|
+
| `client.auth.authenticate({"aid": aid})` | `client.authenticate()` |
|
|
402
|
+
| `client.connect(auth, opts)` | `client.connect(opts)` |
|
|
403
|
+
| `client.list_identities()` | `store.list()` |
|
|
404
|
+
| `client.auth.sign_agent_md(content)` | `aid.sign_agent_md(content)` |
|
|
405
|
+
| `client.auth.verify_agent_md(content)` | `peer.verify_agent_md(content)` |
|
|
406
|
+
| `client.meta.ping()` | `client.call('meta.ping')` |
|
|
407
|
+
| `client.meta.status()` | `client.call('meta.status')` |
|
|
408
|
+
| `client.fetch_agent_md(aid)` | `store.fetch_agent_md(aid)` |
|
|
409
|
+
| `client.check_agent_md(aid, ttl)` | `store.check_agent_md(aid, ttl_days)` |
|
|
410
|
+
| 状态断言 `ConnectionState.CONNECTED` | `ConnectionState.READY` |
|
|
411
|
+
| 状态断言 `ConnectionState.IDLE` | `ConnectionState.NO_IDENTITY` / `STANDBY` |
|
|
412
|
+
|
|
413
|
+
### 4.3 集成测试 / E2E 测试
|
|
414
|
+
|
|
415
|
+
`tests/integration_test_*.py` 和 `tests/e2e_test_*.py`:
|
|
416
|
+
- 开头创建 `AIDStore`
|
|
417
|
+
- `store.load(aid)` 获取 AID
|
|
418
|
+
- `AUNClient(aid)` 构造
|
|
419
|
+
- `client.connect()` 连接
|
|
420
|
+
- 业务操作用 `client.call('message.send', ...)`
|
|
421
|
+
|
|
422
|
+
### 4.4 双域测试
|
|
423
|
+
|
|
424
|
+
`docker-deploy/federation-test/tests/` 下的脚本同步改。
|
|
425
|
+
|
|
426
|
+
### 4.5 CustodyNamespace 处理
|
|
427
|
+
|
|
428
|
+
从 AUNClient 上移除,独立为 `CustodyClient` 类:
|
|
429
|
+
|
|
430
|
+
```python
|
|
431
|
+
from aun_core.custody import CustodyClient
|
|
432
|
+
|
|
433
|
+
custody = CustodyClient(aid="alice.aid.pub", verify_ssl=False)
|
|
434
|
+
await custody.bind_phone(params)
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
保留 HTTP 直连方式,不走 gateway RPC。
|
|
438
|
+
|
|
439
|
+
### 4.6 MetaNamespace 方法迁移
|
|
440
|
+
|
|
441
|
+
| 旧 | 新 |
|
|
442
|
+
|----|-----|
|
|
443
|
+
| `client.meta.ping()` | `client.call('meta.ping')` |
|
|
444
|
+
| `client.meta.status()` | `client.call('meta.status')` |
|
|
445
|
+
| `client.meta.trust_roots()` | `client.call('meta.trust_roots')` |
|
|
446
|
+
| `client.meta.download_trust_roots(...)` | AIDStore 内部自动处理 |
|
|
447
|
+
| `client.meta.verify_trust_roots(...)` | AIDStore.load() 内部链验证 |
|
|
448
|
+
| `client.meta.import_trust_roots(...)` | AIDStore 构造时自动导入 |
|
|
449
|
+
| `client.meta.refresh_trust_roots(...)` | AIDStore 内部按需刷新 |
|
|
450
|
+
|
|
451
|
+
---
|
|
452
|
+
|
|
453
|
+
## 阶段 5:清理 + 跨语言同步
|
|
454
|
+
|
|
455
|
+
**目标**:删除旧代码,跨语言对齐,发布 0.4.0
|
|
456
|
+
|
|
457
|
+
### 5.1 删除文件
|
|
458
|
+
|
|
459
|
+
| 文件 | 说明 |
|
|
460
|
+
|------|------|
|
|
461
|
+
| `namespaces/auth_namespace.py` | 全部功能已迁移到 AIDStore/AID/AUNClient |
|
|
462
|
+
| `namespaces/meta_namespace.py` | ping/status/trust_roots 改 call();信任根管理内化 |
|
|
463
|
+
| `namespaces/custody_namespace.py` | 独立为 `custody.py` |
|
|
464
|
+
| `namespaces/__init__.py` | 目录可删 |
|
|
465
|
+
|
|
466
|
+
### 5.2 AUNClient 内部清理
|
|
467
|
+
|
|
468
|
+
| 删除项 | 原位置 |
|
|
469
|
+
|--------|--------|
|
|
470
|
+
| `self.auth = AuthNamespace(self)` | 构造函数 |
|
|
471
|
+
| `self.meta = MetaNamespace(self)` | 构造函数 |
|
|
472
|
+
| `self.custody = CustodyNamespace(self)` | 构造函数 |
|
|
473
|
+
| `set_agent_md_path()` / `SetAgentMDPath()` | `client.py:562-572` |
|
|
474
|
+
| `list_identities()` | `client.py:1133` |
|
|
475
|
+
| `check_gateway_health()` | `client.py:1067` |
|
|
476
|
+
| `ping()` / `status()` / `trust_roots()` | `client.py:1661-1667` |
|
|
477
|
+
| `fetch_agent_md()` / `check_agent_md()` | `client.py:862-972` |
|
|
478
|
+
| 旧 `connect(auth, opts)` 签名 | `client.py:1071` |
|
|
479
|
+
|
|
480
|
+
### 5.3 `__init__.py` 最终导出
|
|
481
|
+
|
|
482
|
+
```python
|
|
483
|
+
__all__ = [
|
|
484
|
+
"AIDStore", "AID", "AUNClient",
|
|
485
|
+
"CustodyClient",
|
|
486
|
+
"Result", "ErrorInfo", "result_ok", "result_err",
|
|
487
|
+
"ProtectedHeaders", "ConnectionState", "get_device_id",
|
|
488
|
+
# 异常类(AUNClient connect/call 仍抛异常)
|
|
489
|
+
"AUNError", "AuthError", "ConnectionError", "TimeoutError",
|
|
490
|
+
"StateError", "E2EEError", "GroupError", ...
|
|
491
|
+
]
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
### 5.4 跨语言同步
|
|
495
|
+
|
|
496
|
+
| SDK | 改动范围 | 优先级 |
|
|
497
|
+
|-----|---------|--------|
|
|
498
|
+
| TS (`ts/`) | 新增 `AIDStore`/`AID` 类,重构 `AUNClient` 构造和状态机 | 高 |
|
|
499
|
+
| JS (`js/`) | 同 TS,但 `AIDStore.load` 从 IndexedDB/内存加载,无文件 I/O | 中 |
|
|
500
|
+
| Go (`go/`) | `AIDStore`/`AID` 用 struct + method,Result 用 `(T, error)` 惯用法 | 中 |
|
|
501
|
+
|
|
502
|
+
### 5.5 文档 + 版本
|
|
503
|
+
|
|
504
|
+
- 版本号:`0.4.0`
|
|
505
|
+
- 更新 `docs/sdk/06-API手册.md`
|
|
506
|
+
- 运行 `python/sync_docs.py` 同步到 skill 目录
|
|
507
|
+
- CHANGELOG 记录所有破坏性变更
|
|
508
|
+
|
|
509
|
+
---
|
|
510
|
+
|
|
511
|
+
## 接口迁移对照表
|
|
512
|
+
|
|
513
|
+
| 旧 API | 新 API | 归属 |
|
|
514
|
+
|--------|--------|------|
|
|
515
|
+
| `AUNClient(config, debug)` | `AUNClient(aid, debug=, protected_headers=)` | AUNClient |
|
|
516
|
+
| `client.auth.register_aid({"aid": x})` | `store.register(x)` | AIDStore |
|
|
517
|
+
| `client.auth.load_identity({"aid": x})` | `store.load(x)` | AIDStore |
|
|
518
|
+
| `client.auth.authenticate({"aid": x})` | `client.authenticate()` | AUNClient |
|
|
519
|
+
| `client.connect(auth, opts)` | `client.connect(opts)` | AUNClient |
|
|
520
|
+
| `client.disconnect()` | `client.disconnect()` | AUNClient(不变) |
|
|
521
|
+
| `client.close()` | `client.close()` | AUNClient(不变) |
|
|
522
|
+
| `client.call(method, params)` | `client.call(method, params)` | AUNClient(不变) |
|
|
523
|
+
| `client.on(event, handler)` | `client.on(event, handler)` | AUNClient(不变) |
|
|
524
|
+
| `client.list_identities()` | `store.list()` | AIDStore |
|
|
525
|
+
| `client.fetch_agent_md(aid)` | `store.fetch_agent_md(aid)` | AIDStore |
|
|
526
|
+
| `client.check_agent_md(aid, ttl)` | `store.check_agent_md(aid, ttl_days)` | AIDStore |
|
|
527
|
+
| `client.publish_agent_md()` | `client.publish_agent_md(content?)` | AUNClient |
|
|
528
|
+
| `client.auth.upload_agent_md(content)` | `client.upload_agent_md(content)` | AUNClient |
|
|
529
|
+
| `client.auth.sign_agent_md(content)` | `aid.sign_agent_md(content)` | AID |
|
|
530
|
+
| `client.auth.verify_agent_md(content)` | `peer.verify_agent_md(content)` | AID |
|
|
531
|
+
| `client.auth.fetch_peer_cert({"aid": x})` | `store.resolve(x)` | AIDStore |
|
|
532
|
+
| `client.auth.check_aid({"aid": x})` | `store.diagnose(x)` | AIDStore |
|
|
533
|
+
| `client.auth.renew_cert()` | `store.renew_cert(aid)` | AIDStore |
|
|
534
|
+
| `client.auth.rekey()` | `store.rekey(aid)` | AIDStore |
|
|
535
|
+
| `client.meta.ping()` | `client.call('meta.ping')` | RPC 透传 |
|
|
536
|
+
| `client.meta.status()` | `client.call('meta.status')` | RPC 透传 |
|
|
537
|
+
| `client.meta.trust_roots()` | `client.call('meta.trust_roots')` | RPC 透传 |
|
|
538
|
+
| `client.custody.bind_phone(p)` | `CustodyClient(aid).bind_phone(p)` | CustodyClient |
|
|
539
|
+
| `FileKeyStore.change_seed(...)` | `store.change_seed(old, new)` | AIDStore |
|
|
540
|
+
| `client.set_agent_md_path(path)` | 删除(构造参数或 AIDStore 配置) | — |
|
|
541
|
+
| `client.check_gateway_health(url)` | `client.gateway_health` getter | AUNClient |
|
|
542
|
+
| `client.state` | `client.state` | AUNClient(值变更) |
|
|
543
|
+
| `client.aid` | `client.current_aid` | AUNClient |
|
|
544
|
+
|
|
545
|
+
---
|
|
546
|
+
|
|
547
|
+
## 文件变更汇总
|
|
548
|
+
|
|
549
|
+
### 新增文件
|
|
550
|
+
|
|
551
|
+
| 文件 | 说明 |
|
|
552
|
+
|------|------|
|
|
553
|
+
| `python/src/aun_core/result.py` | Result / ErrorInfo / result_ok / result_err |
|
|
554
|
+
| `python/src/aun_core/error_codes.py` | 错误码字符串常量 |
|
|
555
|
+
| `python/src/aun_core/aid.py` | AID 值对象 |
|
|
556
|
+
| `python/src/aun_core/aid_store.py` | AIDStore 管理器 |
|
|
557
|
+
| `python/src/aun_core/_cert_utils.py` | 从现有代码提取的证书/签名纯函数 |
|
|
558
|
+
| `python/src/aun_core/custody.py` | CustodyClient 独立类 |
|
|
559
|
+
| `tests/unit/test_result.py` | Result 单测 |
|
|
560
|
+
| `tests/unit/test_aid.py` | AID 单测 |
|
|
561
|
+
| `tests/unit/test_aid_store_offline.py` | AIDStore 离线单测 |
|
|
562
|
+
| `tests/unit/test_aid_store_network.py` | AIDStore 联网单测 |
|
|
563
|
+
| `tests/unit/test_client_state_machine.py` | 状态机单测 |
|
|
564
|
+
| `tests/unit/test_client_capability.py` | Capability getter 单测 |
|
|
565
|
+
| `tests/unit/test_client_protected_headers.py` | protected_headers 单测 |
|
|
566
|
+
| `tests/integration_test_aid_store.py` | AIDStore 集成测试 |
|
|
567
|
+
|
|
568
|
+
### 修改文件
|
|
569
|
+
|
|
570
|
+
| 文件 | 改动 |
|
|
571
|
+
|------|------|
|
|
572
|
+
| `python/src/aun_core/__init__.py` | 新增导出,移除旧 namespace 导出 |
|
|
573
|
+
| `python/src/aun_core/types.py` | ConnectionState 枚举值替换 |
|
|
574
|
+
| `python/src/aun_core/client.py` | 重构构造函数、状态机、connect、删除 namespace 引用 |
|
|
575
|
+
| `python/src/aun_core/auth.py` | 提取纯函数到 `_cert_utils.py`,AuthFlow 保留供 AIDStore/AUNClient 内部使用 |
|
|
576
|
+
| `python/src/aun_core/keystore/file.py` | 无大改,被 AIDStore 内部调用 |
|
|
577
|
+
| `python/src/aun_cli/__init__.py` | CLI 全量迁移到新 API |
|
|
578
|
+
| `tests/unit/test_auth.py` | 改为测试 AIDStore |
|
|
579
|
+
| `tests/unit/test_client.py` | 改为新构造 + 新状态 |
|
|
580
|
+
| `tests/unit/test_connection_kind.py` | 状态枚举值更新 |
|
|
581
|
+
| `tests/integration_test_*.py` | 全量迁移 |
|
|
582
|
+
| `tests/e2e_test_*.py` | 全量迁移 |
|
|
583
|
+
|
|
584
|
+
### 删除文件
|
|
585
|
+
|
|
586
|
+
| 文件 | 说明 |
|
|
587
|
+
|------|------|
|
|
588
|
+
| `python/src/aun_core/namespaces/auth_namespace.py` | 功能迁移到 AIDStore/AID/AUNClient |
|
|
589
|
+
| `python/src/aun_core/namespaces/meta_namespace.py` | 功能迁移到 call() + AIDStore 内部 |
|
|
590
|
+
| `python/src/aun_core/namespaces/custody_namespace.py` | 独立为 custody.py |
|
|
591
|
+
| `python/src/aun_core/namespaces/__init__.py` | 目录删除 |
|
|
592
|
+
|
|
593
|
+
---
|
|
594
|
+
|
|
595
|
+
**文档版本**:v1.0
|
|
596
|
+
**最后更新**:2026-05-28
|