@agentunion/fastaun 0.5.23 → 0.5.24
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 +33 -0
- package/_packed_docs/CHANGELOG.md +33 -0
- package/_packed_docs/INDEX.md +27 -15
- package/_packed_docs/KITE_DOCS_GUIDE.md +22 -19
- package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2112.1.md +2 -2
- package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +13 -7
- package/_packed_docs/sdk/INDEX.md +21 -6
- package/_packed_docs/sdk/Ownership-SDK/347/274/226/346/216/222/346/214/207/345/215/2272.1.md +49 -2
- 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 +123 -72
- 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 +173 -96
- package/dist/agent-md.d.ts +1 -1
- package/dist/agent-md.js +14 -12
- package/dist/agent-md.js.map +1 -1
- package/dist/aid-store.d.ts +1 -0
- package/dist/aid-store.js +34 -12
- package/dist/aid-store.js.map +1 -1
- package/dist/client.d.ts +9 -10
- package/dist/client.js +115 -94
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/keystore/aid-db.d.ts +1 -8
- package/dist/keystore/aid-db.js +46 -204
- package/dist/keystore/aid-db.js.map +1 -1
- package/dist/keystore/index.d.ts +0 -15
- package/dist/keystore/local-token-store.d.ts +1 -6
- package/dist/keystore/local-token-store.js +0 -15
- package/dist/keystore/local-token-store.js.map +1 -1
- package/dist/ownership-v21.d.ts +2 -2
- package/dist/ownership-v21.js +25 -12
- package/dist/ownership-v21.js.map +1 -1
- package/dist/ownership-workflow.d.ts +30 -59
- package/dist/ownership-workflow.js +260 -1667
- package/dist/ownership-workflow.js.map +1 -1
- package/dist/tools/cross-sdk-agent.d.ts +1 -2
- package/dist/tools/cross-sdk-agent.js +7 -14
- package/dist/tools/cross-sdk-agent.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
|
@@ -1,25 +1,27 @@
|
|
|
1
1
|
# Ownership 2.1 SDK 开发与移植指南
|
|
2
2
|
|
|
3
|
-
面向 SDK
|
|
3
|
+
面向 SDK 实现者,对照当前四 SDK(Python、Go、TS、JS)的文件验证、权威适配和当次编排;TS 工作区版本为 `0.5.24`。应用调用见 [集成指南](Ownership2.1-SDK应用集成指南.md),字段与授权见 [协议 2.1](../protocol/Ownership协议定义2.1.md)。Rust SDK 本轮仅检查、未同步修改,不能据本文认定已实现新的简化流程。
|
|
4
4
|
|
|
5
5
|
## 1. 实现边界与角色
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
关系事实来自 sub 在 NS 的 `agent.md` 和 owner 在本人 Storage 的 `owner.md`,由 NS 验收并锁定。sub 自签自传,owner 自签自传;owner 登录态不能代上传普通 sub 名片。SDK 不维护 Ownership 持久任务、WAL、审批账本或后台恢复,也不调用 decisions 接口。
|
|
8
|
+
|
|
9
|
+
未锁定时,以最后成功上传的 sub 声明为准;NS 比较 owner、sub、关系版本和有效授权等归属事实,不以消息 ID、声明 UUID 或时间判断归属冲突。拒绝只通知对方,不删除或回滚候选。锁定后新的 accept/reject 不能改变归属,必须走释放或转移;锁定前不保证多设备决定只能作出一次。
|
|
8
10
|
|
|
9
11
|
普通名片解析、外层验签与关系验证分层。正文更新保留全部关系材料的存在性、类型和值及原 CAS,不以前置共识或 owner Storage 可用为条件;关系字段变更仍执行双文件授权。
|
|
10
12
|
|
|
11
|
-
Group/Org 作为 sub
|
|
13
|
+
Group/Org 作为 sub 使用专属生命周期:通用关系编排抛出 `not_applicable`,共识返回同名结果;它们仍可按普通 owner 规则作为 owner。现代实体名片由实体主签、当前 owner token 上传,可选 owner 附签且全部验证,详见协议 §2.5。
|
|
12
14
|
|
|
13
15
|
## 2. 分层与最小依赖接口
|
|
14
16
|
|
|
15
17
|
| 层次 | TS 参考与职责 |
|
|
16
18
|
| --- | --- |
|
|
17
19
|
| 文件/共识 | [ownership-v21.ts](../../ts/src/ownership-v21.ts):严格解析、签验、`OwnershipFiles`、权威 Storage 适配。 |
|
|
18
|
-
| 编排 | [ownership-workflow.ts](../../ts/src/ownership-workflow.ts)
|
|
20
|
+
| 编排 | [ownership-workflow.ts](../../ts/src/ownership-workflow.ts):当次上传、通知、原始请求索引、结果和条件清理。 |
|
|
19
21
|
| HTTP/证书 | [agent-md.ts](../../ts/src/agent-md.ts)、[auth.ts](../../ts/src/auth.ts):NS fresh/原条件 PUT、精确证书和当前吊销状态。 |
|
|
20
|
-
| Client
|
|
22
|
+
| Client/消息 | [client.ts](../../ts/src/client.ts)、[delivery.ts](../../ts/src/client/delivery.ts):装配身份、可靠消息投递、事件和专属短连接。 |
|
|
21
23
|
|
|
22
|
-
文件层依赖:`currentAid/now/readAgentMdFresh/submitAgentMdRaw/readOwnerMd/writeOwnerMd/deleteOwnerMd/resolveCertificate
|
|
24
|
+
文件层依赖:`currentAid/now/readAgentMdFresh/submitAgentMdRaw/readOwnerMd/writeOwnerMd/deleteOwnerMd/resolveCertificate`。读返回完整原文、存在性和权威元数据;写保留当次读取的原条件及签名字节。编排只需身份 guard、文件层、发送、事件和可选的原始请求恢复函数,不需要任务存储接口。各服务独立实现网络契约,不 import SDK 或其他服务源码。
|
|
23
25
|
|
|
24
26
|
以下是 SDK 内部装配片段;应用只使用集成指南中的公有 Facade:
|
|
25
27
|
|
|
@@ -28,7 +30,8 @@ import { OwnershipFiles } from './ownership-v21.js';
|
|
|
28
30
|
import { OwnershipWorkflow } from './ownership-workflow.js';
|
|
29
31
|
const files = new OwnershipFiles(fileDeps);
|
|
30
32
|
const workflow = new OwnershipWorkflow({
|
|
31
|
-
currentAid: () => identity.aid,
|
|
33
|
+
currentAid: () => identity.aid, currentType: () => identity.type,
|
|
34
|
+
assertCurrent: assertIdentityUnchanged, files,
|
|
32
35
|
send: sendEncryptedMessage, emit: emitLocalEvent,
|
|
33
36
|
});
|
|
34
37
|
```
|
|
@@ -38,13 +41,13 @@ const workflow = new OwnershipWorkflow({
|
|
|
38
41
|
| 值 | 规则 |
|
|
39
42
|
| --- | --- |
|
|
40
43
|
| AID | 完整小写;分别核对资源、证书、路径和认证身份。 |
|
|
41
|
-
| operation/declaration ID | 小写 UUIDv4
|
|
44
|
+
| operation/declaration ID | 小写 UUIDv4;通知关联 ID 与各文件声明 ID 独立,不是 NS 关系锁。 |
|
|
42
45
|
| 格式版本 | 名片为语义版本字符串;statement.version 为整数 `2`;NS 协议头为 `2.1`。 |
|
|
43
46
|
| 关系版本 | NS 投影 `0..9007199254740991`,statement 从 `1` 起;递增前检查溢出。 |
|
|
44
47
|
|
|
45
|
-
绑定、转移为当前可见声明 `v+1`;release 保持 `v`;sub 清空关系后投影为 `0`,重绑默认 `1
|
|
48
|
+
绑定、转移为当前可见声明 `v+1`;release 保持 `v`;sub 清空关系后投影为 `0`,重绑默认 `1`。关系版本不是全局 operation 序号,不能替代原件和 CAS。双方 statement 的 version、owner、sub、relation_version 相同;declaration_id 和 declared_at 可不同。
|
|
46
49
|
|
|
47
|
-
|
|
50
|
+
当次调用在内存保留 `base_agent_md={exists,raw,sha256,etag,statement}` 和 `base_owner_md={exists,raw,sha256}`,目标 owner 的写前原件单独保留。已签候选在本次调用内使用原字节和原 CAS;失败后的新调用重新读取 NS 和文件事实,不恢复旧任务。
|
|
48
51
|
|
|
49
52
|
## 4. 原始文件、严格解析与签名
|
|
50
53
|
|
|
@@ -95,7 +98,7 @@ TS 当前使用直接 issuer 亲签的严格 OCSP DER:核对 CertID、Responde
|
|
|
95
98
|
|
|
96
99
|
## 6. NS HTTP 与六态共识
|
|
97
100
|
|
|
98
|
-
每个新 SDK NS GET/HEAD/PUT(含 fallback 和重试)携带 `X-AUN-Signature-Format: multi-v1
|
|
101
|
+
每个新 SDK NS GET/HEAD/PUT(含 fallback 和重试)携带 `X-AUN-Signature-Format: multi-v1`;关系查询携带 `X-AUN-Ownership-Check: 1`。管理操作的 fresh 读取再加 `Cache-Control: no-cache`;TS 使用 GET,禁止自动重定向。四 SDK 公有 `checkOwnershipConsensus` 不带 no-cache,允许 NS 的 agent.md 缓存,仍检查 owner.md;Storage 也可使用自身对象缓存。不能把普通查询等同于管理前强制绕缓存。浏览器须能读取 CORS 暴露的关系头和 ETag。
|
|
99
102
|
|
|
100
103
|
必验 `X-AUN-Ownership-Protocol: 2.1`、`X-AUN-Ownership-Version` 严格整数及 `X-AUN-Ownership-Lock: locked|unlocked`,存在时保留原 ETag。只有正确 NS 返回完整权威元数据的 404 才能解释为不存在;初始名片缺失仍不能开始绑定写入。
|
|
101
104
|
|
|
@@ -113,7 +116,7 @@ flowchart TD
|
|
|
113
116
|
B -->|确定材料无效| I[invalid]
|
|
114
117
|
```
|
|
115
118
|
|
|
116
|
-
两类元数据冲突共用一次额外重查额度,不能省略最后稳定性复读。六态为 `consensus/released/unbound/single_sided/invalid/unavailable`,含义见 [应用指南
|
|
119
|
+
两类元数据冲突共用一次额外重查额度,不能省略最后稳定性复读。六态为 `consensus/released/unbound/single_sided/invalid/unavailable`,含义见 [应用指南](Ownership2.1-SDK应用集成指南.md)。pending/unlocked 映射 single_sided,不授权;release 或权威缺失对应 released/v,清理后 unbound/0。转移候选未验收前,普通查询仍返回旧 active 关系;授权过期不使旧关系失效。
|
|
117
120
|
|
|
118
121
|
```ts
|
|
119
122
|
const current = await files.checkConsensus(subAid);
|
|
@@ -130,10 +133,9 @@ const current = await files.checkConsensus(subAid);
|
|
|
130
133
|
```mermaid
|
|
131
134
|
sequenceDiagram
|
|
132
135
|
participant F as 文件层
|
|
133
|
-
participant L as 本地任务库
|
|
134
136
|
participant T as owner Storage
|
|
135
137
|
F->>T: 权威读取起始原文、存在性、SHA 和对象 version
|
|
136
|
-
F->>
|
|
138
|
+
F->>F: 当次内存保留原件、签名全文及 expected_version
|
|
137
139
|
F->>T: 写前复读,精确核对起始原件
|
|
138
140
|
F->>T: create_upload_session,携带原 expected_version
|
|
139
141
|
T-->>F: session_id 与上传 URL
|
|
@@ -143,25 +145,29 @@ sequenceDiagram
|
|
|
143
145
|
F->>T: 权威复读确认完整原文
|
|
144
146
|
```
|
|
145
147
|
|
|
146
|
-
创建时 expected_version=`0`,更新时为起始权威对象的正安全整数 version;三阶段 Content-Type 一致,完成时公开可读。object_id/version
|
|
148
|
+
创建时 expected_version=`0`,更新时为起始权威对象的正安全整数 version;三阶段 Content-Type 一致,完成时公开可读。object_id/version 不参与只读共识,但发布/删除必须具备有效版本;本次写入不能追随最新 version 强行覆盖。
|
|
147
149
|
|
|
148
|
-
删除前 fresh 核对 NS
|
|
150
|
+
删除前 fresh 核对 NS 和完整旧原件,再带最后读取且匹配原件的当前 version 条件删除;缺版本或 `-32009` 冲突即停止。只有本人可写删 owner.md,服务端须同时限制目录、移动、别名和批量入口。删除权威 owner 文件可导致释放,不能把它当作拒绝或取消通知。
|
|
149
151
|
|
|
150
152
|
这些复读不是跨服务一致快照,也不保证识别同内容 ABA 或完整文件回滚。
|
|
151
153
|
|
|
152
|
-
## 8.
|
|
154
|
+
## 8. Facade、当次结果与错误契约
|
|
153
155
|
|
|
154
|
-
|
|
156
|
+
七个远端/查询 Facade 加两个本地编排方法见 [应用指南](Ownership2.1-SDK应用集成指南.md)。内部五步为 `declareOwneraid/declareSubaid/revokeOwneraid/revokeSubaid/transferOwneraid`,只执行本人文件动作,不创建持久任务、不代替对方审批。
|
|
155
157
|
|
|
156
|
-
|
|
158
|
+
发起或接收时核对本端身份、认证来源、操作、参与方及 base/target 事实;不靠本地任务 revision、lease 或审批回执阻止冲突。NS 验收负责保护已锁归属;请求 ID 只关联原始通知。方法返回值是当次调用结果,不是可恢复任务。
|
|
157
159
|
|
|
158
|
-
| 结果 |
|
|
160
|
+
| 结果 | 处理边界 |
|
|
159
161
|
| --- | --- |
|
|
160
|
-
|
|
|
161
|
-
|
|
|
162
|
-
|
|
|
163
|
-
|
|
|
164
|
-
|
|
|
162
|
+
| single_sided/等待对方 | 关系未成立;对方可稍后明确接受,不要求同时在线。 |
|
|
163
|
+
| unavailable/unsupported_protocol | 本次无法完成验证;保留远端事实,显式查询或重新发起。 |
|
|
164
|
+
| 原条件失效、目标/版本变化、授权过期 | conflict 等错误;不能换条件覆盖,先读 NS。 |
|
|
165
|
+
| invalid/明确拒绝 | 本次报错/通知 rejected;拒绝不回滚已上传文件。 |
|
|
166
|
+
| 目标已为 NS 当前 locked 关系 | 相同目标可直接确认;不同目标必须释放或转移。 |
|
|
167
|
+
|
|
168
|
+
保留的 `OwnershipTaskResult` 类型名不表示存在任务状态机。TS/Python 普通编排在当次上传和通知完成后即可返回 completed;JS 会区分尚未锁定的 pending。Go 的 `ownership.completed` 事件也可能只表示本次动作完成,不能把跨 SDK 的 completed 返回或事件统一解释成关系已锁。最终业务授权须查询 `checkOwnershipConsensus` 并核对 owner、sub、版本和锁。
|
|
169
|
+
|
|
170
|
+
`ownership.request` 与 `ownership.rejected` 是通知 UI 的共同入口,但事件附加字段不同;例如 TS 使用 requester_aid,其他 SDK 可附 initiator_aid/local_roles。移植时保留 operation_id 与参与方上下文,可信原请求留在 SDK 内;不要把 progress、action_required、error 或 stopped 等兼容事件写成每次必发。
|
|
165
171
|
|
|
166
172
|
结果读取 code/reason/retryable;TS 异常的业务码为 `AUNError.stringCode`,数字 code 保留 RPC 语义。Storage Base64 传输封装损坏、同次权威元数据与正文摘要不一致归 unavailable;摘要匹配后确定非法的 UTF-8、字段或签名归 invalid。
|
|
167
173
|
|
|
@@ -172,102 +178,87 @@ sequenceDiagram
|
|
|
172
178
|
| 消息 | 方向 |
|
|
173
179
|
| --- | --- |
|
|
174
180
|
| bind.request/accept/reject | 绑定双方;owner-first 正常成功不要求 accept 往返。 |
|
|
175
|
-
| transfer.request
|
|
176
|
-
|
|
|
177
|
-
|
|
|
178
|
-
| operation.stopped | 已关联任务的参与方 → 其他参与方。 |
|
|
181
|
+
| transfer.request | 原 owner → 新 owner、sub;各自 accept/reject 只回复原发起方。 |
|
|
182
|
+
| relation.confirmed | release 的 owner → sub,接收方回源验证后清理本人声明。 |
|
|
183
|
+
| ready/owner.ready/sub.cleaned/operation.stopped | 保留的兼容载荷,不应作为当前编排必经步骤。 |
|
|
179
184
|
|
|
180
|
-
发送使用现有加密持久 `message.send`,不设 volatile TTL;来源只取消息层验证的 from_aid,不接受自发 P2P。
|
|
185
|
+
发送使用现有加密持久 `message.send`,不设 volatile TTL;来源只取消息层验证的 from_aid,不接受自发 P2P。通知不是验收凭证,文件动作前仍核对 NS、原件、授权和目标。通用消息持久交付、序号和 ACK 继续保留,移除的只是 Ownership 自己的任务/inbox/outbox。
|
|
181
186
|
|
|
182
187
|
```mermaid
|
|
183
188
|
flowchart TD
|
|
184
189
|
A[push / pull / 重连 / 有序队列] --> B[解密,保留原 JSON 和已认证 from_aid]
|
|
185
190
|
B --> C{Ownership 控制类型吗}
|
|
186
191
|
C -->|否| D[普通应用消息]
|
|
187
|
-
C -->|是| E[
|
|
188
|
-
E
|
|
189
|
-
E -->|有效| G[
|
|
190
|
-
G
|
|
191
|
-
|
|
192
|
-
I
|
|
193
|
-
J --> K[
|
|
192
|
+
C -->|是| E[纯解析:检查词法、认证方向和载荷]
|
|
193
|
+
E -->|确定非法或不支持| F[消费控制消息,不作为聊天投递]
|
|
194
|
+
E -->|有效| G[保留原始请求索引,或处理通知]
|
|
195
|
+
G --> I[需要副作用时回源核验并执行本人动作]
|
|
196
|
+
I -->|NS/Storage 等处理失败| H[抛错;通用投递保留队列/游标,不 ACK]
|
|
197
|
+
I -->|成功| J[发布当次本地事件]
|
|
198
|
+
J --> K[处理成功后由 delivery 提交投递进度并 ACK]
|
|
194
199
|
F --> K
|
|
195
200
|
```
|
|
196
201
|
|
|
197
|
-
Client 拦截点在 `_publishAppEvent('message.received') → _handleOwnershipMessage → receive`;解密原文由 [decrypt.ts](../../ts/src/v2/e2ee/decrypt.ts)
|
|
202
|
+
TS Client 拦截点在 `_publishAppEvent('message.received') → _handleOwnershipMessage → receive`;解密原文由 [decrypt.ts](../../ts/src/v2/e2ee/decrypt.ts) 保留。仅纯解析阶段确定不可重试的 invalid/forbidden 可隔离消费;合法通知后续的 NS/Storage 错误继续传播,不能吞掉整页处理故障。只给 handler 写 await 不够,[delivery.ts](../../ts/src/client/delivery.ts) 的所有实际投递路径都须等待它。
|
|
198
203
|
|
|
199
204
|
```ts
|
|
200
205
|
const consumed = await workflow.receive(authenticatedFromAid, decryptedJsonText);
|
|
201
206
|
```
|
|
202
207
|
|
|
203
|
-
|
|
208
|
+
原始请求索引只保存已认证来源及原载荷,供 accept/reject 查回请求;可从通用消息历史恢复原请求,但不重建 Ownership 待办状态。进程重启后找不到原请求时,由应用重新发起,不从任意 UI 字段补造请求。
|
|
204
209
|
|
|
205
|
-
## 10.
|
|
210
|
+
## 10. 当次上下文、显式重试与 CAS
|
|
206
211
|
|
|
207
|
-
### 10.1
|
|
212
|
+
### 10.1 不再要求 Ownership 持久存储
|
|
208
213
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
| 记录 | 必须保存 |
|
|
212
|
-
| --- | --- |
|
|
213
|
-
| 上下文/审批 | 固定任务 ID、发起方、参与 AID、角色、base/target;本端决定及各 AID 审批。 |
|
|
214
|
-
| 文件 | 起始双文件证据;本人已签全文、SHA、原 CAS;Stage 候选 raw/SHA/ETag;owner-first 提案原文/SHA。 |
|
|
215
|
-
| 转移授权 | 首次验证的完整授权摘要和实际 expires_at,恢复不替换或延期。 |
|
|
216
|
-
| inbox/outbox | 收件去重键;原回复、接收方、稳定 message_id 和发送进度。 |
|
|
217
|
-
| 完成/清理 | 真实验收状态、版本、锁、NS ETag、双文件摘要、本人的签名原件与原条件;独立清理授权和进度。 |
|
|
214
|
+
编排仅保留当次内存上下文:参与方、base/target、原件及条件、本人签名文件和待发送通知。原始请求索引用于匹配 accept/reject,不记录审批状态;没有任务表、WAL、revision CAS、lease/fencing、Ownership inbox/outbox 或 connect 后台恢复。
|
|
218
215
|
|
|
219
|
-
|
|
216
|
+
TS 文件层仍使用 `OwnershipFileTask` 和 `persist()` 名称以适配已有接口,当前 workflow 注入的 persist 只执行身份 guard,不落盘。移植时不应据这些类型名称重新引入任务存储。通用消息历史、可靠发送与 ACK 进度保留原实现。
|
|
220
217
|
|
|
221
|
-
### 10.2
|
|
218
|
+
### 10.2 故障后查询与重新发起
|
|
222
219
|
|
|
223
220
|
```mermaid
|
|
224
221
|
flowchart TD
|
|
225
|
-
A[
|
|
226
|
-
B
|
|
227
|
-
B
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
F -->|真实冲突/过期| I[stopped;不覆盖后续文件]
|
|
222
|
+
A[应用重新调用;读取 NS 和当前双文件] --> B{NS 当前归属是什么}
|
|
223
|
+
B -->|目标已 locked 且匹配| C[确认当前结果,避免重写关系]
|
|
224
|
+
B -->|已锁为其他归属| D[停止;须释放或合法转移]
|
|
225
|
+
B -->|未锁/尚未完成| E[按当前事实重新发起并取得必要同意]
|
|
226
|
+
E --> F[当次签署、条件上传和发送通知]
|
|
227
|
+
F --> G{NS 验收及目标关系匹配吗}
|
|
228
|
+
G -->|是| C
|
|
229
|
+
G -->|缺对方材料| H[单边声明/旧 active 继续有效]
|
|
230
|
+
G -->|网络未知或冲突| I[本次报错;不覆盖,之后重新查询]
|
|
235
231
|
```
|
|
236
232
|
|
|
237
|
-
|
|
233
|
+
文件发布仍保留原件和条件,但只在当次调用内保存,省略的前后校验按 §4–7 执行:
|
|
238
234
|
|
|
239
235
|
```ts
|
|
240
|
-
|
|
241
|
-
|
|
236
|
+
call.files.agent = saved;
|
|
237
|
+
assertIdentityUnchanged();
|
|
242
238
|
const published = await fileDeps.submitAgentMdRaw(
|
|
243
|
-
|
|
239
|
+
call.sub_aid, saved.raw, saved.condition, assertIdentityUnchanged,
|
|
244
240
|
);
|
|
245
241
|
```
|
|
246
242
|
|
|
247
|
-
|
|
243
|
+
上传已成功但通知未发出时,没有 Ownership 后台补偿;应用重新发起即可。新调用先读当前 NS,目标已经锁定则不重写,未完成则基于当前事实继续合法操作;不保证沿用旧 operation ID、签名字节或审批。`list/get/reset` 等旧任务兼容入口不能再作为恢复来源。
|
|
248
244
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
0.5.20/0.5.19 及更早任务保留原 operation、审批、字节和条件,不因缺后增字段一律重建。旧 expected_revision 无权威匹配证据时保持 unavailable,不能用当前 version 替代或从网络补造历史原件。reset 仅归档当前身份发起的未完成任务,保留远端文件,迟到消息不能复活。
|
|
252
|
-
|
|
253
|
-
### 10.3 CAS 冲突分类与恢复边界
|
|
245
|
+
### 10.3 保留两类远端 CAS 与关系版本
|
|
254
246
|
|
|
255
247
|
| 条件层 | 结果与处理 |
|
|
256
248
|
| --- | --- |
|
|
257
|
-
|
|
|
258
|
-
|
|
|
259
|
-
| Storage 原 expected_version | -32009→conflict;权威重读完整原件和版本,不刷新条件覆盖或删除。 |
|
|
249
|
+
| NS 原 If-Match/If-None-Match | 412→conflict,语义冲突也可为409;重新读取 NS 原文、ETag、锁和关系事实。 |
|
|
250
|
+
| Storage 原 expected_version | -32009→conflict;权威重读完整原件和版本,不刷新条件强行覆盖或删除。 |
|
|
260
251
|
|
|
261
|
-
|
|
252
|
+
NS 文件 CAS 防止覆盖并发名片,Storage 对象版本防止覆盖并发 owner 文件;关系版本绑定本次归属和转移授权。三者用途不同,都应保留。本地任务 CAS 和 decisions 回执已移除,不能因此同时删掉远端条件。签名字节、声明 ID 或时间不同也不必然是归属冲突,NS 按当前事实验收并保护已锁关系。
|
|
262
253
|
|
|
263
|
-
## 11.
|
|
254
|
+
## 11. 关系流程与本地编排
|
|
264
255
|
|
|
265
256
|
| 流程 | SDK 内部步骤 |
|
|
266
257
|
| --- | --- |
|
|
267
|
-
| sub-first bind | 无锁/Stage 预检 →
|
|
268
|
-
| owner-first bind | 无锁预检 → declareSubaid → request → sub
|
|
258
|
+
| sub-first bind | 无锁/Stage 预检 → declareOwneraid Stage → request → owner 明确接受并 declareSubaid → NS fresh 验收。 |
|
|
259
|
+
| owner-first bind | 无锁预检 → declareSubaid → request → sub 明确接受并 declareOwneraid → NS fresh 验收。 |
|
|
269
260
|
| release | revokeSubaid 发布同版本 release → fresh 确认 → 通知 sub → 授权清理 revokeOwneraid。 |
|
|
270
|
-
| transfer |
|
|
261
|
+
| transfer | 原 owner transferOwneraid → request 通知双方 → 双方各自明确接受并上传本人文件 → NS 原子切换 → 条件清理旧文件。 |
|
|
271
262
|
|
|
272
263
|
### 11.1 EC CLI 创建 sub 后,由 human owner 确认绑定
|
|
273
264
|
|
|
@@ -275,37 +266,120 @@ flush 每条通知前 fresh 核对对应原/目标关系,陈旧通知持久
|
|
|
275
266
|
|
|
276
267
|
```mermaid
|
|
277
268
|
flowchart LR
|
|
278
|
-
A[fresh 确认 Stage: 1] --> B[
|
|
269
|
+
A[fresh 确认 Stage: 1] --> B[当次保留 sub 原件和原 CAS]
|
|
279
270
|
B --> C[条件 Stage PUT]
|
|
280
271
|
C --> D[响应确认 Stage: 1 和 pending]
|
|
281
272
|
D --> E[owner 发布匹配 declare]
|
|
282
|
-
E --> F[NS
|
|
283
|
-
F --> G[locked
|
|
273
|
+
E --> F[NS Check 验证当前双文件并验收]
|
|
274
|
+
F --> G[非 Stage active 查询确认 locked 与目标事实]
|
|
284
275
|
```
|
|
285
276
|
|
|
286
|
-
Stage 能力由 fresh Check 响应的 `X-AUN-Ownership-Stage: 1`
|
|
277
|
+
Stage 能力由 fresh Check 响应的 `X-AUN-Ownership-Stage: 1` 确认,首次绑定的 Stage PUT 仍须确认该头及 unlocked/pending;pending 不授权。owner-first 不用 Stage,接受时核对提案原文/SHA 和当前 NS 后提交。拒绝仅发送 reject,不删除本方或对方文件;候选被替换后,旧 owner 的接受无法让不一致文件达成共识。
|
|
287
278
|
|
|
288
279
|
### 11.2 owner 释放与 sub 清理
|
|
289
280
|
|
|
290
|
-
release 保持 v,owner 可在 sub 离线时凭 fresh released/v 完成。sub
|
|
281
|
+
release 保持 v,owner 可在 sub 离线时凭 fresh released/v 完成。sub 收到通知后,当次核对认证来源、NS 当前状态和本人原件,再条件清空关系;不要求历史 bind/transfer 任务提供同意证明。已稳定清空时按当前 unbound/0 处理,不补造 released 或历史条件。
|
|
282
|
+
|
|
283
|
+
这是内部 `revokeOwneraid` 的条件清理,不是普通 `uploadAgentMd` 更新;普通上传禁止删除关系字段,当前也没有公开的强制清理 Facade。通知尚未处理时保持 released 展示,不能指导应用用普通上传绕过关系规则。
|
|
291
284
|
|
|
292
285
|
### 11.3 三方连续转移
|
|
293
286
|
|
|
294
|
-
|
|
287
|
+
原 owner 先发布旧版本的 transfer_owner 和期限,再通过 transfer.request 通知 sub 与新 owner。两方可先后在线,各自明确接受后上传本人 v+1 文件,不需要先收齐审批或交换 decisions 回执。NS 保留旧 active,独立暂存 transfer 候选,材料齐备且授权有效后原子切换,不经过 released;期限不因重试自动延期。
|
|
288
|
+
|
|
289
|
+
transfer 的 Stage 读取可能返回 target 候选正文,但头仍为旧 active 的 locked/base,也可能已晋升为 locked/target;locked 响应可省略 Ownership-Status。移植实现须保留 Stage 能力、完整签验、正文 SHA、原 ETag/CAS、base/target 和有效转移授权检查;只允许已验证 transfer 使用旧锁例外。Stage 上传成功不等于最终归属,完成判断必须重新读取非 Stage 的 active 视图,管理工作流仍使用 fresh 条件。
|
|
295
290
|
|
|
296
|
-
### 11.4
|
|
291
|
+
### 11.4 清理边界与 self-owner
|
|
297
292
|
|
|
298
|
-
|
|
293
|
+
清理以当次读取的 NS 和签名原件为依据,仍须核对来源、归属、版本和远端 CAS;不能删除后续关系的文件。旧 owner 的转移文件仅在目标已为 active 且原授权完整匹配时条件删除。通知丢失或进程退出后,没有 Ownership 历史任务负责自动补偿。
|
|
294
|
+
|
|
295
|
+
self-owner 在一次调用内执行本端两种角色的文件动作,不发送自 P2P,仍分别签双文件并核验;human 禁止声明 owner,原/新 owner 相同不是有效转移。清理后重绑同一 owner,先完成 sub 旧声明的内部清理再发布新 declare。
|
|
296
|
+
|
|
297
|
+
### 11.5 本地双身份自动绑定
|
|
298
|
+
|
|
299
|
+
`ownerClient.bindLocalSubaid(subClient)` 是 Facade 编排:两端必须为不同的普通 agent,且本地持有各自有效私钥、证书和已发布名片。daemon 成为自己创建的 sub 的 owner 时使用此方法;human 成为 owner 仍使用请求/明确确认流程。
|
|
300
|
+
|
|
301
|
+
```mermaid
|
|
302
|
+
sequenceDiagram
|
|
303
|
+
participant F as owner Facade
|
|
304
|
+
participant O as owner 操作短连接
|
|
305
|
+
participant S as sub 操作短连接
|
|
306
|
+
participant T as owner Storage
|
|
307
|
+
participant N as NS
|
|
308
|
+
F->>F: 校验两份身份;生成 ownership-UUID 专属 slot
|
|
309
|
+
F->>O: 登录短连接;禁后台同步和自动重连
|
|
310
|
+
F->>S: 登录短连接;禁后台同步和自动重连
|
|
311
|
+
F->>N: fresh 读取 active 和条件;核对原件
|
|
312
|
+
alt 目标已锁且匹配
|
|
313
|
+
N-->>F: 确认当前关系,避免重写
|
|
314
|
+
else 尚未绑定
|
|
315
|
+
O->>T: 本人签发并 CAS 上传 declare owner.md
|
|
316
|
+
S->>N: 本人签发并 CAS 上传 agent.md
|
|
317
|
+
F->>N: 非 Stage active 查询,核对目标 owner/版本/locked
|
|
318
|
+
end
|
|
319
|
+
F->>O: finally close
|
|
320
|
+
F->>S: finally close
|
|
321
|
+
```
|
|
299
322
|
|
|
300
|
-
|
|
323
|
+
SDK 内部应复用既有文件层,不发 P2P,不建立任务或 WAL。TS Facade 的关键装配如下,身份 guard 和源身份校验见 [client.ts](../../ts/src/client.ts):
|
|
324
|
+
|
|
325
|
+
```ts
|
|
326
|
+
try {
|
|
327
|
+
for (const client of clients) await client.connect({ connection_kind: 'short', background_sync: false, auto_reconnect: false });
|
|
328
|
+
return await ownerWorkflow.bindLocalSubaid(subWorkflow);
|
|
329
|
+
} finally {
|
|
330
|
+
await Promise.all(clients.map((client) => client.close()));
|
|
331
|
+
}
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
每个操作 client 使用与源长连接不同的 `ownership-UUID` slot;同次操作在不同 AID 命名空间下可复用这个 slot 值。源 client 提供身份材料,Facade 自行创建并关闭操作连接,调用方不必手动再登录一次。
|
|
335
|
+
|
|
336
|
+
### 11.6 本地三身份自动转移
|
|
337
|
+
|
|
338
|
+
`oldOwnerClient.transferLocalSubaid(subClient, newOwnerClient)` 同时需要旧 owner、sub、新 owner 三份不同的普通 agent 身份。身份和 slot 规则与本地绑定相同,流程按旧授权、新 owner 文件、sub 文件顺序执行:
|
|
339
|
+
|
|
340
|
+
```mermaid
|
|
341
|
+
sequenceDiagram
|
|
342
|
+
participant F as 旧 owner Facade
|
|
343
|
+
participant O as 旧 owner 操作短连接
|
|
344
|
+
participant S as sub 操作短连接
|
|
345
|
+
participant W as 新 owner 操作短连接
|
|
346
|
+
participant T as Storage
|
|
347
|
+
participant N as NS
|
|
348
|
+
F->>F: 校验三份身份;创建并登录专属 slot 短连接
|
|
349
|
+
F->>N: fresh 读取 active 和本人原件
|
|
350
|
+
alt 目标尚未锁定
|
|
351
|
+
F->>N: 验证旧 owner/版本/locked
|
|
352
|
+
O->>T: 旧 owner CAS 上传 transfer_owner 授权
|
|
353
|
+
W->>T: 新 owner CAS 上传目标 declare owner.md
|
|
354
|
+
S->>N: sub CAS 上传目标 agent.md
|
|
355
|
+
N->>N: 验证三份材料并原子切换 active
|
|
356
|
+
else 目标已锁且匹配
|
|
357
|
+
F->>F: 避免重复提交,核对剩余旧授权
|
|
358
|
+
end
|
|
359
|
+
F->>N: 非 Stage active 查询确认新 owner/版本/locked
|
|
360
|
+
O->>T: 核对原授权后条件清理旧 owner.md
|
|
361
|
+
F->>O: finally close
|
|
362
|
+
F->>S: finally close
|
|
363
|
+
F->>W: finally close
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
公有方法使用源 client,内部临时连接和文件上传由 SDK 完成:
|
|
367
|
+
|
|
368
|
+
```ts
|
|
369
|
+
await daemonClient.bindLocalSubaid(subClient);
|
|
370
|
+
await daemonClient.transferLocalSubaid(subClient, nextOwnerClient);
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
执行期间每次异步副作用前后核对源身份未变;三份材料不交给同一个身份代签或代上传。目标已经 active 时避免重复提交,清理仍核对旧授权;失败后显式重新调用,不恢复本地操作阶段。
|
|
301
374
|
|
|
302
375
|
## 12. 普通/群名片、注册与兼容边界
|
|
303
376
|
|
|
304
377
|
- 普通名片失败稿留本地;显式重试先检查保存的条件,再回源。缺条件不能因远端同字节而清待办;正文更新保留完整关系材料。重连不自动 PUT 普通 pending。
|
|
305
378
|
- 实体 pending 保存完整多签原文、原 CAS、固定 owner 和实体证书代际,应用显式重试;转移/rekey/解散后停止旧待办。现代上传取消额外 V2 授权,旧入口按协议 §2.5 隔离兼容;专属 create/transfer checkpoint 恢复不变。
|
|
306
379
|
- fresh 实体 GET 同时发 Check、no-cache、multi-v1,验证全部签块及当前实体代际,仍为 not_applicable。独立 AIDStore 的 Python/Go 实体上传需注入权威 owner resolver,TS/JS 默认经 fresh NS/PKI 核对。
|
|
307
|
-
- 普通 agent 注册 owner
|
|
308
|
-
-
|
|
380
|
+
- 普通 agent 注册 owner 可选;初始名片不写关系,有 owner 时注册当次使用专属短连接立即发起绑定并 finally close,不等后续 connect 恢复。部分 SDK 仍保留 registration_intent 元数据用于注册信息或兼容读取,不再据此恢复 Ownership 任务;注册成功不等于关系完成。
|
|
381
|
+
- 支持原 2.1 的旧 NS,普通首次绑定、local bind 和新 owner 文件先齐的 local transfer 正常路径仍可用。完整支持双方任意到达顺序、转移候选隔离、Stage 交错重试及同事实重签,需要本轮 NS;旧 NS 在 sub 先接受 transfer 时可能报错,或因 owner SHA 基线导致已齐文件持续 pending、同事实重签被拒绝。
|
|
382
|
+
- 新 SDK 不调用 decisions;仍依赖该接口的过渡 SDK 在接口删除后会遇到 404。旧 proof 或 metadata 不当作 unbound 或当前授权,不能借升级清空锁。不能把 Rust 的旧任务恢复或 Stage 限制当成本轮四 SDK 的行为。
|
|
309
383
|
|
|
310
384
|
## 13. 跨语言 TDD 与有序验收
|
|
311
385
|
|
|
@@ -315,8 +389,11 @@ self-owner 合并本端角色和决定,不发送自 P2P,仍分别签双文
|
|
|
315
389
|
| --- | --- |
|
|
316
390
|
| 文件与密码 | 双角色、严格词法、字节限制、DER、多签、当前证书;invalid/unavailable 分类。 |
|
|
317
391
|
| 权威适配 | NS 原条件/可靠不存在、Storage 完整发布/删除 CAS、fresh 稳定性和一次重查。 |
|
|
318
|
-
|
|
|
319
|
-
|
|
|
320
|
-
|
|
|
321
|
-
|
|
322
|
-
|
|
392
|
+
| 当次编排/实际收件 | 不依赖任务/WAL/decisions;非法控制单条隔离,合法处理错误不 ACK;保留通用投递进度持久化。 |
|
|
393
|
+
| NS 一致性 | 未锁 B→C 候选替换、拒绝不回滚、锁后 accept/reject 不能改归属、相同归属事实重签。 |
|
|
394
|
+
| 转移顺序 | 旧授权先发布;sub 先/新 owner 先均可,旧 active 保留,材料齐后原子切换;Stage locked/base 与 locked/target。 |
|
|
395
|
+
| 故障后重试 | 提交丢响应、上传后未通知、过期、后续关系、清理失败;重新查询/发起,不能期待任务自动恢复。 |
|
|
396
|
+
| 本地编排 | 两/三身份与专属 slot,禁 P2P,源身份变化停止,异常仍关闭全部短连接,完成须核对 active。 |
|
|
397
|
+
| 网络验收 | 单域 → E2E → cross-SDK → 双域;轮换角色,记录服务端权威文件与锁。 |
|
|
398
|
+
|
|
399
|
+
失败即停,修复失败项后从测试检查点续跑;已通过且不受影响的项不重跑。命令见 [测试指南](../aun测试运行指南.md),历史验收范围见 [TDD 账本](Ownership2.1-TDD分阶段实施计划.md)。测试 runner 的检查点不等于产品 Ownership 任务恢复;单元通过不代替真实浏览器、跨域或故障后显式重试验收。
|
package/dist/agent-md.d.ts
CHANGED
|
@@ -133,7 +133,7 @@ export declare class AgentMdManager {
|
|
|
133
133
|
content: string;
|
|
134
134
|
etag: string;
|
|
135
135
|
}>;
|
|
136
|
-
readOwnershipFresh(aid: string): Promise<OwnershipAgentObservation>;
|
|
136
|
+
readOwnershipFresh(aid: string, stage?: boolean): Promise<OwnershipAgentObservation>;
|
|
137
137
|
_readOwnershipQuery(aid: string): Promise<OwnershipAgentObservation>;
|
|
138
138
|
private _readOwnership;
|
|
139
139
|
submitOwnershipRaw(aid: string, raw: string, condition: OwnershipSavedFile['condition'], assertActive?: () => void, stage?: boolean): Promise<OwnershipAgentObservation>;
|
package/dist/agent-md.js
CHANGED
|
@@ -425,15 +425,15 @@ export class AgentMdManager {
|
|
|
425
425
|
throw ownershipFailure('unavailable', 'agent.md fresh read has no ETag', true);
|
|
426
426
|
return { exists: true, content: response.text, etag };
|
|
427
427
|
}
|
|
428
|
-
async readOwnershipFresh(aid) {
|
|
429
|
-
return this._readOwnership(aid, true);
|
|
428
|
+
async readOwnershipFresh(aid, stage = false) {
|
|
429
|
+
return this._readOwnership(aid, true, stage);
|
|
430
430
|
}
|
|
431
431
|
async _readOwnershipQuery(aid) {
|
|
432
432
|
return this._readOwnership(aid, false);
|
|
433
433
|
}
|
|
434
|
-
async _readOwnership(aid, fresh) {
|
|
434
|
+
async _readOwnership(aid, fresh, stage = false) {
|
|
435
435
|
const target = this.safeAid(aid);
|
|
436
|
-
const response = fresh ? await this._ownershipRequest(target, 'GET') : await this._ownershipRequest(target, 'GET', undefined, undefined, false, undefined, undefined, false);
|
|
436
|
+
const response = fresh ? stage ? await this._ownershipRequest(target, 'GET', undefined, undefined, false, undefined, true) : await this._ownershipRequest(target, 'GET') : await this._ownershipRequest(target, 'GET', undefined, undefined, false, undefined, stage || undefined, false);
|
|
437
437
|
if ([200, 400, 404].includes(response.status) && headerValue(response.headers, 'X-AUN-Ownership-Status') === 'not_applicable')
|
|
438
438
|
throw ownershipFailure('not_applicable', 'group/org uses its lifecycle owner relationship');
|
|
439
439
|
if (response.status === 400) {
|
|
@@ -473,9 +473,10 @@ export class AgentMdManager {
|
|
|
473
473
|
throw ownershipFailure('forbidden', 'raw agent.md submission requires its own authenticated identity');
|
|
474
474
|
if (!condition || !condition.if_match && condition.if_none_match !== '*' || condition.if_match && condition.if_none_match)
|
|
475
475
|
throw new ValidationError('raw agent.md requires one original condition');
|
|
476
|
-
parseOwnershipFile(raw, 'agent', { expectedSubAid: target });
|
|
476
|
+
const submitted = parseOwnershipFile(raw, 'agent', { expectedSubAid: target });
|
|
477
477
|
verifyAgentMdUpload(raw, current.certPem, target);
|
|
478
|
-
|
|
478
|
+
const original = stage ? await this.readOwnershipFresh(target) : undefined;
|
|
479
|
+
if (stage && original?.stage_supported !== true)
|
|
479
480
|
throw ownershipFailure('unsupported_protocol', 'NS does not support Ownership Stage', true);
|
|
480
481
|
const response = stage ? await this._ownershipRequest(target, 'PUT', raw, condition, false, assertActive, true) : await this._ownershipRequest(target, 'PUT', raw, condition, false, assertActive);
|
|
481
482
|
if (response.status === 409 || response.status === 412)
|
|
@@ -488,9 +489,10 @@ export class AgentMdManager {
|
|
|
488
489
|
throw ownershipFailure('invalid', 'NS rejected the submitted agent.md: HTTP 400');
|
|
489
490
|
if (response.status < 200 || response.status >= 300)
|
|
490
491
|
throw ownershipFailure('unavailable', `NS raw submission failed: HTTP ${response.status}`, true);
|
|
491
|
-
|
|
492
|
+
const stagedTransfer = original?.lock === 'locked' && submitted.statement?.relation_version === original.relation_version + 1 && headerValue(response.headers, 'X-AUN-Ownership-Lock') === 'locked' && [String(original.relation_version), String(submitted.statement.relation_version)].includes(headerValue(response.headers, 'X-AUN-Ownership-Version'));
|
|
493
|
+
if (stage && (headerValue(response.headers, 'X-AUN-Ownership-Stage') !== '1' || !stagedTransfer && (headerValue(response.headers, 'X-AUN-Ownership-Lock') !== 'unlocked' || headerValue(response.headers, 'X-AUN-Ownership-Status') !== 'pending')))
|
|
492
494
|
throw ownershipFailure('unsupported_protocol', 'NS did not confirm Ownership Stage', true);
|
|
493
|
-
const fresh = await this.readOwnershipFresh(target);
|
|
495
|
+
const fresh = stage ? await this.readOwnershipFresh(target, true) : await this.readOwnershipFresh(target);
|
|
494
496
|
if (fresh.content !== raw)
|
|
495
497
|
throw ownershipFailure('unavailable', 'NS raw submission cannot be confirmed from the current document', true);
|
|
496
498
|
return fresh;
|
|
@@ -498,9 +500,9 @@ export class AgentMdManager {
|
|
|
498
500
|
async _ownershipRequest(target, method, raw, condition, legacyRead = false, assertActive, stage, fresh = true) {
|
|
499
501
|
const gatewayUrl = await this._resolveGateway(target);
|
|
500
502
|
const headers = { 'X-AUN-Signature-Format': 'multi-v1', Accept: 'text/markdown', ...(fresh ? { 'Cache-Control': 'no-cache' } : {}), ...(!legacyRead ? { 'X-AUN-Ownership-Check': '1' } : {}) };
|
|
503
|
+
if (stage)
|
|
504
|
+
headers['X-AUN-Ownership-Stage'] = '1';
|
|
501
505
|
if (method === 'PUT') {
|
|
502
|
-
if (stage)
|
|
503
|
-
headers['X-AUN-Ownership-Stage'] = '1';
|
|
504
506
|
headers['Content-Type'] = 'text/markdown; charset=utf-8';
|
|
505
507
|
headers.Authorization = `Bearer ${await this._uploadAccessToken(target, gatewayUrl)}`;
|
|
506
508
|
if (condition?.if_match)
|
|
@@ -1047,8 +1049,8 @@ export class AgentMdManager {
|
|
|
1047
1049
|
if (!target || (!remoteEtag && !remoteLastModified))
|
|
1048
1050
|
return;
|
|
1049
1051
|
const before = this.loadRecord(target) ?? {};
|
|
1050
|
-
const same =
|
|
1051
|
-
|
|
1052
|
+
const same = remoteEtag ? String(before.remote_etag ?? '').trim() === remoteEtag :
|
|
1053
|
+
!remoteLastModified || String(before.last_modified ?? '').trim() === remoteLastModified;
|
|
1052
1054
|
const hadRemoteVersion = Boolean(String(before.remote_etag ?? '').trim() || String(before.last_modified ?? '').trim());
|
|
1053
1055
|
let record = { ...before };
|
|
1054
1056
|
if (!same || Object.keys(before).length === 0) {
|