@agentunion/fastaun-browser 0.5.18 → 0.5.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. package/CHANGELOG.md +172 -79
  2. package/_packed_docs/CHANGELOG.md +172 -79
  3. package/_packed_docs/INDEX.md +244 -116
  4. package/_packed_docs/KITE_DOCS_GUIDE.md +98 -69
  5. package/_packed_docs/agent.md/SCHEMA.md +245 -205
  6. 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 +11 -11
  7. package/_packed_docs/aun/345/210/206/345/270/203/345/274/217/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md +42 -15
  8. package/_packed_docs/aun/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md +4 -4
  9. 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 +45 -134
  10. package/_packed_docs/protocol/06-/346/234/215/345/212/241/345/215/217/350/256/256.md +1072 -1072
  11. package/_packed_docs/protocol/07-/351/224/231/350/257/257/347/240/201/344/270/216/347/212/266/346/200/201/346/234/272.md +5 -20
  12. package/_packed_docs/protocol/10-Group-/345/255/220/345/215/217/350/256/256.md +687 -668
  13. package/_packed_docs/protocol/11-Storage-/345/255/220/345/215/217/350/256/256.md +19 -28
  14. package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/211.md +443 -0
  15. package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2112.0.md +161 -0
  16. package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2112.1.md +658 -0
  17. package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2113.0.md +233 -0
  18. package/_packed_docs/protocol/agent.md/SCHEMA.md +4 -0
  19. package/_packed_docs/sdk/03-/346/240/270/345/277/203/346/246/202/345/277/265.md +1 -1
  20. package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +68 -61
  21. package/_packed_docs/sdk/05-E2EE/345/212/240/345/257/206/351/200/232/344/277/241.md +1 -1
  22. package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +279 -285
  23. package/_packed_docs/sdk/08-/346/234/200/344/275/263/345/256/236/350/267/265.md +3 -3
  24. package/_packed_docs/sdk/09-group-rpc-manual.md +271 -235
  25. package/_packed_docs/sdk/09-message-rpc-manual.md +47 -6
  26. package/_packed_docs/sdk/09-storage-rpc-manual.md +70 -68
  27. package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +63 -34
  28. package/_packed_docs/sdk/INDEX.md +105 -34
  29. package/_packed_docs/sdk/Ownership-SDK/345/256/236/347/216/260/346/214/207/345/215/227.md +169 -0
  30. package/_packed_docs/sdk/Ownership-SDK/347/274/226/346/216/222/346/214/207/345/215/227.md +349 -0
  31. package/_packed_docs/sdk/Ownership-SDK/347/274/226/346/216/222/346/214/207/345/215/2272.1.md +922 -0
  32. package/_packed_docs/sdk/Ownership-SDK/351/233/206/346/210/220/346/214/207/345/215/227.md +270 -0
  33. 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 +300 -0
  34. 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 +382 -0
  35. package/_packed_docs/sdk/Ownership2.1-TDD/345/210/206/351/230/266/346/256/265/345/256/236/346/226/275/350/256/241/345/210/222.md +708 -0
  36. package/_packed_docs/sdk/Ownership2.1/350/257/246/347/273/206/350/256/276/350/256/241.md +476 -0
  37. package/_packed_docs/sdk/Ownership/347/256/241/347/220/206/344/270/223/351/242/230.md +559 -0
  38. package/_packed_docs/sdk/ProtectedHeaders/347/224/250/346/263/225/344/270/223/351/242/230.md +299 -0
  39. package/_packed_docs/sdk//345/207/255/350/257/201/344/270/216/347/273/204/347/273/207/346/234/215/345/212/241SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +173 -0
  40. package/_packed_docs/sdk//345/207/255/350/257/201/344/270/216/347/273/204/347/273/207/346/234/215/345/212/241SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +211 -0
  41. package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273SDK/345/256/236/347/216/260/347/247/273/346/244/215/346/214/207/345/215/227.md +331 -0
  42. package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273SDK/351/233/206/346/210/220/346/214/207/345/215/227.md +207 -0
  43. package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273/344/273/243/347/240/201/345/256/241/346/237/245/346/212/245/345/221/212.md +252 -0
  44. package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273/345/256/236/346/226/275/350/256/241/345/210/222.md +156 -0
  45. package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273/346/226/271/346/241/210.md +273 -0
  46. package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273/350/257/246/347/273/206/350/256/276/350/256/241.md +211 -0
  47. package/dist/agent-md-schema.d.ts +17 -3
  48. package/dist/agent-md-schema.d.ts.map +1 -1
  49. package/dist/agent-md-schema.js +94 -26
  50. package/dist/agent-md-schema.js.map +1 -1
  51. package/dist/agent-md.d.ts +54 -3
  52. package/dist/agent-md.d.ts.map +1 -1
  53. package/dist/agent-md.js +711 -65
  54. package/dist/agent-md.js.map +1 -1
  55. package/dist/aid-store.d.ts +58 -46
  56. package/dist/aid-store.d.ts.map +1 -1
  57. package/dist/aid-store.js +254 -248
  58. package/dist/aid-store.js.map +1 -1
  59. package/dist/aid.d.ts +4 -1
  60. package/dist/aid.d.ts.map +1 -1
  61. package/dist/aid.js +27 -33
  62. package/dist/aid.js.map +1 -1
  63. package/dist/auth.d.ts +4 -1
  64. package/dist/auth.d.ts.map +1 -1
  65. package/dist/auth.js +426 -39
  66. package/dist/auth.js.map +1 -1
  67. package/dist/bundle.js +38179 -32054
  68. package/dist/cert-utils.d.ts +50 -2
  69. package/dist/cert-utils.d.ts.map +1 -1
  70. package/dist/cert-utils.js +105 -47
  71. package/dist/cert-utils.js.map +1 -1
  72. package/dist/client/delivery.d.ts +22 -1
  73. package/dist/client/delivery.d.ts.map +1 -1
  74. package/dist/client/delivery.js +389 -78
  75. package/dist/client/delivery.js.map +1 -1
  76. package/dist/client/rpc-pipeline.d.ts.map +1 -1
  77. package/dist/client/rpc-pipeline.js +103 -77
  78. package/dist/client/rpc-pipeline.js.map +1 -1
  79. package/dist/client/v2-e2ee.d.ts +1 -0
  80. package/dist/client/v2-e2ee.d.ts.map +1 -1
  81. package/dist/client/v2-e2ee.js +182 -89
  82. package/dist/client/v2-e2ee.js.map +1 -1
  83. package/dist/client.d.ts +100 -1
  84. package/dist/client.d.ts.map +1 -1
  85. package/dist/client.js +1220 -25
  86. package/dist/client.js.map +1 -1
  87. package/dist/crypto.d.ts.map +1 -1
  88. package/dist/crypto.js +8 -6
  89. package/dist/crypto.js.map +1 -1
  90. package/dist/errors.d.ts.map +1 -1
  91. package/dist/errors.js +3 -2
  92. package/dist/errors.js.map +1 -1
  93. package/dist/events.d.ts +9 -2
  94. package/dist/events.d.ts.map +1 -1
  95. package/dist/events.js +72 -13
  96. package/dist/events.js.map +1 -1
  97. package/dist/facades.d.ts +82 -2
  98. package/dist/facades.d.ts.map +1 -1
  99. package/dist/facades.js +100 -6
  100. package/dist/facades.js.map +1 -1
  101. package/dist/index.d.ts +7 -3
  102. package/dist/index.d.ts.map +1 -1
  103. package/dist/index.js +3 -2
  104. package/dist/index.js.map +1 -1
  105. package/dist/keystore/index.d.ts +17 -0
  106. package/dist/keystore/index.d.ts.map +1 -1
  107. package/dist/keystore/indexeddb-shared.d.ts +10 -1
  108. package/dist/keystore/indexeddb-shared.d.ts.map +1 -1
  109. package/dist/keystore/indexeddb-shared.js +111 -0
  110. package/dist/keystore/indexeddb-shared.js.map +1 -1
  111. package/dist/keystore/indexeddb-token-store.d.ts +7 -1
  112. package/dist/keystore/indexeddb-token-store.d.ts.map +1 -1
  113. package/dist/keystore/indexeddb-token-store.js +30 -1
  114. package/dist/keystore/indexeddb-token-store.js.map +1 -1
  115. package/dist/org-protocol.d.ts +98 -0
  116. package/dist/org-protocol.d.ts.map +1 -0
  117. package/dist/org-protocol.js +1802 -0
  118. package/dist/org-protocol.js.map +1 -0
  119. package/dist/ownership-proof.d.ts +93 -5
  120. package/dist/ownership-proof.d.ts.map +1 -1
  121. package/dist/ownership-proof.js +239 -53
  122. package/dist/ownership-proof.js.map +1 -1
  123. package/dist/ownership-v21.d.ts +194 -0
  124. package/dist/ownership-v21.d.ts.map +1 -0
  125. package/dist/ownership-v21.js +678 -0
  126. package/dist/ownership-v21.js.map +1 -0
  127. package/dist/ownership-workflow.d.ts +151 -0
  128. package/dist/ownership-workflow.d.ts.map +1 -0
  129. package/dist/ownership-workflow.js +1229 -0
  130. package/dist/ownership-workflow.js.map +1 -0
  131. package/dist/seq-tracker.d.ts +1 -0
  132. package/dist/seq-tracker.d.ts.map +1 -1
  133. package/dist/seq-tracker.js +10 -2
  134. package/dist/seq-tracker.js.map +1 -1
  135. package/dist/tools/cross-sdk-agent.d.ts +83 -0
  136. package/dist/tools/cross-sdk-agent.d.ts.map +1 -1
  137. package/dist/tools/cross-sdk-agent.js +415 -7
  138. package/dist/tools/cross-sdk-agent.js.map +1 -1
  139. package/dist/transport.d.ts +7 -0
  140. package/dist/transport.d.ts.map +1 -1
  141. package/dist/transport.js +90 -2
  142. package/dist/transport.js.map +1 -1
  143. package/dist/v2/e2ee/decrypt.d.ts +1 -0
  144. package/dist/v2/e2ee/decrypt.d.ts.map +1 -1
  145. package/dist/v2/e2ee/decrypt.js +9 -1
  146. package/dist/v2/e2ee/decrypt.js.map +1 -1
  147. package/dist/version.d.ts +1 -1
  148. package/dist/version.js +1 -1
  149. package/package.json +1 -1
package/dist/client.js CHANGED
@@ -9,6 +9,7 @@ import { EventDispatcher } from './events.js';
9
9
  import { normalizeGroupId } from './group-id.js';
10
10
  import { validateAIDFormat, validateGroupIDFormat } from './validators.js';
11
11
  import { GatewayDiscovery } from './discovery.js';
12
+ import { AIDStore } from './aid-store.js';
12
13
  import { RPCTransport } from './transport.js';
13
14
  import { AuthFlow } from './auth.js';
14
15
  import { SeqTracker } from './seq-tracker.js';
@@ -19,22 +20,28 @@ import { IdentityRuntimeManager } from './client/identity.js';
19
20
  import { LifecycleController } from './client/lifecycle.js';
20
21
  import { PeerDirectory } from './client/peers.js';
21
22
  import { RpcPipeline } from './client/rpc-pipeline.js';
22
- import { GroupFacade, MailFacade, MessageFacade, StreamFacade } from './facades.js';
23
+ import { CredentialFacade, GroupFacade, MailFacade, MessageFacade, OrgFacade, StreamFacade } from './facades.js';
23
24
  import { StorageVFS } from './storage/vfs.js';
24
25
  import { CollabClient } from './collab/client.js';
25
26
  import { V2E2EECoordinator } from './client/v2-e2ee.js';
26
27
  import { GroupStateCoordinator } from './client/group-state.js';
27
- import { CryptoProvider, base64ToUint8, pemToArrayBuffer, ecdsaVerifyDer, importCertPublicKeyEcdsa, } from './crypto.js';
28
+ import { CryptoProvider, base64ToUint8, pemToArrayBuffer, certificateSha256Fingerprint, ecdsaVerifyDer, importCertPublicKeyEcdsa, arrayBufferToPem, uint8ToBase64, } from './crypto.js';
28
29
  import { IndexedDBTokenStore } from './keystore/indexeddb-token-store.js';
29
30
  import { decryptMessage, } from './v2/e2ee/index.js';
30
31
  import { ecdsaVerifyRaw } from './v2/crypto/ecdsa.js';
32
+ import { decryptedOwnershipJsonText } from './v2/e2ee/decrypt.js';
31
33
  import { AUNLogger } from './logger.js';
32
- import { AgentMdManager, buildDefaultAgentMd } from './agent-md.js';
34
+ import { AgentMdManager, buildDefaultAgentMd, buildOwnershipAgentMd, buildOwnershipReleaseAgentMd } from './agent-md.js';
33
35
  import { GroupIndexMetaCache } from './group-index.js';
34
- import { certMatchesFingerprint, normalizeFingerprintHex, parseCertMetadata } from './cert-utils.js';
36
+ import { certMatchesFingerprint, normalizeFingerprintHex, parseAgentMdTailSignature, parseCertMetadata } from './cert-utils.js';
37
+ import { validateAgentMdDocument } from './agent-md-schema.js';
35
38
  import { AUNError, AuthError, ConnectionError, E2EEError, NotFoundError, PermissionError, StateError, TimeoutError, ValidationError, } from './errors.js';
36
39
  import { isJsonObject, ConnectionState, STATE_TO_PUBLIC, } from './types.js';
37
40
  import { AID } from './aid.js';
41
+ import { OwnershipFiles, OwnershipStorageAdapter, ownershipSha256 } from './ownership-v21.js';
42
+ import { canonicalJson, createCredential, createMemberCredential, signMembershipBinding, verifyServiceEvent } from './org-protocol.js';
43
+ import { OwnershipWorkflow, parseOwnershipRegistrationIntent } from './ownership-workflow.js';
44
+ import { buildOwnershipMessage, encryptIdentity, decryptIdentity, } from './ownership-proof.js';
38
45
  function getV2DeviceId(dev) {
39
46
  if (Object.prototype.hasOwnProperty.call(dev, 'device_id')) {
40
47
  return { present: true, value: String(dev.device_id ?? '').trim() };
@@ -52,9 +59,22 @@ function isAIDObject(value) {
52
59
  && typeof candidate.aunPath === 'string'
53
60
  && typeof candidate.isPrivateKeyValid === 'function');
54
61
  }
62
+ function ownershipRandomUUID() {
63
+ return globalThis.crypto?.randomUUID?.()
64
+ ?? 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (char) => {
65
+ const random = Math.floor(Math.random() * 16);
66
+ const value = char === 'x' ? random : (random & 0x3) | 0x8;
67
+ return value.toString(16);
68
+ });
69
+ }
55
70
  function bytesToHex(bytes) {
56
71
  return Array.from(bytes).map((b) => b.toString(16).padStart(2, '0')).join('');
57
72
  }
73
+ function randomBase64Url(byteLength) {
74
+ const bytes = new Uint8Array(byteLength);
75
+ globalThis.crypto.getRandomValues(bytes);
76
+ return uint8ToBase64(bytes).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '');
77
+ }
58
78
  function exactArrayBuffer(bytes) {
59
79
  return bytes.slice().buffer;
60
80
  }
@@ -240,6 +260,9 @@ function createAgentMdManagerForRuntime(opts) {
240
260
  logger: opts.logger(),
241
261
  ownerAidGetter: opts.ownerAid,
242
262
  currentAidGetter: opts.currentAid,
263
+ entityOwnerResolver: opts.entityOwnerResolver,
264
+ entityIdentityGetter: opts.entityIdentityGetter,
265
+ entitySignerResolver: opts.entitySignerResolver,
243
266
  gatewayResolver: async (aid) => {
244
267
  const gatewayUrl = await opts.gateway.resolve(aid);
245
268
  opts.gateway.set(gatewayUrl);
@@ -289,7 +312,46 @@ function createAgentMdManagerForRuntime(opts) {
289
312
  opts.identity.set(await auth.loadIdentityOrNone(target) ?? fallbackIdentity);
290
313
  return token;
291
314
  },
292
- peerResolver: async (aid, certFingerprint) => {
315
+ uploadAccessTokenResolver: async (_aid, gatewayUrl) => {
316
+ const target = String(opts.ownerAid() ?? '').trim();
317
+ let identity = opts.identity.get();
318
+ if (!identity || String(identity.aid ?? '') !== target)
319
+ throw new StateError('no local identity found, register or load an AID first');
320
+ const auth = opts.auth();
321
+ const cachedToken = String(identity.access_token ?? '');
322
+ const expiresAt = auth.getAccessTokenExpiry(identity);
323
+ if (cachedToken && (expiresAt === null || expiresAt > Date.now() / 1000 + 30))
324
+ return cachedToken;
325
+ if (identity.refresh_token) {
326
+ try {
327
+ const refreshed = await auth.refreshCachedTokens(gatewayUrl, identity);
328
+ const refreshedToken = String(refreshed.access_token ?? '');
329
+ const refreshedExpiry = auth.getAccessTokenExpiry(refreshed);
330
+ if (refreshedToken && (refreshedExpiry === null || refreshedExpiry > Date.now() / 1000 + 30)) {
331
+ opts.identity.set(refreshed);
332
+ return refreshedToken;
333
+ }
334
+ }
335
+ catch {
336
+ // refresh 失败时回退到完整 authenticate。
337
+ }
338
+ }
339
+ const result = await auth.authenticate(gatewayUrl, target);
340
+ const token = String(result.access_token ?? '');
341
+ if (!token)
342
+ throw new StateError('authenticate did not return access_token');
343
+ const fallbackIdentity = {
344
+ ...identity,
345
+ access_token: token,
346
+ refresh_token: String(result.refresh_token ?? identity.refresh_token ?? ''),
347
+ };
348
+ const fallbackExpiresAt = Number(result.expires_at ?? identity.expires_at ?? NaN);
349
+ if (Number.isFinite(fallbackExpiresAt))
350
+ fallbackIdentity.expires_at = fallbackExpiresAt;
351
+ opts.identity.set(await auth.loadIdentityOrNone(target) ?? fallbackIdentity);
352
+ return token;
353
+ },
354
+ peerResolver: async (aid, certFingerprint, signedAt) => {
293
355
  const target = String(aid ?? '').trim();
294
356
  const expectedFp = String(certFingerprint ?? '').trim().toLowerCase();
295
357
  const current = opts.currentAid();
@@ -303,7 +365,7 @@ function createAgentMdManagerForRuntime(opts) {
303
365
  }
304
366
  catch { /* best effort */ }
305
367
  }
306
- const certPem = String(await opts.fetchPeerCert(target, expectedFp || undefined) ?? '').trim();
368
+ const certPem = String(await opts.fetchPeerCert(target, expectedFp || undefined, signedAt) ?? '').trim();
307
369
  if (!certPem)
308
370
  throw new NotFoundError(`certificate not found for aid: ${target}`);
309
371
  return await AID.create({
@@ -520,14 +582,79 @@ function normalizeDeliveryModeConfig(raw, opts = {}) {
520
582
  *
521
583
  */
522
584
  export class AUNClient {
585
+ resolveCredentialService;
523
586
  /** SDK 配置模型 */
524
587
  configModel;
525
588
  /** 原始配置字典 */
526
589
  config;
527
590
  _aid = null;
591
+ _serviceEventIdentityVersion = 0;
528
592
  _identity = null;
529
593
  _state = 'idle';
530
594
  _currentAid = null;
595
+ _ownershipRecords = new Map();
596
+ _ownershipType = 'agent';
597
+ _ownershipRecordsLoad = null;
598
+ _ownershipWorkflow = null;
599
+ _ownershipRecoveryTimer = null;
600
+ _ownershipRecoveryGeneration = 0;
601
+ _ownershipReleaseTasks = new Map();
602
+ async _persistOwnershipRecords() {
603
+ const aid = String(this._aid ?? '').trim();
604
+ if (!aid)
605
+ return;
606
+ const store = this._tokenStore;
607
+ if (!store.getMetadata || !store.setMetadata)
608
+ return;
609
+ let records = {};
610
+ try {
611
+ const raw = await store.getMetadata.call(store, aid, 'ownership_records');
612
+ const parsed = raw ? JSON.parse(raw) : {};
613
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed))
614
+ records = parsed;
615
+ }
616
+ catch {
617
+ records = {};
618
+ }
619
+ for (const [key, record] of this._ownershipRecords) {
620
+ if (![record.subject_aid, record.owner_aid].some((value) => String(value ?? '').trim().toLowerCase() === aid.toLowerCase()))
621
+ continue;
622
+ records[key] = Object.fromEntries(Object.entries(record).filter(([name]) => !['subject_identity', 'escrow_password', 'escrow_download_url'].includes(name)));
623
+ }
624
+ await store.setMetadata.call(store, aid, 'ownership_records', JSON.stringify(records));
625
+ }
626
+ async _restoreOwnershipRecords() {
627
+ const aid = String(this._aid ?? '').trim();
628
+ if (!aid)
629
+ return;
630
+ const store = this._tokenStore;
631
+ if (!store.getMetadata)
632
+ return;
633
+ try {
634
+ const intentRaw = await store.getMetadata.call(store, aid, 'registration_intent');
635
+ if (this._aid !== aid)
636
+ return;
637
+ if (intentRaw)
638
+ this._ownershipType = String(JSON.parse(intentRaw).type || this._ownershipType);
639
+ const raw = await store.getMetadata.call(store, aid, 'ownership_records');
640
+ if (this._aid !== aid)
641
+ return;
642
+ const records = raw ? JSON.parse(raw) : {};
643
+ if (!records || typeof records !== 'object' || Array.isArray(records))
644
+ return;
645
+ for (const [key, value] of Object.entries(records)) {
646
+ if (!value || typeof value !== 'object' || Array.isArray(value))
647
+ continue;
648
+ const record = value;
649
+ this._ownershipRecords.set(key, record);
650
+ if (String(record.subject_aid ?? '').trim().toLowerCase() === aid.toLowerCase())
651
+ this._ownershipType = String(record.type ?? this._ownershipType);
652
+ }
653
+ }
654
+ catch {
655
+ return;
656
+ }
657
+ }
531
658
  _instanceProtectedHeaders = null;
532
659
  _gatewayUrl = null;
533
660
  _gatewayCandidates = [];
@@ -537,6 +664,7 @@ export class AUNClient {
537
664
  _connectDeliveryMode;
538
665
  _defaultConnectDeliveryMode;
539
666
  _closing = false;
667
+ _disconnecting = false;
540
668
  _sessionParams = null;
541
669
  _sessionOptions = { ...DEFAULT_SESSION_OPTIONS };
542
670
  _dispatcher;
@@ -560,6 +688,10 @@ export class AUNClient {
560
688
  _v2BootstrapCache = new Map();
561
689
  _v2SenderIKPending = new Map();
562
690
  _v2SenderIKFetching = new Set();
691
+ _readStateCache = new Map();
692
+ _readStateQueries = new Map();
693
+ _readStateIdentity = null;
694
+ _readStateGeneration = 0;
563
695
  static V2_BOOTSTRAP_TTL_MS = 60 * 60 * 1000;
564
696
  /** V2 state 签名验证缓存:cacheKey(hex) → expiry_unix_ms */
565
697
  _v2SigCache = new Map();
@@ -643,6 +775,8 @@ export class AUNClient {
643
775
  _mailFacade;
644
776
  _groupFacade;
645
777
  _streamFacade;
778
+ _credentialFacade;
779
+ _orgFacade;
646
780
  _groupInvitePending = new Map();
647
781
  _groupInviteStateAid = null;
648
782
  _groupInviteStateLoad = null;
@@ -675,7 +809,7 @@ export class AUNClient {
675
809
  this._logDiscovery = this._logger.for('aun_core.discovery');
676
810
  this._logEvents = this._logger.for('aun_core.events');
677
811
  this._clientLog.info(`AUNClient initialized: debug=${_debug} aunPath=${this.configModel.aunPath} aid=${initAid ?? '-'}`);
678
- this._dispatcher = new EventDispatcher();
812
+ this._dispatcher = new EventDispatcher(() => this._ensureReadStateIdentity());
679
813
  this._discovery = new GatewayDiscovery();
680
814
  this._tokenStore = new IndexedDBTokenStore();
681
815
  this._slotId = inputAid?.slotId || 'default';
@@ -691,6 +825,9 @@ export class AUNClient {
691
825
  verifySsl: this.configModel.verifySsl,
692
826
  });
693
827
  this._aid = initAid;
828
+ this._dispatcher.setEventIdentity(initAid ?? '');
829
+ if (initAid)
830
+ this._ownershipRecordsLoad = this._restoreOwnershipRecords();
694
831
  this._agentMdManager = createAgentMdManagerForRuntime({
695
832
  config: () => this.configModel,
696
833
  logger: () => this._logger.for('aun_core.agent_md'),
@@ -708,13 +845,19 @@ export class AUNClient {
708
845
  },
709
846
  auth: () => this._auth,
710
847
  tokenStore: () => this._tokenStore,
711
- fetchPeerCert: (target, certFingerprint) => this._fetchPeerCert(target, certFingerprint || undefined),
848
+ fetchPeerCert: (target, certFingerprint, signedAt) => this._fetchPeerCert(target, certFingerprint || undefined, undefined, signedAt, true),
849
+ entityOwnerResolver: (target, type) => this._resolveEntityAgentMdOwner(target, type),
850
+ entityIdentityGetter: (target, type) => this._freshEntityCertificate(target, type),
851
+ entitySignerResolver: (target, fingerprint) => this._freshEntitySigner(target, fingerprint),
712
852
  });
713
853
  this._transport = new RPCTransport({
714
854
  eventDispatcher: this._dispatcher,
715
855
  timeout: DEFAULT_SESSION_OPTIONS.timeouts.call,
716
856
  });
857
+ this._transport.setServiceEventVerifier((event, payload) => this._verifyIncomingServiceEvent(event, payload));
717
858
  this._transport.setProtocolDisconnectCallback((error, closeCode) => this._handleTransportDisconnect(error, closeCode));
859
+ this._transport._onForegroundDrained = () => this._delivery.onForegroundPullsDrained();
860
+ this._transport._onRpcResponse = () => this._delivery.requestBackgroundPullRecovery();
718
861
  this._transport.setProtocolMetaObserver((meta) => this._observeRpcMeta(meta).catch((exc) => {
719
862
  this._clientLog.debug(`rpc meta observer skipped: ${String(exc)}`);
720
863
  }));
@@ -736,6 +879,7 @@ export class AUNClient {
736
879
  cert: inputAid.certPem,
737
880
  };
738
881
  this._auth.setIdentity(this._identity);
882
+ this._ownershipRecordsLoad = this._restoreOwnershipRecords();
739
883
  this._state = 'standby';
740
884
  }
741
885
  }
@@ -756,6 +900,9 @@ export class AUNClient {
756
900
  this._dispatcher.subscribeProtocol('_raw.message.recalled', (data) => {
757
901
  this._safeAsync(this._onRawMessageRecalled(data));
758
902
  });
903
+ this._dispatcher.subscribeProtocol('_raw.message.read_changed', (data) => {
904
+ this._onRawReadChanged(data, 'p2p');
905
+ });
759
906
  // 群组消息推送:re-publish(V2 加密消息走 V2 push 路径)
760
907
  this._dispatcher.subscribeProtocol('_raw.group.message_created', (data) => {
761
908
  this._onRawGroupMessageCreated(data);
@@ -764,6 +911,9 @@ export class AUNClient {
764
911
  this._dispatcher.subscribeProtocol('_raw.group.message_recalled', (data) => {
765
912
  this._safeAsync(this._onRawGroupMessageRecalled(data));
766
913
  });
914
+ this._dispatcher.subscribeProtocol('_raw.group.read_changed', (data) => {
915
+ this._onRawReadChanged(data, 'group');
916
+ });
767
917
  // 群组变更事件:验签 + 透传 + gap 检测 + V2 state/SPK 维护
768
918
  this._dispatcher.subscribeProtocol('_raw.group.changed', (data) => {
769
919
  this._onRawGroupChanged(data);
@@ -894,6 +1044,18 @@ export class AUNClient {
894
1044
  this._mailFacade = new MailFacade(this);
895
1045
  return this._mailFacade;
896
1046
  }
1047
+ /** Credential RPC facade 入口 */
1048
+ get credential() {
1049
+ if (!this._credentialFacade)
1050
+ this._credentialFacade = new CredentialFacade(this);
1051
+ return this._credentialFacade;
1052
+ }
1053
+ /** Org RPC facade 入口 */
1054
+ get org() {
1055
+ if (!this._orgFacade)
1056
+ this._orgFacade = new OrgFacade(this);
1057
+ return this._orgFacade;
1058
+ }
897
1059
  getGroupIndexRemoteMeta(groupAid) {
898
1060
  return this._groupIndexMetaCache.remoteMeta(this._aid ?? '', groupAid);
899
1061
  }
@@ -978,6 +1140,501 @@ export class AUNClient {
978
1140
  get currentAid() {
979
1141
  return this._currentAid;
980
1142
  }
1143
+ _ownershipKey(subjectAid, ownerAid) {
1144
+ return `${String(subjectAid ?? '').trim().toLowerCase()}|${String(ownerAid ?? '').trim().toLowerCase()}`;
1145
+ }
1146
+ getType() { return String(this._ownershipType || 'agent').trim() || 'agent'; }
1147
+ get_type() { return this.getType(); }
1148
+ getOwner() {
1149
+ const aid = String(this._aid ?? '').trim().toLowerCase();
1150
+ let best = null;
1151
+ let bestActive = false;
1152
+ for (const record of this._ownershipRecords.values()) {
1153
+ if (String(record.subject_aid ?? '').trim().toLowerCase() === aid) {
1154
+ const owner = String(record.owner_aid ?? '').trim();
1155
+ if (!owner)
1156
+ continue;
1157
+ const active = String(record.status ?? '').trim().toLowerCase() === 'active';
1158
+ if (!best || active && !bestActive) {
1159
+ best = owner;
1160
+ bestActive = active;
1161
+ }
1162
+ }
1163
+ }
1164
+ return best;
1165
+ }
1166
+ get_owner() { return this.getOwner(); }
1167
+ getOwnershipStatus() {
1168
+ const aid = String(this._aid ?? '').trim().toLowerCase();
1169
+ let best = null;
1170
+ let bestActive = false;
1171
+ for (const record of this._ownershipRecords.values()) {
1172
+ const subject = String(record.subject_aid ?? '').trim().toLowerCase();
1173
+ const owner = String(record.owner_aid ?? '').trim().toLowerCase();
1174
+ if (aid !== subject && aid !== owner)
1175
+ continue;
1176
+ const active = String(record.status ?? '').trim().toLowerCase() === 'active';
1177
+ if (!best || active && !bestActive) {
1178
+ best = record;
1179
+ bestActive = active;
1180
+ }
1181
+ }
1182
+ if (best)
1183
+ return Object.fromEntries(Object.entries(best).filter(([key]) => key !== 'subject_identity' && key !== 'escrow_password' && key !== 'escrow_download_url'));
1184
+ return { subject_aid: this._aid ?? '', owner_aid: null, status: 'legacy_unbound', type: this.getType() };
1185
+ }
1186
+ get_ownership_status() { return this.getOwnershipStatus(); }
1187
+ _ownershipEscrowPath(subjectAid, proofId) {
1188
+ return `/.aun/ownership/${String(subjectAid ?? '').trim().toLowerCase()}/${String(proofId ?? '').trim()}.json`;
1189
+ }
1190
+ async _uploadOwnershipEscrow(subjectAid, ownerAid, proofId) {
1191
+ const current = this._currentAid;
1192
+ if (!current || !current.isPrivateKeyValid())
1193
+ throw new StateError('ownership escrow requires a loaded identity with a valid private key');
1194
+ const packageValue = { aid: current.aid, private_key_pem: current.privateKeyPem, public_key_der_b64: current.publicKey, curve: 'P-256', cert: current.certPem };
1195
+ const { envelope, password } = await encryptIdentity(packageValue);
1196
+ const path = this._ownershipEscrowPath(subjectAid, proofId);
1197
+ await this.storage.writeBytes(path, new TextEncoder().encode(JSON.stringify(envelope)), { overwrite: true, contentType: 'application/json' });
1198
+ await this.storage.setAcl(path, { granteeAid: ownerAid, perms: 'r' });
1199
+ const ticket = await this.storage.lowlevel.call('storage.create_download_ticket', {
1200
+ owner_aid: subjectAid, bucket: 'default', object_key: path.replace(/^\/+/, ''), viewer_aid: ownerAid,
1201
+ }, path);
1202
+ const downloadUrl = String(ticket.logical_url ?? ticket.download_url ?? ticket.url ?? '').trim();
1203
+ if (!downloadUrl)
1204
+ throw new ValidationError('ownership escrow download ticket did not return a URL');
1205
+ return { escrow_path: path, escrow_download_url: downloadUrl, escrow_password: password, escrow_sha256: envelope.sha256 };
1206
+ }
1207
+ async _downloadOwnershipEscrow(record) {
1208
+ const subject = String(record.subject_aid ?? '').trim().toLowerCase();
1209
+ const downloadUrl = String(record.escrow_download_url ?? '').trim();
1210
+ const password = String(record.escrow_password ?? '');
1211
+ if (!subject || !downloadUrl || !password)
1212
+ throw new ValidationError('ownership escrow metadata is incomplete');
1213
+ const currentAid = String(this._aid ?? '').trim().toLowerCase();
1214
+ const currentIssuer = currentAid.includes('.') ? currentAid.slice(currentAid.indexOf('.') + 1) : '';
1215
+ const subjectIssuer = subject.includes('.') ? subject.slice(subject.indexOf('.') + 1) : '';
1216
+ const sameIssuer = currentAid === subject || Boolean(currentIssuer && subjectIssuer && currentIssuer === subjectIssuer);
1217
+ const token = sameIssuer ? this.getAccessToken() : null;
1218
+ const raw = await this.storage.lowlevel.httpGet(downloadUrl, token ? { Authorization: 'Bearer ' + token } : undefined);
1219
+ const envelope = JSON.parse(new TextDecoder().decode(raw));
1220
+ const identity = await decryptIdentity(envelope, password);
1221
+ if (String(identity.aid ?? '').trim().toLowerCase() !== subject)
1222
+ throw new ValidationError('ownership escrow identity AID mismatch');
1223
+ const store = this._aidStore ?? new AIDStore({ aunPath: this.configModel.aunPath, encryptionSeed: this.configModel.seedPassword ?? '' });
1224
+ this._aidStore = store;
1225
+ const imported = await store.importGroupIdentity(subject, identity);
1226
+ if (!imported.ok)
1227
+ throw new StateError(imported.error?.message ?? 'ownership custody identity persistence failed');
1228
+ record.subject_identity = identity;
1229
+ record.escrow_sha256 = String(envelope.sha256 ?? '');
1230
+ return identity;
1231
+ }
1232
+ _ownershipV21() {
1233
+ if (!this._ownershipWorkflow) {
1234
+ const storage = new OwnershipStorageAdapter({ currentAid: () => this._aid ?? '', call: (method, params) => this.call(method, params), httpPut: (url, data, headers) => this.storage.lowlevel.httpPut(url, data, headers) });
1235
+ const files = new OwnershipFiles({
1236
+ currentAidType: () => this.getType(),
1237
+ currentAid: () => {
1238
+ if (!this._currentAid?.isPrivateKeyValid() || this._currentAid.aid !== this._aid)
1239
+ throw new StateError('ownership signing requires the current local identity', { stringCode: 'forbidden' });
1240
+ return this._currentAid;
1241
+ },
1242
+ readAgentMdFresh: (aid) => this._agentMdManager.readOwnershipFresh(aid),
1243
+ submitAgentMdRaw: (aid, raw, condition) => this._agentMdManager.submitOwnershipRaw(aid, raw, condition),
1244
+ readOwnerMd: (owner, sub) => storage.readOwnerMd(owner, sub),
1245
+ writeOwnerMd: (owner, sub, raw) => storage.writeOwnerMd(owner, sub, raw),
1246
+ deleteOwnerMd: (owner, sub) => storage.deleteOwnerMd(owner, sub),
1247
+ resolveCertificate: async (aid, fingerprint, keyPurpose) => this._auth.resolveOwnershipCertificate(await this._resolveOwnershipCertificateGateway(aid), aid, fingerprint, keyPurpose),
1248
+ });
1249
+ this._ownershipWorkflow = new OwnershipWorkflow({
1250
+ currentAidType: () => this.getType(),
1251
+ currentAid: () => this._aid ?? '', store: this._tokenStore, files,
1252
+ send: (to, payload, messageId) => this.call('message.send', { to, payload: payload, encrypt: true, message_id: messageId }),
1253
+ emit: (event, payload) => this._dispatcher.publish(event, payload),
1254
+ });
1255
+ }
1256
+ return this._ownershipWorkflow;
1257
+ }
1258
+ async bindOwneraid(ownerAid) { return await this._ownershipV21().bindOwneraid(ownerAid); }
1259
+ async bindSubaid(subAid) { return await this._ownershipV21().bindSubaid(subAid); }
1260
+ async releaseSubaid(subAid) { return await this._ownershipV21().releaseSubaid(subAid); }
1261
+ async transferSubaid(subAid, newOwnerAid) { return await this._ownershipV21().transferSubaid(subAid, newOwnerAid); }
1262
+ async checkOwnershipConsensus(subAid) { return await this._ownershipV21().checkOwnershipConsensus(subAid); }
1263
+ async _resumeOwnershipAfterConnect() {
1264
+ await this._checkIdentityAdmissionAfterConnect();
1265
+ await this._resumeRegistrationIntent();
1266
+ await this._ownershipV21().resume();
1267
+ }
1268
+ async _resumeRegistrationIntent(guard) {
1269
+ guard?.();
1270
+ if (!this._aid)
1271
+ return;
1272
+ const store = this._tokenStore;
1273
+ if (typeof store.getMetadata !== 'function' || typeof store.setMetadata !== 'function')
1274
+ throw new StateError('ownership registration storage unavailable', { stringCode: 'persistence_unavailable' });
1275
+ let raw;
1276
+ try {
1277
+ raw = await store.getMetadata(this._aid, 'registration_intent');
1278
+ }
1279
+ catch (error) {
1280
+ throw new StateError(`ownership registration read failed: ${String(error)}`, { stringCode: 'persistence_unavailable' });
1281
+ }
1282
+ if (!raw)
1283
+ return;
1284
+ const intent = parseOwnershipRegistrationIntent(raw, this._aid);
1285
+ if (intent.protocol === 'entity')
1286
+ return;
1287
+ if (!intent.pending)
1288
+ return;
1289
+ if (!intent.operation_id)
1290
+ throw new StateError('ownership registration intent is damaged', { stringCode: 'persistence_unavailable' });
1291
+ guard?.();
1292
+ await this._ownershipV21().startRegistrationIntent(intent.owner, intent.operation_id, guard);
1293
+ guard?.();
1294
+ try {
1295
+ await store.setMetadata(this._aid, 'registration_intent', JSON.stringify({ ...intent, pending: false }));
1296
+ }
1297
+ catch (error) {
1298
+ throw new StateError(`ownership registration save failed: ${String(error)}`, { stringCode: 'persistence_unavailable' });
1299
+ }
1300
+ }
1301
+ async _recoverOwnershipRekeyBeforeConnect() {
1302
+ await this._ownershipRecordsLoad;
1303
+ if (!['standby', 'authenticated', 'retry_backoff', 'connection_failed'].includes(this.state))
1304
+ return;
1305
+ for (const [key, record] of this._ownershipRecords) {
1306
+ if (record.subject_aid !== this._aid || !['pending_rekey', 'pending_agent_md', 'pending_upload'].includes(String(record.release_status ?? '')))
1307
+ continue;
1308
+ const store = this._aidStore ?? new AIDStore({ aunPath: this.configModel.aunPath, encryptionSeed: this.configModel.seedPassword ?? '', verifySsl: this.configModel.verifySsl });
1309
+ this._aidStore = store;
1310
+ let loaded = await store.load(String(record.subject_aid));
1311
+ if (!loaded.ok)
1312
+ throw new StateError(loaded.error.message);
1313
+ if (record.release_status === 'pending_rekey' && loaded.data.aid.certFingerprint === record.release_old_fingerprint) {
1314
+ const rekeyed = await store.rekey(loaded.data.aid.aid);
1315
+ if (!rekeyed.ok)
1316
+ throw new StateError(rekeyed.error.message);
1317
+ loaded = await store.load(String(record.subject_aid));
1318
+ if (!loaded.ok)
1319
+ throw new StateError(loaded.error.message);
1320
+ }
1321
+ if (record.release_status === 'pending_rekey')
1322
+ record.release_status = 'pending_agent_md';
1323
+ this._ownershipRecords.set(key, record);
1324
+ await this._persistOwnershipRecords();
1325
+ if (this._currentAid?.certFingerprint !== loaded.data.aid.certFingerprint) {
1326
+ this._state = 'no_identity';
1327
+ this.loadIdentity(loaded.data.aid);
1328
+ }
1329
+ }
1330
+ }
1331
+ async _sendOwnershipControl(params) {
1332
+ const payload = params.payload;
1333
+ const key = this._ownershipKey(String(payload.subject_aid), String(payload.owner_aid));
1334
+ const record = this._ownershipRecords.get(key);
1335
+ record.pending_message = params;
1336
+ if (payload.action === 'bind.response' || payload.action === 'bind.reject')
1337
+ delete record.ownership_request_pending;
1338
+ if (payload.action === 'bind.reject')
1339
+ record.status = 'rejected';
1340
+ await this._persistOwnershipRecords();
1341
+ const result = await this.call('message.send', params);
1342
+ delete record.pending_message;
1343
+ await this._persistOwnershipRecords();
1344
+ return result;
1345
+ }
1346
+ async _resumeOwnership() {
1347
+ await this._ownershipRecordsLoad;
1348
+ const current = String(this._aid ?? '').toLowerCase();
1349
+ for (const [key, record] of this._ownershipRecords) {
1350
+ const subject = String(record.subject_aid ?? ''), owner = String(record.owner_aid ?? '');
1351
+ try {
1352
+ if (record.pending_message)
1353
+ await this._sendOwnershipControl(record.pending_message);
1354
+ if (record.ownership_request_pending) {
1355
+ const role = current === subject ? 'owner' : 'subject';
1356
+ await this._dispatcher.publish('ownership.request', {
1357
+ request_id: record.request_id, proof_id: record.proof_id, subject_aid: subject, owner_aid: owner, subject_type: record.type,
1358
+ requester_aid: current === subject ? owner : subject, requester_role: role, statement: record[`${role}_statement`], signature: record[`${role}_signature`],
1359
+ });
1360
+ continue;
1361
+ }
1362
+ if (record.status !== 'active' || record.release_status)
1363
+ continue;
1364
+ if (subject === owner) {
1365
+ if (!record.agent_md_uploaded)
1366
+ await this._publishSubjectOwnershipAgentMd(record);
1367
+ }
1368
+ else if (current === subject && !record.agent_md_synced) {
1369
+ await this._publishSubjectOwnershipAgentMd(record);
1370
+ Object.assign(record, await this._uploadOwnershipEscrow(subject, owner, String(record.proof_id)));
1371
+ await this.call('message.send', { to: owner, encrypt: true, payload: buildOwnershipMessage('escrow.ready', {
1372
+ requestId: String(record.request_id), proofId: String(record.proof_id), subjectAid: subject, ownerAid: owner, subject_type: record.type,
1373
+ escrow_path: record.escrow_path, escrow_download_url: record.escrow_download_url, escrow_password: record.escrow_password, escrow_sha256: record.escrow_sha256,
1374
+ }) });
1375
+ }
1376
+ else if (current === owner && !record.agent_md_notified) {
1377
+ const store = this._aidStore ?? new AIDStore({ aunPath: this.configModel.aunPath, encryptionSeed: this.configModel.seedPassword ?? '' });
1378
+ this._aidStore = store;
1379
+ const loaded = await store.load(subject);
1380
+ const signature = record.subject_signature;
1381
+ if (!loaded.ok || !loaded.data.aid.isPrivateKeyValid() || loaded.data.aid.certFingerprint !== signature?.cert_fingerprint) {
1382
+ await this._sendOwnershipControl({ to: subject, encrypt: true, payload: buildOwnershipMessage('bind.response', {
1383
+ requestId: String(record.request_id), proofId: String(record.proof_id), subjectAid: subject, ownerAid: owner,
1384
+ subject_type: record.type, statement: record.owner_statement, signature: record.owner_signature,
1385
+ }) });
1386
+ continue;
1387
+ }
1388
+ await this._publishOwnershipAgentMd(record);
1389
+ await this.call('message.send', { to: subject, encrypt: true, payload: buildOwnershipMessage('agent_md.updated', {
1390
+ requestId: String(record.request_id), proofId: String(record.proof_id), subjectAid: subject, ownerAid: owner,
1391
+ }) });
1392
+ record.agent_md_notified = true;
1393
+ }
1394
+ delete record.last_error;
1395
+ this._ownershipRecords.set(key, record);
1396
+ await this._persistOwnershipRecords();
1397
+ }
1398
+ catch (error) {
1399
+ record.last_error = error instanceof Error ? error.message : String(error);
1400
+ this._ownershipRecords.set(key, record);
1401
+ await this._persistOwnershipRecords();
1402
+ }
1403
+ }
1404
+ }
1405
+ async _ownershipAgentMdContent(record, content = '') {
1406
+ const subject = String(record.subject_aid ?? '').trim().toLowerCase();
1407
+ const owner = String(record.owner_aid ?? '').trim().toLowerCase();
1408
+ const proof = { proof_id: String(record.proof_id ?? ''), subject_aid: subject, owner_aid: owner };
1409
+ if (record.subject_signature)
1410
+ proof.subject_signature = record.subject_signature;
1411
+ if (record.owner_signature && subject !== owner)
1412
+ proof.owner_signature = record.owner_signature;
1413
+ const base = content || await this._agentMdManager.readContent(subject) || buildDefaultAgentMd(subject, { type: String(record.type ?? 'agent') || 'agent' });
1414
+ return buildOwnershipAgentMd(base, owner, proof);
1415
+ }
1416
+ async _publishSubjectOwnershipAgentMd(record, uploadPartial = false) {
1417
+ const subject = String(record.subject_aid ?? '').trim().toLowerCase();
1418
+ if (!this._currentAid?.isPrivateKeyValid() || this._currentAid.aid.toLowerCase() !== subject)
1419
+ return;
1420
+ if (record.subject_proof_published && !uploadPartial)
1421
+ return;
1422
+ const content = await this._ownershipAgentMdContent(record);
1423
+ try {
1424
+ const pending = await this._agentMdManager.pendingUpload(subject);
1425
+ const proof = validateAgentMdDocument(content).frontmatter.ownership_proof;
1426
+ if (pending && JSON.stringify(validateAgentMdDocument(pending.content).frontmatter.ownership_proof) === JSON.stringify(proof))
1427
+ await this._agentMdManager.resumePendingUpload(subject, this._currentAid);
1428
+ else {
1429
+ await this._agentMdManager.writeContent(subject, content);
1430
+ await this._agentMdManager.upload(content);
1431
+ }
1432
+ record.agent_md_uploaded = true;
1433
+ if (record.subject_signature && (record.owner_signature || subject === record.owner_aid))
1434
+ record.subject_proof_published = true;
1435
+ await this._persistOwnershipRecords();
1436
+ }
1437
+ catch (error) {
1438
+ this._clientLog.debug('ownership partial agent.md upload deferred: ' + (error instanceof Error ? error.message : String(error)));
1439
+ }
1440
+ }
1441
+ async _publishOwnershipAgentMd(record) {
1442
+ if (record.agent_md_uploaded || !record.owner_signature || !record.subject_signature)
1443
+ return;
1444
+ if (!record.subject_identity) {
1445
+ const store = this._aidStore ?? new AIDStore({ aunPath: this.configModel.aunPath, encryptionSeed: this.configModel.seedPassword ?? '' });
1446
+ this._aidStore = store;
1447
+ const loaded = await store.load(String(record.subject_aid ?? ''));
1448
+ if (!loaded.ok || !loaded.data.aid.isPrivateKeyValid())
1449
+ throw new StateError('ownership subject signing identity is unavailable');
1450
+ if (!await this._agentMdManager.resumePendingUpload(String(record.subject_aid ?? ''), loaded.data.aid)) {
1451
+ const remote = await this._agentMdManager.download(String(record.subject_aid ?? ''));
1452
+ await this._agentMdManager.uploadAs(String(record.subject_aid ?? ''), await this._ownershipAgentMdContent(record, remote.content), loaded.data.aid);
1453
+ }
1454
+ record.agent_md_uploaded = true;
1455
+ return;
1456
+ }
1457
+ const identity = record.subject_identity;
1458
+ const subject = String(record.subject_aid ?? '').trim().toLowerCase();
1459
+ const signer = await AID.create({
1460
+ aid: String(identity.aid ?? subject).trim().toLowerCase(),
1461
+ aunPath: this.configModel.aunPath,
1462
+ certPem: String(identity.cert ?? '').trim(),
1463
+ privateKeyPem: String(identity.private_key_pem ?? ''),
1464
+ certValid: true,
1465
+ privateKeyValid: true,
1466
+ });
1467
+ if (!await this._agentMdManager.resumePendingUpload(subject, signer)) {
1468
+ const remote = await this._agentMdManager.download(subject);
1469
+ await this._agentMdManager.uploadAs(subject, await this._ownershipAgentMdContent(record, remote.content), signer);
1470
+ }
1471
+ record.agent_md_uploaded = true;
1472
+ }
1473
+ async releaseOwnership(_subjectAid) {
1474
+ throw new StateError('legacy ownership workflow is unsupported', { stringCode: 'unsupported_protocol' });
1475
+ }
1476
+ async release_ownership(subjectAid) { return await this.releaseOwnership(subjectAid); }
1477
+ _scheduleOwnershipRelease(key) {
1478
+ if (this._ownershipReleaseTasks.has(key))
1479
+ return;
1480
+ const task = this._runOwnershipRelease(key).finally(() => {
1481
+ if (this._ownershipReleaseTasks.get(key) === task)
1482
+ this._ownershipReleaseTasks.delete(key);
1483
+ });
1484
+ this._ownershipReleaseTasks.set(key, task);
1485
+ }
1486
+ async _runOwnershipRelease(key) {
1487
+ const record = this._ownershipRecords.get(key);
1488
+ if (!record || !['pending_rekey', 'pending_agent_md', 'pending_upload'].includes(String(record.release_status ?? '')))
1489
+ return;
1490
+ const active = !['no_identity', 'standby', 'closed'].includes(String(this._state).toLowerCase());
1491
+ const reconnect = active && record.release_status === 'pending_rekey' && this._currentAid?.certFingerprint === record.release_old_fingerprint;
1492
+ const options = { ...this._sessionOptions };
1493
+ try {
1494
+ if (reconnect)
1495
+ await this.disconnect();
1496
+ await this._resumeOwnershipRelease(key);
1497
+ }
1498
+ catch (error) {
1499
+ record.last_error = error instanceof Error ? error.message : String(error);
1500
+ this._ownershipRecords.set(key, record);
1501
+ await this._persistOwnershipRecords();
1502
+ this._clientLog.debug(`ownership release deferred: ${error instanceof Error ? error.message : String(error)}`);
1503
+ }
1504
+ finally {
1505
+ if (reconnect && this._state !== 'closed') {
1506
+ try {
1507
+ await this._lifecycle.connect(options);
1508
+ await this._resumeOwnershipRelease(key);
1509
+ }
1510
+ catch (error) {
1511
+ this._clientLog.debug(`ownership release reconnect deferred: ${error instanceof Error ? error.message : String(error)}`);
1512
+ }
1513
+ }
1514
+ }
1515
+ }
1516
+ _resumePendingOwnershipReleases() {
1517
+ for (const [key, record] of this._ownershipRecords) {
1518
+ if (['pending_rekey', 'pending_agent_md', 'pending_upload'].includes(String(record.release_status ?? '')))
1519
+ this._scheduleOwnershipRelease(key);
1520
+ }
1521
+ }
1522
+ async _resumeOwnershipRelease(key) {
1523
+ const record = this._ownershipRecords.get(key);
1524
+ if (!record || !['pending_rekey', 'pending_agent_md', 'pending_upload'].includes(String(record.release_status ?? '')))
1525
+ return;
1526
+ const subject = String(record.subject_aid ?? '').trim().toLowerCase();
1527
+ const owner = String(record.owner_aid ?? '').trim().toLowerCase();
1528
+ const release = record.ownership_release;
1529
+ const store = this._aidStore;
1530
+ if (!subject || !owner || !release || !store)
1531
+ return;
1532
+ const loaded = await store.load(subject);
1533
+ if (!loaded.ok || !loaded.data)
1534
+ throw new StateError(`ownership release could not load subject identity: ${subject}`);
1535
+ const oldFingerprint = String(record.release_old_fingerprint ?? '');
1536
+ if (oldFingerprint && loaded.data.aid.certFingerprint === oldFingerprint) {
1537
+ const result = await store.rekey(subject);
1538
+ if (!result.ok)
1539
+ throw new StateError(`ownership release rekey failed: ${result.error.message}`);
1540
+ const refreshed = await store.load(subject);
1541
+ if (!refreshed.ok || !refreshed.data)
1542
+ throw new StateError(`ownership release rekeyed identity could not be loaded: ${subject}`);
1543
+ record.release_status = 'pending_agent_md';
1544
+ this._ownershipRecords.set(key, record);
1545
+ await this._persistOwnershipRecords();
1546
+ this._state = 'no_identity';
1547
+ this.loadIdentity(refreshed.data.aid);
1548
+ return;
1549
+ }
1550
+ if (record.release_status === 'pending_rekey')
1551
+ record.release_status = 'pending_agent_md';
1552
+ this._ownershipRecords.set(key, record);
1553
+ await this._persistOwnershipRecords();
1554
+ if (this._currentAid?.certFingerprint !== loaded.data.aid.certFingerprint) {
1555
+ this._state = 'no_identity';
1556
+ this.loadIdentity(loaded.data.aid);
1557
+ }
1558
+ let payload = String(record.release_agent_md_payload ?? '');
1559
+ if (!payload) {
1560
+ const localContent = String(record.release_local_content ?? '');
1561
+ let remoteContent = '';
1562
+ try {
1563
+ const downloaded = await this._agentMdManager.download(subject);
1564
+ remoteContent = downloaded.content;
1565
+ const remoteFrontmatter = validateAgentMdDocument(remoteContent, { expectedAid: subject, validateOwnership: false }).frontmatter;
1566
+ const remoteStatus = String(downloaded.verification.ownership_status ?? '');
1567
+ const currentRelease = remoteFrontmatter.ownership_release;
1568
+ if (currentRelease && currentRelease.statement.proof_id === release.statement.proof_id && remoteStatus === 'released') {
1569
+ record.release_status = 'released';
1570
+ record.status = 'released';
1571
+ delete record.release_agent_md_payload;
1572
+ delete record.release_local_content;
1573
+ this._ownershipRecords.set(key, record);
1574
+ await this._persistOwnershipRecords();
1575
+ return;
1576
+ }
1577
+ const proof = remoteFrontmatter.ownership_proof;
1578
+ if (remoteFrontmatter.owner !== owner || String(proof?.proof_id ?? '') !== String(record.proof_id ?? '') || remoteStatus !== 'complete') {
1579
+ throw new StateError('remote agent.md does not contain the active ownership relation being released');
1580
+ }
1581
+ }
1582
+ catch (error) {
1583
+ if (!(error instanceof NotFoundError))
1584
+ throw error;
1585
+ }
1586
+ if (!localContent && !remoteContent)
1587
+ throw new StateError('ownership release has no local or remote agent.md to preserve');
1588
+ const localTimestamp = Number(parseAgentMdTailSignature(localContent).fields?.timestamp ?? 0) || 0;
1589
+ const remoteTimestamp = Number(parseAgentMdTailSignature(remoteContent).fields?.timestamp ?? 0) || 0;
1590
+ const base = localContent && localTimestamp > remoteTimestamp ? localContent : remoteContent || localContent;
1591
+ payload = buildOwnershipReleaseAgentMd(base, release);
1592
+ record.release_agent_md_payload = payload;
1593
+ record.release_status = 'pending_upload';
1594
+ this._ownershipRecords.set(key, record);
1595
+ await this._persistOwnershipRecords();
1596
+ await this._agentMdManager.writeContent(subject, payload);
1597
+ }
1598
+ const pending = await this._agentMdManager.pendingUpload(subject);
1599
+ const pendingRelease = pending ? validateAgentMdDocument(pending.content, { validateOwnership: false }).frontmatter.ownership_release : undefined;
1600
+ if (pendingRelease?.statement.proof_id === release.statement.proof_id)
1601
+ await this._agentMdManager.resumePendingUpload(subject);
1602
+ else
1603
+ await this._agentMdManager.upload(payload);
1604
+ record.release_status = 'released';
1605
+ record.status = 'released';
1606
+ delete record.release_agent_md_payload;
1607
+ delete record.release_local_content;
1608
+ this._ownershipRecords.set(key, record);
1609
+ await this._persistOwnershipRecords();
1610
+ }
1611
+ async bindAid(_subjectAid, _options = {}) {
1612
+ throw new StateError('legacy ownership workflow is unsupported', { stringCode: 'unsupported_protocol' });
1613
+ }
1614
+ async bind_aid(subjectAid, options = {}) { return await this.bindAid(subjectAid, options); }
1615
+ async bindOwner(_ownerAid, _options = {}) {
1616
+ throw new StateError('legacy ownership workflow is unsupported', { stringCode: 'unsupported_protocol' });
1617
+ }
1618
+ async bind_owner(ownerAid, options = {}) { return await this.bindOwner(ownerAid, options); }
1619
+ _ownershipPendingRequest(requestId) {
1620
+ const id = String(requestId ?? '').trim();
1621
+ for (const [key, record] of this._ownershipRecords) {
1622
+ if (record.ownership_request_pending === true && String(record.request_id ?? '').trim() === id)
1623
+ return [key, { ...record }];
1624
+ }
1625
+ throw new StateError('no pending ownership request found');
1626
+ }
1627
+ async acceptOwnershipRequest(operationId) {
1628
+ return await this._ownershipV21().acceptOwnershipRequest(operationId);
1629
+ }
1630
+ async rejectOwnershipRequest(operationId, reason) {
1631
+ return await this._ownershipV21().rejectOwnershipRequest(operationId, reason);
1632
+ }
1633
+ async _handleOwnershipMessage(message) {
1634
+ const envelope = message && typeof message === 'object' && !Array.isArray(message) ? message : null;
1635
+ const content = envelope && Object.hasOwn(envelope, 'payload') ? envelope.payload : message;
1636
+ return await this._ownershipV21().receive(String(envelope?.from_aid ?? envelope?.from ?? ''), decryptedOwnershipJsonText(content) ?? content);
1637
+ }
981
1638
  get hasIdentity() {
982
1639
  return this._currentAid !== null && this.state !== ConnectionState.CLOSED;
983
1640
  }
@@ -1119,13 +1776,19 @@ export class AUNClient {
1119
1776
  },
1120
1777
  auth: () => this._auth,
1121
1778
  tokenStore: () => this._tokenStore,
1122
- fetchPeerCert: (target, certFingerprint) => this._fetchPeerCert(target, certFingerprint || undefined),
1779
+ fetchPeerCert: (target, certFingerprint, signedAt) => this._fetchPeerCert(target, certFingerprint || undefined, undefined, signedAt, true),
1780
+ entityOwnerResolver: (target, type) => this._resolveEntityAgentMdOwner(target, type),
1781
+ entityIdentityGetter: (target, type) => this._freshEntityCertificate(target, type),
1782
+ entitySignerResolver: (target, fingerprint) => this._freshEntitySigner(target, fingerprint),
1123
1783
  });
1124
1784
  this._transport = new RPCTransport({
1125
1785
  eventDispatcher: this._dispatcher,
1126
1786
  timeout: DEFAULT_SESSION_OPTIONS.timeouts.call,
1127
1787
  });
1788
+ this._transport.setServiceEventVerifier((event, payload) => this._verifyIncomingServiceEvent(event, payload));
1128
1789
  this._transport.setProtocolDisconnectCallback((error, closeCode) => this._handleTransportDisconnect(error, closeCode));
1790
+ this._transport._onForegroundDrained = () => this._delivery.onForegroundPullsDrained();
1791
+ this._transport._onRpcResponse = () => this._delivery.requestBackgroundPullRecovery();
1129
1792
  this._transport.setProtocolMetaObserver((meta) => this._observeRpcMeta(meta).catch((exc) => {
1130
1793
  this._clientLog.debug(`rpc meta observer skipped: ${String(exc)}`);
1131
1794
  }));
@@ -1140,7 +1803,17 @@ export class AUNClient {
1140
1803
  }
1141
1804
  }
1142
1805
  loadIdentity(aid) {
1806
+ const previousAid = this._aid;
1143
1807
  this._identityRuntime.loadIdentity(aid);
1808
+ this._dispatcher.setEventIdentity(this._aid ?? '');
1809
+ if (previousAid !== this._aid)
1810
+ this._serviceEventIdentityVersion++;
1811
+ this._ensureReadStateIdentity();
1812
+ if (previousAid !== this._aid) {
1813
+ this._ownershipRecords.clear();
1814
+ this._ownershipType = 'agent';
1815
+ }
1816
+ this._ownershipRecordsLoad = this._restoreOwnershipRecords();
1144
1817
  }
1145
1818
  setProtectedHeaders(headers) {
1146
1819
  if (!headers) {
@@ -1187,7 +1860,7 @@ export class AUNClient {
1187
1860
  }
1188
1861
  /** 连接到 Gateway;身份来自构造函数或 loadIdentity(aid),认证由 SDK 内部自动完成。 */
1189
1862
  async connect(opts) {
1190
- return this._lifecycle.connect(opts);
1863
+ await this._lifecycle.connect(opts);
1191
1864
  }
1192
1865
  /** 断开连接但保留本地状态,可再次 connect */
1193
1866
  async disconnect() {
@@ -1205,8 +1878,13 @@ export class AUNClient {
1205
1878
  * 自动解密 message.pull/group.pull、Group E2EE 生命周期编排。
1206
1879
  */
1207
1880
  async call(method, params) {
1881
+ const readIdentity = ['message.read', 'message.read_state', 'group.read', 'group.read_state'].includes(method)
1882
+ ? this._ensureReadStateIdentity() : null;
1208
1883
  try {
1209
- return await this._rpcPipeline.call(method, params);
1884
+ const result = await this._rpcPipeline.call(method, params);
1885
+ if (readIdentity !== null && readIdentity === this._ensureReadStateIdentity())
1886
+ this._observeReadStateResult(method, result, params ?? {});
1887
+ return result;
1210
1888
  }
1211
1889
  catch (err) {
1212
1890
  await this._maybeHandleHalfOpenRpcFailure(err);
@@ -1247,6 +1925,8 @@ export class AUNClient {
1247
1925
  const store = this._aidStore;
1248
1926
  if (!groupAid || !store)
1249
1927
  return false;
1928
+ if (String((await (store._agentMdManager ?? this._agentMdManager).loadRecord(groupAid, true))?.local_draft_content ?? '').trim())
1929
+ return false;
1250
1930
  const loaded = await store.load(groupAid);
1251
1931
  if (!loaded.ok || !loaded.data?.aid?.isPrivateKeyValid())
1252
1932
  return false;
@@ -1262,18 +1942,39 @@ export class AUNClient {
1262
1942
  const target = message.slice(agentPrefix.length).trim();
1263
1943
  if (!target || target.toLowerCase() !== String(this._aid ?? '').trim().toLowerCase())
1264
1944
  return false;
1265
- await this._agentMdManager.upload(await this._agentMdManager.readContent(target) ?? buildDefaultAgentMd(target));
1945
+ if (String((await this._agentMdManager.loadRecord(target, true))?.local_draft_content ?? '').trim())
1946
+ return false;
1947
+ const content = await this._agentMdManager.readContent(target) ?? buildDefaultAgentMd(target);
1948
+ if (!this._ordinaryAdmissionAgentMd(content, target))
1949
+ return false;
1950
+ await this._agentMdManager.upload(content);
1266
1951
  return true;
1267
1952
  }
1953
+ _ordinaryAdmissionAgentMd(content, aid) {
1954
+ const meta = validateAgentMdDocument(content, { expectedAid: aid, validateOwnership: false, checkTimestamp: false }).frontmatter;
1955
+ return !['owner', 'ownership', 'ownership_proof', 'ownership_release'].some((field) => Object.hasOwn(meta, field));
1956
+ }
1268
1957
  async _checkIdentityAdmissionAfterConnect() {
1269
1958
  const aid = String(this._aid ?? this._currentAid?.aid ?? '').trim();
1270
1959
  if (!aid)
1271
1960
  return;
1272
1961
  try {
1273
- const checked = await this._agentMdManager.check(aid);
1274
- if (!checked.remote_found) {
1275
- const content = await this._agentMdManager.readContent(aid) ?? buildDefaultAgentMd(aid);
1276
- await this._agentMdManager.upload(content);
1962
+ const draft = String((await this._agentMdManager.loadRecord(aid, true))?.local_draft_content ?? '').trim();
1963
+ const pending = draft ? null : await this._agentMdManager.pendingUpload(aid);
1964
+ if (!draft && pending) {
1965
+ if (this._ordinaryAdmissionAgentMd(pending.content, aid))
1966
+ await this._agentMdManager.resumePendingUpload(aid);
1967
+ }
1968
+ else if (!draft) {
1969
+ const checked = await this._agentMdManager.check(aid, 0);
1970
+ if (!checked.remote_found) {
1971
+ const content = await this._agentMdManager.readContent(aid) ?? buildDefaultAgentMd(aid);
1972
+ if (this._ordinaryAdmissionAgentMd(content, aid))
1973
+ await this._agentMdManager.upload(content);
1974
+ }
1975
+ else if (checked.in_sync === false) {
1976
+ await this._agentMdManager.download(aid);
1977
+ }
1277
1978
  }
1278
1979
  }
1279
1980
  catch (exc) {
@@ -1282,6 +1983,7 @@ export class AUNClient {
1282
1983
  const store = this._aidStore;
1283
1984
  if (!store)
1284
1985
  return;
1986
+ await this._resumeOrgIdentityAfterConnect(store);
1285
1987
  let listed;
1286
1988
  try {
1287
1989
  listed = await this.call('group.list_my', {});
@@ -1317,6 +2019,10 @@ export class AUNClient {
1317
2019
  await this.bindGroupAid({ group_id: groupId, group_aid: groupAid }, { aidStore: store });
1318
2020
  return;
1319
2021
  }
2022
+ if (String((await (store._agentMdManager ?? this._agentMdManager).loadRecord(groupAid, true))?.local_draft_content ?? '').trim())
2023
+ return;
2024
+ if (await this._resumeGroupAgentMdUpload(store, groupAid))
2025
+ return;
1320
2026
  const checked = await this._agentMdManager.check(groupAid);
1321
2027
  if (!checked.remote_found) {
1322
2028
  await this._uploadGroupAgentMd(store, groupAid, {}, {}, aid);
@@ -1347,6 +2053,208 @@ export class AUNClient {
1347
2053
  this._groupIdentityOperations.delete(key);
1348
2054
  }
1349
2055
  }
2056
+ async createOrg(params = {}, options = {}) {
2057
+ const store = options.aidStore;
2058
+ const owner = this._currentAid;
2059
+ const orgAid = String(params.org_id ?? '').trim();
2060
+ if (!store || !owner?.isPrivateKeyValid() || !orgAid)
2061
+ throw new ValidationError('createOrg requires aidStore, org_id and current owner identity');
2062
+ this._aidStore = store;
2063
+ let identity = await store.load(orgAid);
2064
+ if (!identity.ok) {
2065
+ const registered = await store.register(orgAid, { type: 'org', owner: owner.aid });
2066
+ if (!registered.ok)
2067
+ throw new StateError(registered.error.message, { stringCode: 'unavailable', retryable: true });
2068
+ identity = await store.load(orgAid);
2069
+ }
2070
+ if (!identity.ok)
2071
+ throw new StateError(identity.error.message, { stringCode: 'unavailable', retryable: true });
2072
+ const target = options.serviceAid ?? `org.${orgAid.slice(orgAid.indexOf('.') + 1)}`;
2073
+ const tokens = store._tokenStore;
2074
+ const inputHash = await ownershipSha256(canonicalJson(params));
2075
+ const raw = await tokens.getMetadata(orgAid, 'org_identity_creation');
2076
+ let checkpoint = raw ? JSON.parse(raw) : undefined;
2077
+ if (checkpoint && (checkpoint.owner_aid !== owner.aid || checkpoint.input_hash !== inputHash))
2078
+ throw new StateError('pending Org creation context changed', { stringCode: 'conflict', retryable: false });
2079
+ if (!checkpoint) {
2080
+ const prepared = await this.org.prepareRequest('org.lifecycle.create', { ...params, owner_aid: owner.aid }, target, owner, { orgSigner: identity.data.aid });
2081
+ checkpoint = { owner_aid: owner.aid, input_hash: inputHash, source_params: structuredClone(params), prepared, phase: 'create' };
2082
+ await tokens.setMetadata(orgAid, 'org_identity_creation', JSON.stringify(checkpoint));
2083
+ }
2084
+ if (checkpoint.phase === 'create') {
2085
+ const result = await this.call('org.lifecycle.create', checkpoint.prepared);
2086
+ checkpoint = { ...checkpoint, result, phase: 'upload' };
2087
+ await tokens.setMetadata(orgAid, 'org_identity_creation', JSON.stringify(checkpoint));
2088
+ }
2089
+ await this._uploadOrgAgentMd(store, orgAid, checkpoint.source_params);
2090
+ await tokens.setMetadata(orgAid, 'org_identity_creation', '');
2091
+ return checkpoint.result;
2092
+ }
2093
+ async _resumeOrgIdentityAfterConnect(store) {
2094
+ const keyStore = store._keystore;
2095
+ const tokens = store._tokenStore;
2096
+ if (typeof keyStore?.listIdentities !== 'function' || typeof tokens?.getMetadata !== 'function')
2097
+ return;
2098
+ for (const aid of await keyStore.listIdentities()) {
2099
+ try {
2100
+ const creationRaw = await tokens.getMetadata(aid, 'org_identity_creation');
2101
+ const transferRaw = await tokens.getMetadata(aid, 'org_identity_transfer');
2102
+ const creation = creationRaw ? JSON.parse(creationRaw) : undefined;
2103
+ const transfer = transferRaw ? JSON.parse(transferRaw) : undefined;
2104
+ if (creation?.owner_aid === this._aid) {
2105
+ await this.createOrg(creation.source_params, { aidStore: store });
2106
+ continue;
2107
+ }
2108
+ if (transfer?.owner_aid === this._aid) {
2109
+ await this.completeOrgTransfer(transfer.source_params, { aidStore: store });
2110
+ continue;
2111
+ }
2112
+ if (String((await (store._agentMdManager ?? this._agentMdManager).loadRecord(aid, true))?.local_draft_content ?? '').trim())
2113
+ continue;
2114
+ const pending = await store._agentMdManager.pendingUpload(aid);
2115
+ if (!pending)
2116
+ continue;
2117
+ const fields = validateAgentMdDocument(pending.content, { expectedAid: aid }).frontmatter;
2118
+ if (fields.type === 'org' && fields.owner === this._aid)
2119
+ await this._resumeGroupAgentMdUpload(store, aid);
2120
+ }
2121
+ catch (error) {
2122
+ this._clientLog.warn(`post-connect Org identity recovery failed: aid=${aid} err=${String(error)}`);
2123
+ }
2124
+ }
2125
+ }
2126
+ async completeOrgTransfer(params = {}, options = {}) {
2127
+ const store = options.aidStore;
2128
+ const owner = this._currentAid;
2129
+ const orgAid = String(params.org_id ?? '').trim();
2130
+ const transferId = String(params.transfer_id ?? '').trim();
2131
+ if (!store || !owner?.isPrivateKeyValid() || !orgAid || !transferId)
2132
+ throw new ValidationError('completeOrgTransfer requires aidStore, org_id, transfer_id and current accepting owner');
2133
+ for (const field of ['previous_owner', 'new_owner']) {
2134
+ const material = params[field];
2135
+ if (!isJsonObject(material) || !isJsonObject(material.membership_binding) || !isJsonObject(material.terms) || field === 'previous_owner' && !isJsonObject(material.credential_template))
2136
+ throw new StateError('Org transfer requires both replacement membership bindings, terms and previous-owner credential', { stringCode: 'invalid', retryable: false });
2137
+ }
2138
+ this._aidStore = store;
2139
+ const localStore = store._keystore;
2140
+ const tokenStore = store._tokenStore;
2141
+ const inputHash = await ownershipSha256(canonicalJson(params));
2142
+ const raw = await tokenStore.getMetadata(orgAid, 'org_identity_transfer');
2143
+ let pending = raw ? JSON.parse(raw) : undefined;
2144
+ if (pending && (pending.transfer_id !== transferId || pending.owner_aid !== owner.aid || pending.input_hash !== inputHash))
2145
+ throw new StateError('pending Org transfer context changed', { stringCode: 'conflict', retryable: false });
2146
+ const pendingKey = `org-transfer:${orgAid}:${transferId}`;
2147
+ let keyPair = await localStore.loadPendingGroupBind(pendingKey);
2148
+ if (!keyPair) {
2149
+ if (pending)
2150
+ throw new StateError('Org transfer private key checkpoint is unavailable', { stringCode: 'persistence_unavailable', retryable: false });
2151
+ keyPair = await new CryptoProvider().generateIdentity();
2152
+ await localStore.savePendingGroupBind(pendingKey, keyPair);
2153
+ }
2154
+ const target = options.serviceAid ?? `org.${orgAid.slice(orgAid.indexOf('.') + 1)}`;
2155
+ const publicKey = arrayBufferToPem(base64ToUint8(keyPair.public_key_der_b64).buffer, 'PUBLIC KEY');
2156
+ if (pending && pending.public_key !== publicKey)
2157
+ throw new StateError('pending Org transfer key changed', { stringCode: 'conflict', retryable: false });
2158
+ if (!pending) {
2159
+ const rekey = { ...params, stage: 'rekey', public_key: publicKey, curve: 'P-256' };
2160
+ delete rekey.new_owner;
2161
+ delete rekey.request_binding;
2162
+ delete rekey.client_signature;
2163
+ pending = { transfer_id: transferId, owner_aid: owner.aid, public_key: publicKey, input_hash: inputHash, phase: 'rekey', source_params: structuredClone(params), rekey_params: await this.org.prepareRequest('org.lifecycle.transferAccept', rekey, target, owner) };
2164
+ await tokenStore.setMetadata(orgAid, 'org_identity_transfer', JSON.stringify(pending));
2165
+ }
2166
+ if (pending.phase === 'rekey') {
2167
+ const result = await this.call('org.lifecycle.transferAccept', pending.rekey_params);
2168
+ pending = { ...pending, phase: 'finalize', rekey_result: result };
2169
+ await tokenStore.setMetadata(orgAid, 'org_identity_transfer', JSON.stringify(pending));
2170
+ }
2171
+ const rekeyResult = pending.rekey_result;
2172
+ const certPem = String(rekeyResult?.cert_pem ?? rekeyResult?.cert ?? '').trim();
2173
+ if (!certPem)
2174
+ throw new StateError('Org rekey response has no certificate', { stringCode: 'unavailable', retryable: true });
2175
+ const imported = await store.importGroupIdentity(orgAid, { ...keyPair, cert_pem: certPem });
2176
+ if (!imported.ok)
2177
+ throw new StateError(imported.error.message, { stringCode: 'invalid', retryable: false });
2178
+ const loaded = await store.load(orgAid);
2179
+ if (!loaded.ok)
2180
+ throw new StateError(loaded.error.message, { stringCode: 'unavailable', retryable: true });
2181
+ const orgIdentity = loaded.data.aid;
2182
+ if (!pending.finalize_params) {
2183
+ const final = { ...pending.source_params, stage: 'finalize', public_key: publicKey, curve: 'P-256', request_id: crypto.randomUUID().replace(/-/g, ''), nonce: randomBase64Url(32) };
2184
+ delete final.request_binding;
2185
+ delete final.client_signature;
2186
+ for (const field of ['previous_owner', 'new_owner']) {
2187
+ const material = final[field];
2188
+ let binding = material.membership_binding;
2189
+ if (!binding.member_signature && binding.member_aid === owner.aid)
2190
+ binding = await signMembershipBinding(binding, owner, 'member');
2191
+ binding = await signMembershipBinding(binding, orgIdentity, 'org');
2192
+ const oldCredential = isJsonObject(material.credential_template) ? { ...material.credential_template } : undefined;
2193
+ if (oldCredential) {
2194
+ delete oldCredential.signature;
2195
+ oldCredential.key_version = orgIdentity.certFingerprint;
2196
+ }
2197
+ const credential = oldCredential ? await createCredential(oldCredential, orgIdentity) : await createMemberCredential(binding, orgIdentity, { key_version: orgIdentity.certFingerprint });
2198
+ final[field] = { ...material, membership_binding: binding, credential_template: credential };
2199
+ delete final[field].registration_authorization;
2200
+ }
2201
+ pending = { ...pending, finalize_params: await this.org.prepareRequest('org.lifecycle.transferAccept', final, target, owner, { orgSigner: orgIdentity }) };
2202
+ await tokenStore.setMetadata(orgAid, 'org_identity_transfer', JSON.stringify(pending));
2203
+ }
2204
+ const result = await this.call('org.lifecycle.transferAccept', pending.finalize_params);
2205
+ await this._uploadOrgAgentMd(store, orgAid, pending.source_params);
2206
+ await tokenStore.setMetadata(orgAid, 'org_identity_transfer', '');
2207
+ await localStore.clearPendingGroupBind(pendingKey);
2208
+ return result;
2209
+ }
2210
+ async dissolveOrg(params = {}, options = {}) {
2211
+ const store = options.aidStore ?? this._aidStore;
2212
+ if (store)
2213
+ this._aidStore = store;
2214
+ const orgAid = String(params.org_id ?? '').trim();
2215
+ const owner = this._currentAid;
2216
+ const target = options.serviceAid ?? `org.${orgAid.slice(orgAid.indexOf('.') + 1)}`;
2217
+ const payload = owner ? await this.org.prepareRequest('org.lifecycle.dissolve', params, target, owner) : params;
2218
+ const result = await this.call('org.lifecycle.dissolve', payload);
2219
+ const organization = isJsonObject(result?.organization) ? result.organization : {};
2220
+ const status = String(organization.status ?? result?.status ?? '');
2221
+ if (store && orgAid && status === 'dissolved') {
2222
+ const tokenStore = store._tokenStore;
2223
+ const keyStore = store._keystore;
2224
+ const certPem = await keyStore?.loadCert?.(orgAid);
2225
+ const transferRaw = await tokenStore?.getMetadata?.(orgAid, 'org_identity_transfer');
2226
+ const transfer = transferRaw ? JSON.parse(transferRaw) : undefined;
2227
+ if (transfer?.transfer_id && typeof keyStore?.clearPendingGroupBind === 'function') {
2228
+ await keyStore.clearPendingGroupBind(`org-transfer:${orgAid}:${String(transfer.transfer_id)}`);
2229
+ }
2230
+ if (certPem)
2231
+ await tokenStore?.setMetadata?.(orgAid, 'invalidated_entity_certificate', await certificateSha256Fingerprint(certPem));
2232
+ await tokenStore?.setMetadata?.(orgAid, 'org_identity_creation', '');
2233
+ await tokenStore?.setMetadata?.(orgAid, 'org_identity_transfer', '');
2234
+ await tokenStore?.saveInstanceState?.(orgAid, store.deviceId, store.slotId, {});
2235
+ await store._agentMdManager?.saveRecord?.(orgAid, {
2236
+ pending_operation: '', pending_content: '', pending_content_etag: '', pending_error: '',
2237
+ pending_condition: {}, pending_relation: {}, pending_authorization: '',
2238
+ });
2239
+ }
2240
+ return result;
2241
+ }
2242
+ async _uploadOrgAgentMd(store, orgAid, params) {
2243
+ const owner = this._currentAid;
2244
+ if (!owner)
2245
+ throw new StateError('Org agent.md requires current owner', { stringCode: 'forbidden', retryable: false });
2246
+ if (await this._resumeGroupAgentMdUpload(store, orgAid))
2247
+ return;
2248
+ const loaded = await store.load(orgAid);
2249
+ if (!loaded.ok)
2250
+ throw new StateError(loaded.error.message, { stringCode: 'unavailable', retryable: true });
2251
+ const content = String(params.org_agent_md ?? buildDefaultAgentMd(orgAid, { type: 'org', owner: owner.aid, entityCertFingerprint: loaded.data.aid.certFingerprint, name: String(params.name ?? orgAid) }));
2252
+ const uploaded = await store.uploadAgentMd(orgAid, content, { uploaderAid: owner.aid, entityOwnerResolver: (aid, type) => this._resolveEntityAgentMdOwner(aid, type), entityIdentityGetter: (aid) => this._freshEntityCertificate(aid, 'org'), entitySignerResolver: (aid, fingerprint) => this._freshEntitySigner(aid, fingerprint) });
2253
+ if (!uploaded.ok) {
2254
+ const code = ['invalid', 'unavailable', 'forbidden', 'conflict', 'persistence_unavailable'].includes(uploaded.error.code) ? uploaded.error.code : uploaded.error.code === 'NETWORK_ERROR' ? 'unavailable' : 'invalid';
2255
+ throw new StateError(uploaded.error.message, { stringCode: code, retryable: code === 'unavailable' });
2256
+ }
2257
+ }
1350
2258
  async createGroup(params = {}, options = {}) {
1351
2259
  if (!isJsonObject(params)) {
1352
2260
  throw new ValidationError('createGroup params must be an object');
@@ -1505,7 +2413,7 @@ export class AUNClient {
1505
2413
  }
1506
2414
  return { ...(isJsonObject(result) ? result : {}), invites, invite_errors: inviteErrors };
1507
2415
  }
1508
- _groupAgentMdContent(groupAid, params = {}, group = {}) {
2416
+ async _groupAgentMdContent(groupAid, params = {}, group = {}) {
1509
2417
  const explicit = params.group_agent_md ?? params.groupAgentMd ?? params.content;
1510
2418
  if (explicit !== undefined && explicit !== null)
1511
2419
  return String(explicit);
@@ -1524,27 +2432,78 @@ export class AUNClient {
1524
2432
  .split(/\s+/).filter(Boolean).join(' ');
1525
2433
  const description = Array.from(normalizedDescription).slice(0, 100).join('');
1526
2434
  const avatar = Array.from(value('avatar')).slice(0, 256).join('');
2435
+ const loaded = await this._aidStore?.load(groupAid);
2436
+ if (!loaded?.ok)
2437
+ throw new StateError('entity local certificate is unavailable', { stringCode: 'unavailable', retryable: true });
1527
2438
  return [
1528
2439
  '---\n',
1529
2440
  `aid: ${JSON.stringify(groupAid)}\n`,
1530
2441
  `name: ${JSON.stringify(name)}\n`,
1531
2442
  'type: "group"\n',
1532
2443
  'version: "1.0.0"\n',
2444
+ `owner: ${JSON.stringify(String(group.owner_aid ?? group.owner ?? this._aid ?? ''))}\n`,
2445
+ `entity_cert_fingerprint: ${JSON.stringify(loaded.data.aid.certFingerprint)}\n`,
1533
2446
  `description: ${JSON.stringify(description)}\n`,
1534
2447
  ...(avatar ? [`avatar: ${JSON.stringify(avatar)}\n`] : []),
1535
2448
  '---\n\n',
1536
2449
  `# ${name}\n`,
1537
2450
  ].join('');
1538
2451
  }
2452
+ async _resumeGroupAgentMdUpload(store, groupAid) {
2453
+ const identityStore = store;
2454
+ const pendingManager = identityStore._agentMdManager ?? this._agentMdManager;
2455
+ const pending = await pendingManager.pendingUpload(groupAid);
2456
+ if (!pending)
2457
+ return false;
2458
+ const fields = validateAgentMdDocument(pending.content, { expectedAid: groupAid, validateOwnership: false }).frontmatter;
2459
+ if (['ownership', 'ownership_proof', 'ownership_release'].some((field) => Object.hasOwn(fields, field))) {
2460
+ throw new StateError('ownership pending agent.md requires its persisted 2.1 task', { stringCode: 'unsupported_protocol' });
2461
+ }
2462
+ const identity = this._currentAid;
2463
+ if (!identity || fields.owner !== identity.aid)
2464
+ throw new StateError('group agent.md recovery requires its original current owner', { stringCode: 'forbidden' });
2465
+ const manager = new AgentMdManager({
2466
+ aunPath: store.aunPath,
2467
+ tokenStore: identityStore._tokenStore,
2468
+ uploadViaGatewayHost: true,
2469
+ ownerAidGetter: () => identity.aid,
2470
+ currentAidGetter: () => identity,
2471
+ entityOwnerResolver: (aid, type) => this._resolveEntityAgentMdOwner(aid, type),
2472
+ entityIdentityGetter: (aid, type) => this._freshEntityCertificate(aid, type),
2473
+ entitySignerResolver: (aid, fingerprint) => this._freshEntitySigner(aid, fingerprint),
2474
+ gatewayResolver: (aid) => identityStore._resolveGateway(aid),
2475
+ accessTokenResolver: (_aid, gatewayUrl) => identityStore._uploadAgentMdToken(identity, gatewayUrl),
2476
+ aidValidator: (aid) => identityStore._registerFlow.validateAidName(aid),
2477
+ });
2478
+ return await manager.resumePendingUpload(groupAid);
2479
+ }
1539
2480
  async _uploadGroupAgentMd(store, groupAid, params, group, uploaderAid) {
2481
+ if (await this._resumeGroupAgentMdUpload(store, groupAid))
2482
+ return;
1540
2483
  if (typeof store.uploadAgentMd !== 'function') {
1541
2484
  throw new ValidationError('group agent.md upload is unavailable');
1542
2485
  }
1543
- const uploaded = await store.uploadAgentMd(groupAid, this._groupAgentMdContent(groupAid, params, group), { uploaderAid });
2486
+ const uploaded = await store.uploadAgentMd(groupAid, await this._groupAgentMdContent(groupAid, params, group), { uploaderAid, entityOwnerResolver: (aid, type) => this._resolveEntityAgentMdOwner(aid, type), entityIdentityGetter: (aid, type) => this._freshEntityCertificate(aid, type), entitySignerResolver: (aid, fingerprint) => this._freshEntitySigner(aid, fingerprint) });
1544
2487
  if (!uploaded.ok) {
1545
- throw new ValidationError(`group agent.md upload failed: ${uploaded.error.message}`);
2488
+ const code = ['invalid', 'unavailable', 'forbidden', 'conflict', 'persistence_unavailable'].includes(uploaded.error.code) ? uploaded.error.code : uploaded.error.code === 'NETWORK_ERROR' ? 'unavailable' : 'invalid';
2489
+ throw new StateError(`group agent.md upload failed: ${uploaded.error.message}`, { stringCode: code, retryable: code === 'unavailable' });
1546
2490
  }
1547
2491
  }
2492
+ async _freshEntityCertificate(aid, type = 'group') {
2493
+ const cert = await this._auth.resolveCurrentEntityCertificate(await this._resolveOwnershipCertificateGateway(aid), aid, type);
2494
+ return AID.create({ aid, aunPath: this.configModel.aunPath, certPem: cert.cert_pem, certValid: true, privateKeyPem: null, privateKeyValid: false });
2495
+ }
2496
+ async _freshEntitySigner(aid, fingerprint) {
2497
+ const cert = await this._auth.resolveOwnershipCertificate(await this._resolveOwnershipCertificateGateway(aid), aid, fingerprint);
2498
+ return AID.create({ aid, aunPath: this.configModel.aunPath, certPem: cert.cert_pem, certValid: true, privateKeyPem: null, privateKeyValid: false });
2499
+ }
2500
+ async _resolveEntityAgentMdOwner(aid, type) {
2501
+ const authority = await this._agentMdManager.readEntityAuthorityFresh(aid);
2502
+ const certificate = await this._freshEntityCertificate(aid, type);
2503
+ if (authority.entity_cert_fingerprint !== certificate.certFingerprint)
2504
+ throw new StateError('entity certificate authority changed', { stringCode: 'conflict' });
2505
+ return authority.owner_aid;
2506
+ }
1548
2507
  async bindGroupAid(params = {}, options = {}) {
1549
2508
  if (!isJsonObject(params)) {
1550
2509
  throw new ValidationError('bindGroupAid params must be an object');
@@ -2002,6 +2961,168 @@ export class AUNClient {
2002
2961
  on(event, handler) {
2003
2962
  return this._dispatcher.subscribe(event, handler);
2004
2963
  }
2964
+ _readStateIsNewer(previous, current) {
2965
+ if (!previous)
2966
+ return true;
2967
+ const oldOrder = BigInt(String(previous.last_read_order ?? 0));
2968
+ const newOrder = BigInt(String(current.last_read_order ?? 0));
2969
+ if (oldOrder !== newOrder)
2970
+ return newOrder > oldOrder;
2971
+ const oldId = String(previous.last_read_message_id ?? '');
2972
+ const newId = String(current.last_read_message_id ?? '');
2973
+ if (oldOrder === 0n)
2974
+ return newId === oldId;
2975
+ const oldPoints = Array.from(oldId);
2976
+ const newPoints = Array.from(newId);
2977
+ for (let index = 0; index < Math.min(oldPoints.length, newPoints.length); index++) {
2978
+ const oldPoint = oldPoints[index].codePointAt(0);
2979
+ const newPoint = newPoints[index].codePointAt(0);
2980
+ if (oldPoint !== newPoint)
2981
+ return newPoint > oldPoint;
2982
+ }
2983
+ return newPoints.length >= oldPoints.length;
2984
+ }
2985
+ _ensureReadStateIdentity() {
2986
+ const identity = `${this._aid ?? ''}\u0000${this._deviceId ?? ''}`;
2987
+ if (identity !== this._readStateIdentity) {
2988
+ this._readStateCache.clear();
2989
+ this._readStateQueries.clear();
2990
+ this._readStateIdentity = identity;
2991
+ this._readStateGeneration += 1;
2992
+ }
2993
+ return `${identity}\u0000${this._readStateGeneration}`;
2994
+ }
2995
+ _registerReadStateQuery(method, params) {
2996
+ const key = method + '\u0000' + JSON.stringify(Object.keys(params).sort().map((field) => [field, params[field]]));
2997
+ this._readStateQueries.set(key, { method, params: { ...params } });
2998
+ }
2999
+ _readStateItems(result) {
3000
+ const resultObject = isJsonObject(result) ? result : null;
3001
+ return Array.isArray(resultObject?.states)
3002
+ ? resultObject.states.filter((item) => isJsonObject(item))
3003
+ : [];
3004
+ }
3005
+ _observeReadStateResult(method, result, params) {
3006
+ if (!['message.read', 'message.read_state', 'group.read', 'group.read_state'].includes(method))
3007
+ return;
3008
+ this._ensureReadStateIdentity();
3009
+ const isGroup = method.startsWith('group.');
3010
+ if (method.endsWith('.read')) {
3011
+ for (const state of this._readStateItems(params)) {
3012
+ const query = { topic_key: state.topic_key ?? '' };
3013
+ if (isGroup)
3014
+ query.group_id = state.group_id ?? params.group_id ?? '';
3015
+ else
3016
+ query.scope = state.scope || params.scope || 'p2p';
3017
+ this._registerReadStateQuery(`${isGroup ? 'group' : 'message'}.read_state`, query);
3018
+ }
3019
+ if (!Array.isArray(params.states) && params.topic_key) {
3020
+ const query = {};
3021
+ for (const field of ['scope', 'group_id', 'topic_key']) {
3022
+ if (field in params)
3023
+ query[field] = params[field];
3024
+ }
3025
+ this._registerReadStateQuery(`${isGroup ? 'group' : 'message'}.read_state`, query);
3026
+ }
3027
+ return;
3028
+ }
3029
+ this._registerReadStateQuery(method, params);
3030
+ for (const state of this._readStateItems(result)) {
3031
+ const scope = String(state.scope ?? (isGroup ? 'group' : 'p2p')).trim();
3032
+ const topicKey = String(state.topic_key ?? params.topic_key ?? '');
3033
+ const groupId = isGroup ? String(state.group_id ?? params.group_id ?? '').trim() : '';
3034
+ const lastRead = String(state.last_read_message_id ?? '').trim();
3035
+ if (!scope || !topicKey || !lastRead || (isGroup && !groupId))
3036
+ continue;
3037
+ const key = `${scope}\u0000${groupId}\u0000${topicKey}`;
3038
+ const query = { topic_key: topicKey };
3039
+ if (isGroup)
3040
+ query.group_id = groupId;
3041
+ else
3042
+ query.scope = scope;
3043
+ const previous = this._readStateCache.get(key)?.state;
3044
+ if (previous && !this._readStateIsNewer(previous, state))
3045
+ continue;
3046
+ const samePosition = previous?.last_read_message_id === state.last_read_message_id
3047
+ && BigInt(String(previous?.last_read_order ?? 0)) === BigInt(String(state.last_read_order ?? 0));
3048
+ this._readStateCache.set(key, {
3049
+ method: `${isGroup ? 'group' : 'message'}.read_state`,
3050
+ params: query,
3051
+ state: samePosition ? { ...previous, ...state } : { ...state },
3052
+ });
3053
+ }
3054
+ }
3055
+ _onRawReadChanged(data, expectedScope) {
3056
+ if (!isJsonObject(data))
3057
+ return;
3058
+ const eventData = data;
3059
+ if (eventData.scope !== expectedScope || eventData.owner_aid !== this._aid)
3060
+ return;
3061
+ const fields = ['owner_aid', 'topic_key', 'last_read_message_id', ...(expectedScope === 'group' ? ['group_id'] : [])];
3062
+ if (fields.some((field) => {
3063
+ const value = eventData[field];
3064
+ return typeof value !== 'string' || !value || Array.from(value).length > 128;
3065
+ }))
3066
+ return;
3067
+ const order = eventData.last_read_order;
3068
+ if (!(typeof order === 'number' && Number.isSafeInteger(order) && order > 0)
3069
+ && !(typeof order === 'string' && /^[1-9]\d*$/.test(order)))
3070
+ return;
3071
+ this._ensureReadStateIdentity();
3072
+ const scope = expectedScope;
3073
+ const topicKey = String(eventData.topic_key ?? '');
3074
+ const groupId = scope === 'group' ? String(eventData.group_id) : '';
3075
+ const lastRead = String(eventData.last_read_message_id);
3076
+ if (scope && topicKey && lastRead && (scope !== 'group' || groupId)) {
3077
+ const key = `${scope}\u0000${groupId}\u0000${topicKey}`;
3078
+ const query = { topic_key: topicKey };
3079
+ if (scope === 'group')
3080
+ query.group_id = groupId;
3081
+ else
3082
+ query.scope = scope;
3083
+ const method = `${scope === 'group' ? 'group' : 'message'}.read_state`;
3084
+ this._registerReadStateQuery(method, query);
3085
+ const previous = this._readStateCache.get(key)?.state;
3086
+ if (previous && (!this._readStateIsNewer(previous, eventData)
3087
+ || (previous.last_read_message_id === eventData.last_read_message_id
3088
+ && BigInt(String(previous.last_read_order ?? 0)) === BigInt(String(eventData.last_read_order ?? 0)))))
3089
+ return;
3090
+ this._readStateCache.set(key, { method, params: query, state: { ...eventData } });
3091
+ }
3092
+ this._dispatcher.enqueue(scope === 'group' ? 'group.read_changed' : 'message.read_changed', eventData);
3093
+ }
3094
+ async _reconcileReadStates() {
3095
+ const identity = this._ensureReadStateIdentity();
3096
+ const keys = new Set();
3097
+ const errors = [];
3098
+ for (const entry of [...this._readStateQueries.values()]) {
3099
+ try {
3100
+ const result = await this.call(entry.method, { ...entry.params });
3101
+ if (identity !== this._ensureReadStateIdentity())
3102
+ return;
3103
+ this._observeReadStateResult(entry.method, result, entry.params);
3104
+ for (const state of this._readStateItems(result)) {
3105
+ const scope = String(state.scope ?? (entry.method.startsWith('group.') ? 'group' : 'p2p'));
3106
+ const groupId = scope === 'group' ? String(state.group_id ?? entry.params.group_id ?? '') : '';
3107
+ const topicKey = String(state.topic_key ?? entry.params.topic_key ?? '');
3108
+ const key = `${scope}\u0000${groupId}\u0000${topicKey}`;
3109
+ if (this._readStateCache.has(key))
3110
+ keys.add(key);
3111
+ }
3112
+ }
3113
+ catch (error) {
3114
+ errors.push({ method: entry.method, params: { ...entry.params }, error: error instanceof Error ? error.message : String(error) });
3115
+ this._clientLog.debug(`read state reconnect reconciliation failed: ${error instanceof Error ? error.message : String(error)}`);
3116
+ }
3117
+ if (identity !== this._ensureReadStateIdentity())
3118
+ return;
3119
+ }
3120
+ this._dispatcher.enqueue('read_state_sync', {
3121
+ owner_aid: this._aid ?? '',
3122
+ states: [...keys].map((key) => ({ ...this._readStateCache.get(key).state })),
3123
+ errors,
3124
+ });
3125
+ }
2005
3126
  /** 取消订阅事件 */
2006
3127
  off(event, handler) {
2007
3128
  this._dispatcher.unsubscribe(event, handler);
@@ -2045,6 +3166,8 @@ export class AUNClient {
2045
3166
  this._delivery.markPublishedSeq(ns, seq);
2046
3167
  }
2047
3168
  async _publishAppEvent(event, payload, source = 'direct', ns = '', batch) {
3169
+ if (event === 'message.received' && await this._handleOwnershipMessage(payload))
3170
+ return;
2048
3171
  await this._delivery.publishAppEvent(event, payload, source, ns, batch);
2049
3172
  }
2050
3173
  _echoTimestamp() {
@@ -2086,8 +3209,8 @@ export class AUNClient {
2086
3209
  const trace = `${this._echoTimestamp()} [AUN-SDK.receive] aid=${this._aid ?? '-'} conn_uptime=${uptime}s`;
2087
3210
  msg.payload = { ...payload, text: payload.text + '\n' + trace };
2088
3211
  }
2089
- async _drainOrderedMessages(ns, beforeSeq, pullResponse = false, persist = true) {
2090
- await this._delivery.drainOrderedMessages(ns, beforeSeq, pullResponse, persist);
3212
+ async _drainOrderedMessages(ns, beforeSeq, pullResponse = false, persist = true, throughSeq) {
3213
+ await this._delivery.drainOrderedMessages(ns, beforeSeq, pullResponse, persist, undefined, 'pull', throughSeq);
2091
3214
  }
2092
3215
  async _publishOrderedMessage(event, ns, seq, payload, source = 'push', batch) {
2093
3216
  return this._delivery.publishOrderedMessage(event, ns, seq, payload, source, batch);
@@ -2546,11 +3669,26 @@ export class AUNClient {
2546
3669
  * 获取对方证书(带缓存 + 完整 PKI 验证:链 + CRL + OCSP + AID 绑定)。
2547
3670
  * 跨域时自动将请求路由到 peer 所在域的 Gateway。
2548
3671
  */
2549
- async _fetchPeerCert(aid, certFingerprint, timeoutMs = 5000) {
3672
+ async _resolveServiceEventCertificate(aid, fingerprint) {
3673
+ const certPem = await this._fetchPeerCert(aid, fingerprint);
3674
+ if (!this._gatewayUrl)
3675
+ throw new ValidationError('gateway url unavailable for service event certificate verification');
3676
+ await this._auth.verifyPeerCertificate(resolvePeerGatewayUrl(this._gatewayUrl, aid), certPem, aid, undefined, true);
3677
+ return certPem;
3678
+ }
3679
+ async _verifyIncomingServiceEvent(event, payload) {
3680
+ const targetAid = this._aid || this._currentAid?.aid || '';
3681
+ const version = this._serviceEventIdentityVersion;
3682
+ const verified = await verifyServiceEvent(event, payload, { targetAid, resolveEventCertificate: (service, fingerprint) => this._resolveServiceEventCertificate(service, fingerprint), resolveCredentialService: (cid) => this.resolveCredentialService?.(cid) });
3683
+ if (version !== this._serviceEventIdentityVersion || targetAid !== (this._aid || this._currentAid?.aid || ''))
3684
+ throw new ValidationError('service event receiver identity changed');
3685
+ return verified;
3686
+ }
3687
+ async _fetchPeerCert(aid, certFingerprint, timeoutMs = 5000, signedAt, agentMdRead = false) {
2550
3688
  const tStart = Date.now();
2551
3689
  this._clientLog.debug(`_fetchPeerCert enter: aid=${aid} fingerprint=${certFingerprint ?? '<none>'}`);
2552
3690
  try {
2553
- const cacheKey = certCacheKey(aid, certFingerprint);
3691
+ const cacheKey = certCacheKey(aid, certFingerprint) + (signedAt === undefined ? '' : `:signed:${signedAt}`) + (agentMdRead ? ':agentmd' : '');
2554
3692
  const cached = this._certCache.get(cacheKey);
2555
3693
  const now = Date.now() / 1000;
2556
3694
  if (cached && now < cached.refreshAfter) {
@@ -2589,7 +3727,12 @@ export class AUNClient {
2589
3727
  }
2590
3728
  // 完整 PKI 验证:链 + CRL + OCSP + AID 绑定
2591
3729
  try {
2592
- await this._auth.verifyPeerCertificate(peerGatewayUrl, certPem, aid);
3730
+ if (signedAt === undefined)
3731
+ await this._auth.verifyPeerCertificate(peerGatewayUrl, certPem, aid);
3732
+ else if (agentMdRead)
3733
+ await this._auth.verifyPeerCertificate(peerGatewayUrl, certPem, aid, signedAt, false, true);
3734
+ else
3735
+ await this._auth.verifyPeerCertificate(peerGatewayUrl, certPem, aid, signedAt);
2593
3736
  }
2594
3737
  catch (exc) {
2595
3738
  throw new ValidationError(`peer cert verification failed for ${aid}: ${exc}`);
@@ -2664,6 +3807,8 @@ export class AUNClient {
2664
3807
  if (identity && isJsonObject(identity)) {
2665
3808
  this._identity = identity;
2666
3809
  this._aid = String(identity.aid ?? this._aid ?? '');
3810
+ this._ownershipRecordsLoad = this._restoreOwnershipRecords();
3811
+ await this._ownershipRecordsLoad;
2667
3812
  this._logger.bindAid(this._aid);
2668
3813
  if (this._sessionParams) {
2669
3814
  this._sessionParams.access_token = String(auth.token ?? params.access_token ?? '');
@@ -2746,6 +3891,7 @@ export class AUNClient {
2746
3891
  }
2747
3892
  this._lifecycle.assertConnectionAttemptOwner(connectionOwner);
2748
3893
  this._startBackgroundTasks();
3894
+ this._safeAsync(this._reconcileReadStates());
2749
3895
  this._safeAsync(this.restoreGroupInvitesAfterConnect());
2750
3896
  const connectionKind = String(params.connection_kind ?? 'long');
2751
3897
  const isShortConnection = connectionKind === 'short';
@@ -2764,7 +3910,7 @@ export class AUNClient {
2764
3910
  if (backgroundSyncEnabled) {
2765
3911
  this._safeAsync(this._fillP2pGap());
2766
3912
  }
2767
- this._safeAsync(this._checkIdentityAdmissionAfterConnect());
3913
+ this._safeAsync(this._resumeOwnershipAfterConnect());
2768
3914
  this._clientLog.debug(`_connectOnce exit: elapsed=${Date.now() - tStart}ms aid=${this._aid ?? '-'}`);
2769
3915
  }
2770
3916
  catch (err) {
@@ -2783,6 +3929,23 @@ export class AUNClient {
2783
3929
  const gateways = await this._resolveGatewayCandidatesForAid(aid);
2784
3930
  return gateways[0];
2785
3931
  }
3932
+ async _resolveOwnershipCertificateGateway(aid) {
3933
+ const issuer = aid.slice(aid.indexOf('.') + 1);
3934
+ if (issuer === this._aid?.slice(this._aid.indexOf('.') + 1))
3935
+ return this._resolveGatewayForAid(aid);
3936
+ let lastError;
3937
+ for (const host of [aid, `gateway.${issuer}`]) {
3938
+ try {
3939
+ const gateways = await this._discovery.discoverAll(`https://${host}/.well-known/aun-gateway`);
3940
+ if (gateways.length)
3941
+ return gateways[0];
3942
+ }
3943
+ catch (error) {
3944
+ lastError = error;
3945
+ }
3946
+ }
3947
+ throw new StateError(lastError instanceof Error ? lastError.message : 'signer Gateway discovery failed', { stringCode: 'unavailable', retryable: true });
3948
+ }
2786
3949
  async _resolveGatewayCandidatesForAid(aid) {
2787
3950
  const target = String(aid ?? this._aid ?? '').trim();
2788
3951
  if (!target)
@@ -3006,6 +4169,7 @@ export class AUNClient {
3006
4169
  }
3007
4170
  // ── 内部:后台任务 ────────────────────────────────
3008
4171
  _startBackgroundTasks() {
4172
+ this._startOwnershipRecovery();
3009
4173
  // WebSocket Ping/Pong 由 Gateway 与传输层处理;SDK 不发送 RPC 心跳。
3010
4174
  // 短连接生命周期短,不需要 token 刷新。
3011
4175
  if (this._sessionOptions?.connection_kind !== 'short') {
@@ -3034,6 +4198,11 @@ export class AUNClient {
3034
4198
  }
3035
4199
  }
3036
4200
  _stopBackgroundTasks() {
4201
+ this._ownershipRecoveryGeneration += 1;
4202
+ if (this._ownershipRecoveryTimer !== null) {
4203
+ clearTimeout(this._ownershipRecoveryTimer);
4204
+ this._ownershipRecoveryTimer = null;
4205
+ }
3037
4206
  if (this._heartbeatTimer !== null) {
3038
4207
  clearInterval(this._heartbeatTimer);
3039
4208
  this._heartbeatTimer = null;
@@ -3047,6 +4216,32 @@ export class AUNClient {
3047
4216
  this._cacheCleanupTimer = null;
3048
4217
  }
3049
4218
  }
4219
+ _startOwnershipRecovery() {
4220
+ if (this._ownershipRecoveryTimer !== null || !this._aid || this._state !== 'connected' || this._closing || this._disconnecting)
4221
+ return;
4222
+ const aid = this._aid, generation = ++this._ownershipRecoveryGeneration;
4223
+ const active = () => generation === this._ownershipRecoveryGeneration && aid === this._aid && this._state === 'connected' && !this._closing && !this._disconnecting;
4224
+ const guard = () => { if (!active())
4225
+ throw new StateError('ownership recovery was superseded', { stringCode: 'forbidden' }); };
4226
+ const recover = async () => {
4227
+ try {
4228
+ if (!active())
4229
+ return;
4230
+ await this._withBackgroundRpc(async () => {
4231
+ await this._resumeRegistrationIntent(guard);
4232
+ guard();
4233
+ await this._ownershipV21().resume(guard);
4234
+ });
4235
+ }
4236
+ finally {
4237
+ if (active())
4238
+ this._ownershipRecoveryTimer = setTimeout(() => this._safeAsync(recover()), 5000);
4239
+ else if (generation === this._ownershipRecoveryGeneration)
4240
+ this._ownershipRecoveryTimer = null;
4241
+ }
4242
+ };
4243
+ this._ownershipRecoveryTimer = setTimeout(() => this._safeAsync(recover()), 5000);
4244
+ }
3050
4245
  /** 心跳定时器 */
3051
4246
  _startHeartbeat() {
3052
4247
  if (this._heartbeatTimer !== null) {