agent-bridge-aun 1.0.41 → 1.0.44

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.
@@ -1,19 +1,126 @@
1
1
  {
2
2
  "type": "object",
3
3
  "properties": {
4
- "identities": { "type": "array", "items": { "type": "string" } },
5
- "created_aids": { "type": "array", "items": { "type": "string" } },
6
- "bindings": { "type": "object", "additionalProperties": { "type": "string" } },
7
- "binding_times": { "type": "object", "additionalProperties": { "type": "string" } },
8
- "aid": { "type": "string", "minLength": 1 },
9
- "owner_aid": { "type": "string", "minLength": 1 },
10
- "agent_plugin": { "type": "string", "description": "兼容旧配置;消息只按 AID 的显式关联路由。" },
11
- "aun_path": { "type": "string", "minLength": 1 },
12
- "encryption_seed": { "type": "string" },
13
- "verify_ssl": { "type": "boolean", "default": true },
14
- "root_ca_path": { "type": "string" },
15
- "allow_from": { "type": "string", "default": "", "description": "留空接受所有 AUN Peer;填写 AID 后限制来源,可用英文逗号分隔。" },
16
- "delivery_mode": { "enum": ["fanout", "queue"] }
4
+ "identities": {
5
+ "type": "array",
6
+ "items": {
7
+ "type": "string"
8
+ }
9
+ },
10
+ "created_aids": {
11
+ "type": "array",
12
+ "items": {
13
+ "type": "string"
14
+ }
15
+ },
16
+ "bindings": {
17
+ "type": "object",
18
+ "additionalProperties": {
19
+ "type": "string"
20
+ }
21
+ },
22
+ "binding_times": {
23
+ "type": "object",
24
+ "additionalProperties": {
25
+ "type": "string"
26
+ }
27
+ },
28
+ "aid": {
29
+ "type": "string",
30
+ "minLength": 1
31
+ },
32
+ "owner_aid": {
33
+ "type": "string",
34
+ "minLength": 1
35
+ },
36
+ "agent_plugin": {
37
+ "type": "string",
38
+ "description": "兼容旧配置;消息只按 AID 的显式关联路由。"
39
+ },
40
+ "aun_path": {
41
+ "type": "string",
42
+ "minLength": 1
43
+ },
44
+ "encryption_seed": {
45
+ "type": "string"
46
+ },
47
+ "verify_ssl": {
48
+ "type": "boolean",
49
+ "default": true
50
+ },
51
+ "root_ca_path": {
52
+ "type": "string"
53
+ },
54
+ "allow_from": {
55
+ "type": "string",
56
+ "default": "",
57
+ "description": "留空接受所有 AUN Peer;填写 AID 后限制来源,可用英文逗号分隔。"
58
+ },
59
+ "delivery_mode": {
60
+ "enum": [
61
+ "fanout",
62
+ "queue"
63
+ ]
64
+ },
65
+ "ability_selections": {
66
+ "type": "object",
67
+ "default": {},
68
+ "description": "按 Provider AID 保存明确批准的能力发布路径;默认为不发布。",
69
+ "additionalProperties": {
70
+ "type": "array",
71
+ "items": {
72
+ "type": "string"
73
+ },
74
+ "uniqueItems": true
75
+ }
76
+ },
77
+ "ability_ledgers": {
78
+ "type": "object",
79
+ "default": {},
80
+ "description": "AUN 能力发布归属与中断恢复账本,由通道维护。",
81
+ "additionalProperties": {
82
+ "type": "object",
83
+ "additionalProperties": {
84
+ "type": "object",
85
+ "required": [
86
+ "definition_id",
87
+ "release_id",
88
+ "plugin_id",
89
+ "plugin_version"
90
+ ],
91
+ "properties": {
92
+ "definition_id": {
93
+ "type": "string"
94
+ },
95
+ "release_id": {
96
+ "type": "string"
97
+ },
98
+ "plugin_id": {
99
+ "type": "string"
100
+ },
101
+ "plugin_version": {
102
+ "type": "string"
103
+ },
104
+ "previous_definition_ids": {
105
+ "type": "array",
106
+ "items": {
107
+ "type": "string"
108
+ }
109
+ }
110
+ },
111
+ "additionalProperties": false
112
+ }
113
+ }
114
+ },
115
+ "ability_owners": {
116
+ "type": "object",
117
+ "default": {},
118
+ "description": "宿主管理员为每个 Provider AID 明确委托的能力确认 Owner;不等于 AUN 协议 Ownership,需验证该 AID 的控制权。",
119
+ "additionalProperties": {
120
+ "type": "string",
121
+ "pattern": "^[a-z0-9][a-z0-9_-]{2,63}\\.[a-z0-9-]+(?:\\.[a-z0-9-]+)+$"
122
+ }
123
+ }
17
124
  },
18
125
  "additionalProperties": false
19
126
  }
@@ -0,0 +1,318 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AbilityOperationStore = void 0;
4
+ exports.abilityOperationKey = abilityOperationKey;
5
+ const node_crypto_1 = require("node:crypto");
6
+ const promises_1 = require("node:fs/promises");
7
+ const node_path_1 = require("node:path");
8
+ function failure(code, message) {
9
+ return Object.assign(new Error(`${code}: ${message}`), { code });
10
+ }
11
+ function canonical(value, parents = new Set()) {
12
+ if (value === null || typeof value === "string" || typeof value === "boolean")
13
+ return JSON.stringify(value);
14
+ if (typeof value === "number" && Number.isFinite(value))
15
+ return JSON.stringify(value);
16
+ if (!value || typeof value !== "object" || parents.has(value))
17
+ throw failure("params_invalid", "操作数据必须是有效 JSON");
18
+ parents.add(value);
19
+ try {
20
+ if (Array.isArray(value))
21
+ return `[${Array.from(value, item => canonical(item, parents)).join(",")}]`;
22
+ if (Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null)
23
+ throw failure("params_invalid", "操作数据必须是普通 JSON 对象");
24
+ return `{${Object.keys(value).sort().map(key => `${JSON.stringify(key)}:${canonical(value[key], parents)}`).join(",")}}`;
25
+ }
26
+ finally {
27
+ parents.delete(value);
28
+ }
29
+ }
30
+ function digest(value) { return (0, node_crypto_1.createHash)("sha256").update(canonical(value)).digest("hex"); }
31
+ function abilityOperationKey(input) {
32
+ return digest([input.providerAid, input.authenticatedCaller, input.operationId]);
33
+ }
34
+ function operationFingerprint(input) {
35
+ return digest({ providerAid: input.providerAid, authenticatedCaller: input.authenticatedCaller, operationId: input.operationId,
36
+ path: input.path, definitionId: input.definitionId, params: input.params, effects: input.effects, executionTarget: input.executionTarget, async: input.async ?? false });
37
+ }
38
+ function confirmationFingerprint(input, fingerprint) {
39
+ return digest({ key: abilityOperationKey(input), fingerprint, expiresAt: input.expiresAt });
40
+ }
41
+ function validateInput(input) {
42
+ for (const field of ["providerAid", "authenticatedCaller", "operationId", "callId", "path", "definitionId"]) {
43
+ if (typeof input[field] !== "string" || !input[field] || input[field].length > 512 || /[\u0000-\u001f\u007f]/.test(input[field]))
44
+ throw failure("params_invalid", `操作字段 ${field} 无效`);
45
+ }
46
+ if (!input.params || typeof input.params !== "object" || Array.isArray(input.params)
47
+ || !input.executionTarget || typeof input.executionTarget !== "object" || Array.isArray(input.executionTarget)
48
+ || !Array.isArray(input.effects) || !input.effects.length || !input.effects.every(item => typeof item === "string" && item.length > 0)
49
+ || !Number.isSafeInteger(input.expiresAt) || input.expiresAt <= 0
50
+ || !Number.isSafeInteger(input.dedupeRetentionSeconds) || input.dedupeRetentionSeconds <= 0
51
+ || !Number.isSafeInteger(input.dedupeRetentionSeconds * 1000)
52
+ || (input.async !== undefined && typeof input.async !== "boolean"))
53
+ throw failure("params_invalid", "操作契约、参数或过期时间无效");
54
+ if (Buffer.byteLength(canonical(input.params)) > 256 * 1024)
55
+ throw failure("params_invalid", "操作参数超过 256 KiB");
56
+ canonical(input.executionTarget);
57
+ }
58
+ function response(entry, code, message, executionState = "not_executed") {
59
+ return { type: "ability", role: "result", call_id: entry.callId, operation_id: entry.operationId, ability_path: entry.path, definition_id: entry.definitionId,
60
+ ok: false, execution_state: executionState, error: { code, message } };
61
+ }
62
+ function validEntry(value) {
63
+ if (!value || typeof value !== "object" || Array.isArray(value))
64
+ return false;
65
+ const entry = value;
66
+ try {
67
+ validateInput(entry);
68
+ if (entry.key !== abilityOperationKey(entry) || entry.fingerprint !== operationFingerprint(entry)
69
+ || entry.confirmationFingerprint !== confirmationFingerprint(entry, entry.fingerprint)
70
+ || !["pending_confirmation", "executing", "succeeded", "failed", "unknown"].includes(entry.state)
71
+ || ![entry.createdAt, entry.updatedAt, entry.retainUntil].every(Number.isSafeInteger)
72
+ || entry.createdAt < 0 || entry.updatedAt < entry.createdAt || entry.retainUntil < entry.createdAt + entry.dedupeRetentionSeconds * 1000)
73
+ return false;
74
+ if (["succeeded", "failed", "unknown"].includes(entry.state) && (!entry.result || typeof entry.result !== "object" || Array.isArray(entry.result)))
75
+ return false;
76
+ if (["pending_confirmation", "executing"].includes(entry.state) && entry.result !== undefined)
77
+ return false;
78
+ if (entry.result !== undefined)
79
+ canonical(entry.result);
80
+ return true;
81
+ }
82
+ catch {
83
+ return false;
84
+ }
85
+ }
86
+ class AbilityOperationStore {
87
+ file;
88
+ now;
89
+ entries = new Map();
90
+ writes = Promise.resolve();
91
+ constructor(file, now) {
92
+ this.file = file;
93
+ this.now = now;
94
+ }
95
+ static async open(file, options = {}) {
96
+ const store = new AbilityOperationStore(file, options.now ?? Date.now);
97
+ let raw;
98
+ try {
99
+ raw = await (0, promises_1.readFile)(file, "utf8");
100
+ }
101
+ catch (error) {
102
+ if (error.code === "ENOENT")
103
+ return store;
104
+ throw error;
105
+ }
106
+ let snapshot;
107
+ try {
108
+ snapshot = JSON.parse(raw);
109
+ }
110
+ catch {
111
+ throw failure("operation_store_corrupt", "操作账本损坏,已停止新的执行");
112
+ }
113
+ if (!snapshot || snapshot.version !== 1 || !Array.isArray(snapshot.entries) || !snapshot.entries.every(validEntry))
114
+ throw failure("operation_store_corrupt", "操作账本格式无效,已停止新的执行");
115
+ let migrated = false;
116
+ for (const entry of snapshot.entries) {
117
+ if (store.entries.has(entry.key))
118
+ throw failure("operation_store_corrupt", "操作账本含重复幂等键");
119
+ const legacyExpired = entry.result?.error?.code === "expired";
120
+ if (legacyExpired) {
121
+ const result = structuredClone(entry.result);
122
+ result.error = { ...result.error, code: "message_expired" };
123
+ store.entries.set(entry.key, { ...entry, result });
124
+ migrated = true;
125
+ }
126
+ else
127
+ store.entries.set(entry.key, entry);
128
+ }
129
+ if (migrated)
130
+ await store.persist(new Map(store.entries));
131
+ await store.interruptExecuting();
132
+ return store;
133
+ }
134
+ serial(run) {
135
+ const next = this.writes.catch(() => undefined).then(run);
136
+ this.writes = next;
137
+ return next;
138
+ }
139
+ async persist(next) {
140
+ const directory = (0, node_path_1.dirname)(this.file);
141
+ await (0, promises_1.mkdir)(directory, { recursive: true, mode: 0o700 });
142
+ const temp = `${this.file}.${(0, node_crypto_1.randomUUID)()}.tmp`;
143
+ const data = `${JSON.stringify({ version: 1, entries: [...next.values()] })}\n`;
144
+ try {
145
+ const handle = await (0, promises_1.open)(temp, "wx", 0o600);
146
+ try {
147
+ await handle.writeFile(data, "utf8");
148
+ await handle.sync();
149
+ }
150
+ finally {
151
+ await handle.close();
152
+ }
153
+ await (0, promises_1.rename)(temp, this.file);
154
+ this.entries = next;
155
+ }
156
+ finally {
157
+ await (0, promises_1.rm)(temp, { force: true });
158
+ }
159
+ }
160
+ expirePending(entries, now) {
161
+ const next = new Map(entries);
162
+ let changed = false;
163
+ for (const entry of entries.values()) {
164
+ if (entry.state !== "pending_confirmation" || now < entry.expiresAt)
165
+ continue;
166
+ next.set(entry.key, { ...entry, state: "failed", updatedAt: Math.max(entry.updatedAt, now),
167
+ retainUntil: Math.max(entry.retainUntil, now + entry.dedupeRetentionSeconds * 1000),
168
+ result: response(entry, "message_expired", "调用消息已过期,不会执行") });
169
+ changed = true;
170
+ }
171
+ return { entries: next, changed };
172
+ }
173
+ draft(input) {
174
+ return this.serial(async () => {
175
+ validateInput(input);
176
+ const copied = JSON.parse(canonical(input));
177
+ const key = abilityOperationKey(copied);
178
+ const fingerprint = operationFingerprint(copied);
179
+ const now = this.now();
180
+ const expired = this.expirePending(this.entries, now);
181
+ const next = new Map([...expired.entries].filter(([, item]) => !["succeeded", "failed"].includes(item.state) || item.retainUntil > now));
182
+ const existing = next.get(key);
183
+ if (existing) {
184
+ if (expired.changed)
185
+ await this.persist(next);
186
+ if (existing.fingerprint !== fingerprint)
187
+ throw failure("idempotency_conflict", "相同 operation_id 的请求内容已改变");
188
+ return { created: false, entry: structuredClone(existing) };
189
+ }
190
+ const retainUntil = now + copied.dedupeRetentionSeconds * 1000;
191
+ if (!Number.isSafeInteger(retainUntil))
192
+ throw failure("params_invalid", "操作保留期无效");
193
+ const entry = { ...copied, key, fingerprint, confirmationFingerprint: confirmationFingerprint(copied, fingerprint),
194
+ state: "pending_confirmation", createdAt: now, updatedAt: now, retainUntil };
195
+ next.set(key, entry);
196
+ await this.persist(next);
197
+ return { created: true, entry: structuredClone(entry) };
198
+ });
199
+ }
200
+ approve(key, fingerprint) {
201
+ return this.confirm(key, fingerprint, true);
202
+ }
203
+ reject(key, fingerprint) {
204
+ return this.confirm(key, fingerprint, false);
205
+ }
206
+ confirm(key, fingerprint, approve) {
207
+ return this.serial(async () => {
208
+ const expired = this.expirePending(this.entries, this.now());
209
+ if (expired.changed)
210
+ await this.persist(expired.entries);
211
+ const existing = this.entries.get(key);
212
+ if (!existing)
213
+ throw failure("operation_not_found", "操作记录不存在");
214
+ if (existing.confirmationFingerprint !== fingerprint)
215
+ throw failure("confirmation_mismatch", "确认内容与原请求不一致");
216
+ if (existing.state !== "pending_confirmation")
217
+ return { started: false, entry: structuredClone(existing) };
218
+ const now = this.now();
219
+ const messageExpired = now >= existing.expiresAt;
220
+ const started = approve && !messageExpired;
221
+ const entry = { ...existing, state: started ? "executing" : "failed", updatedAt: Math.max(existing.updatedAt, now),
222
+ retainUntil: Math.max(existing.retainUntil, now + existing.dedupeRetentionSeconds * 1000),
223
+ ...(!started ? { result: messageExpired ? response(existing, "message_expired", "调用消息已过期,不会执行") : response(existing, "user_denied", "Owner 已拒绝本次调用") } : {}) };
224
+ const next = new Map(this.entries);
225
+ next.set(key, entry);
226
+ await this.persist(next);
227
+ return { started, entry: structuredClone(entry) };
228
+ });
229
+ }
230
+ finish(key, state, result) {
231
+ return this.serial(async () => {
232
+ const existing = this.entries.get(key);
233
+ if (!existing)
234
+ throw failure("operation_not_found", "操作记录不存在");
235
+ if (existing.state !== "executing")
236
+ throw failure("operation_not_executing", "只有本次已批准执行的操作可以保存结果");
237
+ if (!result || typeof result !== "object" || Array.isArray(result))
238
+ throw failure("result_invalid", "操作结果必须是 JSON 对象");
239
+ const saved = JSON.parse(canonical(result));
240
+ if (Buffer.byteLength(canonical(saved)) > 512 * 1024)
241
+ throw failure("result_invalid", "操作结果超过 512 KiB");
242
+ const now = this.now();
243
+ const entry = { ...existing, state, updatedAt: Math.max(existing.updatedAt, now), retainUntil: Math.max(existing.retainUntil, now + existing.dedupeRetentionSeconds * 1000),
244
+ result: { ...saved, type: "ability", role: "result", call_id: existing.callId, operation_id: existing.operationId, ability_path: existing.path, definition_id: existing.definitionId } };
245
+ const next = new Map(this.entries);
246
+ next.set(key, entry);
247
+ await this.persist(next);
248
+ return structuredClone(entry);
249
+ });
250
+ }
251
+ replay(key, callId) {
252
+ return this.serial(async () => {
253
+ if (typeof callId !== "string" || !callId || callId.length > 512 || /[\u0000-\u001f\u007f]/.test(callId))
254
+ throw failure("params_invalid", "调用关联标识无效");
255
+ const expired = this.expirePending(this.entries, this.now());
256
+ if (expired.changed)
257
+ await this.persist(expired.entries);
258
+ const existing = this.entries.get(key);
259
+ if (!existing)
260
+ return undefined;
261
+ const entry = structuredClone(existing);
262
+ entry.callId = callId;
263
+ if (entry.result)
264
+ entry.result.call_id = callId;
265
+ return entry;
266
+ });
267
+ }
268
+ get(key) {
269
+ return this.serial(async () => {
270
+ const expired = this.expirePending(this.entries, this.now());
271
+ if (expired.changed)
272
+ await this.persist(expired.entries);
273
+ const entry = this.entries.get(key);
274
+ return entry ? structuredClone(entry) : undefined;
275
+ });
276
+ }
277
+ interruptExecuting() {
278
+ return this.serial(async () => {
279
+ const executing = [...this.entries.values()].filter(entry => entry.state === "executing");
280
+ if (!executing.length)
281
+ return;
282
+ const next = new Map(this.entries);
283
+ for (const entry of executing)
284
+ next.set(entry.key, { ...entry, state: "unknown", updatedAt: Math.max(entry.updatedAt, this.now()), result: response(entry, "execution_unknown", "执行曾被中断,必须先核对结果,不会自动重试", "unknown") });
285
+ await this.persist(next);
286
+ });
287
+ }
288
+ pending() {
289
+ return this.serial(async () => {
290
+ const expired = this.expirePending(this.entries, this.now());
291
+ if (expired.changed)
292
+ await this.persist(expired.entries);
293
+ return [...this.entries.values()].filter(entry => entry.state === "pending_confirmation").map(entry => structuredClone(entry));
294
+ });
295
+ }
296
+ invalidateProvider(providerAid, code = "provider_invalidated", message = "Provider 发布或 Owner policy 已改变,本次调用不会执行") {
297
+ return this.serial(async () => {
298
+ if (typeof providerAid !== "string" || !providerAid || typeof code !== "string" || !code || typeof message !== "string" || !message)
299
+ throw failure("params_invalid", "Provider 失效参数无效");
300
+ const now = this.now();
301
+ const expired = this.expirePending(this.entries, now);
302
+ const next = expired.entries;
303
+ let invalidated = 0;
304
+ for (const entry of next.values()) {
305
+ if (entry.providerAid !== providerAid || entry.state !== "pending_confirmation")
306
+ continue;
307
+ next.set(entry.key, { ...entry, state: "failed", updatedAt: Math.max(entry.updatedAt, now),
308
+ retainUntil: Math.max(entry.retainUntil, now + entry.dedupeRetentionSeconds * 1000),
309
+ result: response(entry, code, message) });
310
+ invalidated++;
311
+ }
312
+ if (invalidated || expired.changed)
313
+ await this.persist(next);
314
+ return invalidated;
315
+ });
316
+ }
317
+ }
318
+ exports.AbilityOperationStore = AbilityOperationStore;
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isAbilityMessage = isAbilityMessage;
4
+ exports.parseAbilityEnvelope = parseAbilityEnvelope;
5
+ exports.abilityFailureResult = abilityFailureResult;
6
+ const aidPattern = /^[a-z0-9][a-z0-9_-]{2,63}\.[a-z0-9-]+(?:\.[a-z0-9-]+)+$/;
7
+ function isAbilityMessage(value) {
8
+ if (!value || typeof value !== "object")
9
+ return false;
10
+ const body = value.payload;
11
+ return !!body && typeof body === "object" && !Array.isArray(body) && body.type === "ability";
12
+ }
13
+ function parseAbilityEnvelope(value) {
14
+ if (!isAbilityMessage(value))
15
+ return undefined;
16
+ const event = value;
17
+ const envelope = event.envelope && typeof event.envelope === "object" ? event.envelope : undefined;
18
+ // fastaun 解密事件的顶层 from 来自认证信封,不能读取业务 payload 自报身份。
19
+ const callerAid = typeof event.from === "string" ? event.from : typeof envelope?.from === "string" ? envelope.from : "";
20
+ if (envelope?.from !== undefined && envelope.from !== callerAid)
21
+ return undefined;
22
+ const payload = event.payload;
23
+ if (!aidPattern.test(callerAid) || event.encrypted !== true || payload.role !== "call")
24
+ return undefined;
25
+ if (typeof payload.call_id !== "string" || !payload.call_id || typeof payload.operation_id !== "string" || !payload.operation_id || typeof payload.ability_path !== "string")
26
+ return undefined;
27
+ if (payload.ability_path !== "protocol/discover" && typeof payload.definition_id !== "string")
28
+ return undefined;
29
+ if (payload.params !== undefined && (!payload.params || typeof payload.params !== "object" || Array.isArray(payload.params)))
30
+ return undefined;
31
+ return { callerAid, encrypted: true, payload };
32
+ }
33
+ function abilityFailureResult(payload, code, message) {
34
+ return {
35
+ type: "ability", role: "result",
36
+ call_id: payload.call_id,
37
+ operation_id: payload.operation_id,
38
+ ...(typeof payload.ability_path === "string" ? { ability_path: payload.ability_path } : {}),
39
+ ...(typeof payload.definition_id === "string" ? { definition_id: payload.definition_id } : {}),
40
+ ok: false, execution_state: "not_executed", error: { code, message },
41
+ };
42
+ }
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OwnerProof = void 0;
4
+ const node_crypto_1 = require("node:crypto");
5
+ const lifetime = 10 * 60_000;
6
+ const limit = 1024;
7
+ const aidPattern = /^[a-z0-9][a-z0-9_-]{2,63}\.[a-z0-9-]+(?:\.[a-z0-9-]+)+$/;
8
+ function key(session, providerAid, ownerAid) {
9
+ return JSON.stringify([session.id, providerAid, ownerAid]);
10
+ }
11
+ function validSession(session, now) {
12
+ return typeof session.id === "string" && session.id.length > 0 && session.id.length <= 512
13
+ && !/[\u0000-\u001f\u007f]/.test(session.id) && Number.isSafeInteger(session.expiresAt) && session.expiresAt > now;
14
+ }
15
+ class OwnerProof {
16
+ entries = new Map();
17
+ now;
18
+ constructor(options = {}) { this.now = options.now ?? Date.now; }
19
+ prune(now) {
20
+ for (const [recordKey, entry] of this.entries)
21
+ if (entry.expiresAt <= now)
22
+ this.entries.delete(recordKey);
23
+ }
24
+ start(session, providerAid, ownerAid) {
25
+ const now = this.now();
26
+ if (!validSession(session, now))
27
+ throw new Error("管理会话无效或已过期");
28
+ if (typeof providerAid !== "string" || !aidPattern.test(providerAid) || typeof ownerAid !== "string" || !aidPattern.test(ownerAid))
29
+ throw new Error("Provider 或 Owner AID 无效");
30
+ this.prune(now);
31
+ const recordKey = key(session, providerAid, ownerAid);
32
+ if (!this.entries.has(recordKey) && this.entries.size >= limit)
33
+ throw new Error("Owner 证明记录已达上限,请稍后重试");
34
+ const nonce = (0, node_crypto_1.randomBytes)(32).toString("hex");
35
+ const expiresAt = Math.min(session.expiresAt, now + lifetime);
36
+ this.entries.set(recordKey, { providerAid, ownerAid, sessionExpiresAt: session.expiresAt, expiresAt, nonceHash: (0, node_crypto_1.createHash)("sha256").update(nonce).digest("hex") });
37
+ return { nonce, expiresAt };
38
+ }
39
+ accept(providerAid, authenticatedSenderAid, nonce, encrypted) {
40
+ const now = this.now();
41
+ this.prune(now);
42
+ if (encrypted !== true || !authenticatedSenderAid || typeof nonce !== "string" || !/^[0-9a-f]{64}$/.test(nonce))
43
+ return false;
44
+ const nonceHash = (0, node_crypto_1.createHash)("sha256").update(nonce).digest("hex");
45
+ for (const entry of this.entries.values()) {
46
+ if (entry.providerAid !== providerAid || entry.ownerAid !== authenticatedSenderAid || entry.nonceHash !== nonceHash)
47
+ continue;
48
+ delete entry.nonceHash;
49
+ entry.expiresAt = Math.min(entry.sessionExpiresAt, now + lifetime);
50
+ return true;
51
+ }
52
+ return false;
53
+ }
54
+ verified(session, providerAid, ownerAid) {
55
+ const now = this.now();
56
+ this.prune(now);
57
+ if (!validSession(session, now))
58
+ return false;
59
+ const entry = this.entries.get(key(session, providerAid, ownerAid));
60
+ return entry !== undefined && entry.nonceHash === undefined;
61
+ }
62
+ clear(providerAid) {
63
+ if (providerAid === undefined)
64
+ this.entries.clear();
65
+ else
66
+ for (const [recordKey, entry] of this.entries)
67
+ if (entry.providerAid === providerAid)
68
+ this.entries.delete(recordKey);
69
+ }
70
+ }
71
+ exports.OwnerProof = OwnerProof;