@agentunion/fastaun 0.5.19 → 0.5.21

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 (129) hide show
  1. package/CHANGELOG.md +176 -78
  2. package/_packed_docs/CHANGELOG.md +176 -78
  3. package/_packed_docs/INDEX.md +288 -269
  4. package/_packed_docs/KITE_DOCS_GUIDE.md +101 -95
  5. package/_packed_docs/agent.md/SCHEMA.md +7 -7
  6. package/_packed_docs/agent.md/working/AID/347/261/273/345/236/213-owner/345/205/263/347/263/273/344/270/216/346/215/242owner/347/274/226/346/216/222/346/226/271/346/241/210.md +530 -0
  7. package/_packed_docs/agent.md/working/AID/347/261/273/345/236/213-owner/345/205/263/347/263/273/345/256/214/346/225/264/350/256/276/350/256/241/344/270/216/345/256/236/346/226/275/350/256/241/345/210/222.md +685 -0
  8. package/_packed_docs/agent.md/working/agent.md-schema/344/270/216/347/255/276/345/217/221/351/252/214/347/255/276/346/265/201/347/250/213/346/276/204/346/270/205.md +349 -0
  9. package/_packed_docs/agent.md/working//346/234/254/345/234/260Docker/346/265/213/350/257/225Bug/346/270/205/345/215/225.md +40 -0
  10. 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
  11. package/_packed_docs/protocol/06-/346/234/215/345/212/241/345/215/217/350/256/256.md +372 -331
  12. package/_packed_docs/protocol/08-AUN-E2EE.md +17 -12
  13. package/_packed_docs/protocol/10-Group-/345/255/220/345/215/217/350/256/256.md +676 -676
  14. package/_packed_docs/protocol/11-Storage-/345/255/220/345/215/217/350/256/256.md +8 -12
  15. package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/211.md +445 -0
  16. package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2112.0.md +161 -0
  17. package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2112.1.md +666 -0
  18. package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2113.0.md +233 -0
  19. package/_packed_docs/protocol/agent.md/SCHEMA.md +1 -1
  20. package/_packed_docs/sdk/0.5.21-SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +368 -0
  21. package/_packed_docs/sdk/0.5.21-SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +350 -0
  22. package/_packed_docs/sdk/01-/345/277/253/351/200/237/345/274/200/345/247/213.md +1 -1
  23. package/_packed_docs/sdk/02-WebSocket/345/215/217/350/256/256.md +5 -1
  24. package/_packed_docs/sdk/03-/346/240/270/345/277/203/346/246/202/345/277/265.md +64 -64
  25. package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +70 -63
  26. package/_packed_docs/sdk/05-E2EE/345/212/240/345/257/206/351/200/232/344/277/241.md +43 -40
  27. package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +179 -19
  28. package/_packed_docs/sdk/08-/346/234/200/344/275/263/345/256/236/350/267/265.md +50 -49
  29. package/_packed_docs/sdk/09-group-rpc-manual.md +261 -254
  30. package/_packed_docs/sdk/09-message-rpc-manual.md +253 -160
  31. package/_packed_docs/sdk/09-storage-rpc-manual.md +18 -26
  32. package/_packed_docs/sdk/09-/345/207/255/350/257/201/346/234/215/345/212/241RPC/346/211/213/345/206/214.md +71 -0
  33. package/_packed_docs/sdk/09-/347/273/204/347/273/207/346/234/215/345/212/241RPC/346/211/213/345/206/214.md +88 -0
  34. package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +105 -92
  35. package/_packed_docs/sdk/INDEX.md +197 -167
  36. package/_packed_docs/sdk/Ownership-SDK/345/256/236/347/216/260/346/214/207/345/215/227.md +169 -0
  37. package/_packed_docs/sdk/Ownership-SDK/347/274/226/346/216/222/346/214/207/345/215/227.md +349 -0
  38. package/_packed_docs/sdk/Ownership-SDK/347/274/226/346/216/222/346/214/207/345/215/2272.1.md +930 -0
  39. package/_packed_docs/sdk/Ownership-SDK/351/233/206/346/210/220/346/214/207/345/215/227.md +270 -0
  40. 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 +363 -0
  41. 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 +553 -0
  42. 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
  43. package/_packed_docs/sdk/Ownership2.1/350/257/246/347/273/206/350/256/276/350/256/241.md +480 -0
  44. package/_packed_docs/sdk/Ownership/347/256/241/347/220/206/344/270/223/351/242/230.md +559 -0
  45. package/_packed_docs/sdk/ProtectedHeaders/347/224/250/346/263/225/344/270/223/351/242/230.md +299 -0
  46. 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
  47. 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
  48. 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
  49. 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
  50. 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
  51. 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
  52. 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
  53. 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
  54. package/dist/agent-md-schema.d.ts +13 -1
  55. package/dist/agent-md-schema.js +63 -33
  56. package/dist/agent-md-schema.js.map +1 -1
  57. package/dist/agent-md.d.ts +30 -3
  58. package/dist/agent-md.js +293 -152
  59. package/dist/agent-md.js.map +1 -1
  60. package/dist/aid-store.d.ts +42 -0
  61. package/dist/aid-store.js +224 -44
  62. package/dist/aid-store.js.map +1 -1
  63. package/dist/aid.js +18 -13
  64. package/dist/aid.js.map +1 -1
  65. package/dist/auth.js +78 -2
  66. package/dist/auth.js.map +1 -1
  67. package/dist/cert-utils.d.ts +44 -1
  68. package/dist/cert-utils.js +157 -56
  69. package/dist/cert-utils.js.map +1 -1
  70. package/dist/client/delivery.d.ts +68 -1
  71. package/dist/client/delivery.js +1196 -135
  72. package/dist/client/delivery.js.map +1 -1
  73. package/dist/client/rpc-pipeline.d.ts +5 -1
  74. package/dist/client/rpc-pipeline.js +240 -111
  75. package/dist/client/rpc-pipeline.js.map +1 -1
  76. package/dist/client/v2-e2ee.d.ts +12 -2
  77. package/dist/client/v2-e2ee.js +842 -379
  78. package/dist/client/v2-e2ee.js.map +1 -1
  79. package/dist/client.d.ts +26 -2
  80. package/dist/client.js +420 -75
  81. package/dist/client.js.map +1 -1
  82. package/dist/config.d.ts +4 -0
  83. package/dist/config.js +1 -0
  84. package/dist/config.js.map +1 -1
  85. package/dist/events.d.ts +23 -4
  86. package/dist/events.js +171 -15
  87. package/dist/events.js.map +1 -1
  88. package/dist/facades.d.ts +43 -1
  89. package/dist/facades.js +368 -12
  90. package/dist/facades.js.map +1 -1
  91. package/dist/index.d.ts +7 -4
  92. package/dist/index.js +1 -1
  93. package/dist/index.js.map +1 -1
  94. package/dist/keystore/aid-db.d.ts +7 -2
  95. package/dist/keystore/aid-db.js +194 -43
  96. package/dist/keystore/aid-db.js.map +1 -1
  97. package/dist/keystore/index.d.ts +15 -1
  98. package/dist/keystore/local-token-store.d.ts +10 -2
  99. package/dist/keystore/local-token-store.js +81 -16
  100. package/dist/keystore/local-token-store.js.map +1 -1
  101. package/dist/org-protocol.d.ts +45 -0
  102. package/dist/org-protocol.js +829 -22
  103. package/dist/org-protocol.js.map +1 -1
  104. package/dist/ownership-v21.d.ts +40 -9
  105. package/dist/ownership-v21.js +174 -79
  106. package/dist/ownership-v21.js.map +1 -1
  107. package/dist/ownership-workflow.d.ts +22 -0
  108. package/dist/ownership-workflow.js +861 -66
  109. package/dist/ownership-workflow.js.map +1 -1
  110. package/dist/secret-store/file-store.js +84 -0
  111. package/dist/secret-store/file-store.js.map +1 -1
  112. package/dist/seq-tracker.d.ts +1 -0
  113. package/dist/seq-tracker.js +2 -2
  114. package/dist/seq-tracker.js.map +1 -1
  115. package/dist/tools/cross-sdk-agent.d.ts +67 -3
  116. package/dist/tools/cross-sdk-agent.js +575 -42
  117. package/dist/tools/cross-sdk-agent.js.map +1 -1
  118. package/dist/transport.d.ts +4 -0
  119. package/dist/transport.js +108 -44
  120. package/dist/transport.js.map +1 -1
  121. package/dist/types.d.ts +1 -0
  122. package/dist/v2/e2ee/encrypt-group.js +1 -0
  123. package/dist/v2/e2ee/encrypt-group.js.map +1 -1
  124. package/dist/v2/e2ee/encrypt-p2p.js +1 -0
  125. package/dist/v2/e2ee/encrypt-p2p.js.map +1 -1
  126. package/dist/v2/e2ee/types.d.ts +1 -0
  127. package/dist/version.d.ts +1 -1
  128. package/dist/version.js +1 -1
  129. package/package.json +1 -1
package/dist/client.js CHANGED
@@ -26,7 +26,7 @@ import { normalizeGroupId } from './group-id.js';
26
26
  import { validateAIDFormat, validateGroupIDFormat } from './validators.js';
27
27
  import { RPCTransport } from './transport.js';
28
28
  import { AuthFlow } from './auth.js';
29
- import { AgentMdManager, buildDefaultAgentMd, updateOwnershipAgentMd } from './agent-md.js';
29
+ import { AgentMdManager, buildDefaultAgentMd } from './agent-md.js';
30
30
  import { GroupIndexMetaCache } from './group-index.js';
31
31
  import { SeqTracker } from './seq-tracker.js';
32
32
  import { ClientRuntime } from './client/runtime.js';
@@ -37,7 +37,7 @@ import { LifecycleController } from './client/lifecycle.js';
37
37
  import { PeerDirectory } from './client/peers.js';
38
38
  import { RpcPipeline } from './client/rpc-pipeline.js';
39
39
  import { CollabClient } from './collab/client.js';
40
- import { CredentialFacade, OrgFacade, GroupFacade, MailFacade, MessageFacade, StreamFacade } from './facades.js';
40
+ import { DebugFacade, CredentialFacade, OrgFacade, GroupFacade, MailFacade, MessageFacade, StreamFacade } from './facades.js';
41
41
  import { StorageVFS } from './storage/vfs.js';
42
42
  import { V2E2EECoordinator } from './client/v2-e2ee.js';
43
43
  import { GroupStateCoordinator } from './client/group-state.js';
@@ -47,10 +47,10 @@ import { decryptedOwnershipJsonText } from './v2/e2ee/decrypt.js';
47
47
  import { isJsonObject, ConnectionState, STATE_TO_PUBLIC, } from './types.js';
48
48
  import { AID } from './aid.js';
49
49
  import { certFingerprint, certMatchesFingerprint, normalizeFingerprintHex, publicKeyMatchesFingerprint } from './cert-utils.js';
50
- import { validateAgentMdDocument } from './agent-md-schema.js';
51
- import { OwnershipFiles, OwnershipStorageAdapter, ownershipFailure, signOwnershipFile } from './ownership-v21.js';
50
+ import { validateAgentMdCertificate, validateAgentMdDocument } from './agent-md-schema.js';
51
+ import { OwnershipFiles, OwnershipStorageAdapter, ownershipFailure } from './ownership-v21.js';
52
52
  import { OwnershipWorkflow } from './ownership-workflow.js';
53
- import { canonicalJson, createCredential, createMemberCredential, signMembershipBinding, verifyServiceEvent } from './org-protocol.js';
53
+ import { canonicalJson, createCredential, createMemberCredential, signMembershipBinding, digest, validateMembershipBinding, verifyMembershipBinding, verifyCredentialSignature, createRepresentationProof, verifyServiceEvent, buildRequestBinding, validateRequestBinding, CredentialRenewal } from './org-protocol.js';
54
54
  function isPromiseLike(value) {
55
55
  return Boolean(value && typeof value.then === 'function');
56
56
  }
@@ -440,7 +440,7 @@ function createAgentMdManagerForRuntime(opts) {
440
440
  const target = String(aid ?? '').trim();
441
441
  const expectedFp = String(certFingerprint ?? '').trim().toLowerCase();
442
442
  const current = opts.currentAid();
443
- if (current?.aid === target) {
443
+ if (signedAt === undefined && current?.aid === target) {
444
444
  if (!expectedFp || certMatchesFingerprint(current.certPem, expectedFp))
445
445
  return current;
446
446
  }
@@ -478,6 +478,7 @@ export class AUNClient {
478
478
  /** 当前 AID 值对象(新 API) */
479
479
  _currentAid = null;
480
480
  _serviceEventIdentityVersion = 0;
481
+ _serviceEventIdentityAbort = new AbortController();
481
482
  /** ownership 关系只在 SDK 内存中编排;服务端不保存状态机。 */
482
483
  _ownershipRecords = new Map();
483
484
  _ownershipType = 'agent';
@@ -485,7 +486,7 @@ export class AUNClient {
485
486
  _ownershipFiles = null;
486
487
  _ownershipWorkflow = null;
487
488
  _getOwnershipStorage() {
488
- return this._ownershipStorage ??= new OwnershipStorageAdapter({ currentAid: () => this._aid, call: (method, params) => this.call(method, params) });
489
+ return this._ownershipStorage ??= new OwnershipStorageAdapter({ currentAid: () => this._aid, call: (method, params) => this.call(method, params), httpPut: (url, data, headers) => this.storage.lowlevel.httpPut(url, data, headers) });
489
490
  }
490
491
  _getOwnershipFiles() {
491
492
  return this._ownershipFiles ??= new OwnershipFiles({
@@ -494,10 +495,10 @@ export class AUNClient {
494
495
  currentType: () => this.getType(),
495
496
  now: () => Math.trunc(Date.now() / 1000),
496
497
  readAgentMdFresh: (subAid) => this._agentMdManager.readOwnershipFresh(subAid),
497
- submitAgentMdRaw: (subAid, raw, condition) => this._agentMdManager.submitOwnershipRaw(subAid, raw, condition),
498
+ submitAgentMdRaw: (subAid, raw, condition, assertActive) => this._agentMdManager.submitOwnershipRaw(subAid, raw, condition, assertActive),
498
499
  readOwnerMd: (ownerAid, subAid) => this._getOwnershipStorage().readOwnerMd(ownerAid, subAid),
499
- writeOwnerMd: (ownerAid, subAid, raw, revision) => this._getOwnershipStorage().writeOwnerMd(ownerAid, subAid, raw, revision),
500
- deleteOwnerMd: (ownerAid, subAid, revision) => this._getOwnershipStorage().deleteOwnerMd(ownerAid, subAid, revision),
500
+ writeOwnerMd: (ownerAid, subAid, raw, original, assertActive) => this._getOwnershipStorage().writeOwnerMd(ownerAid, subAid, raw, original, assertActive),
501
+ deleteOwnerMd: (ownerAid, subAid) => this._getOwnershipStorage().deleteOwnerMd(ownerAid, subAid),
501
502
  resolveCertificate: async (aid, fingerprint, purpose) => this._auth.resolveOwnershipCertificate(await this._resolveOwnershipCertificateGateway(aid), aid, fingerprint, purpose),
502
503
  });
503
504
  }
@@ -549,6 +550,188 @@ export class AUNClient {
549
550
  await this._resumeRegistrationIntent();
550
551
  await this._resumeOwnership();
551
552
  }
553
+ _credentialRenewal;
554
+ _credentialRenewalTimer = null;
555
+ _credentialRenewalAbort = null;
556
+ _credentialRenewalScan = Promise.resolve();
557
+ _getCredentialRenewal() {
558
+ return this._credentialRenewal ??= new CredentialRenewal({
559
+ currentAid: () => this._aid ?? '',
560
+ atomicCheckpointSave: () => this._tokenStore instanceof LocalTokenStore,
561
+ load: (aid) => (this._tokenStore._loadMetadataStrict?.(aid) ?? this._tokenStore.loadMetadata?.(aid))?.credential_renewals_v1 ?? {},
562
+ ...(typeof this._tokenStore._loadMetadataStrict === 'function' ? {
563
+ loadRevocations: (aid) => this._tokenStore._loadMetadataStrict(aid)?.credential_revocations_v1 ?? {},
564
+ saveRevocations: (aid, state, guard) => {
565
+ if (guard && !guard.current())
566
+ throw new ValidationError('credential revocation synchronization was superseded');
567
+ this._tokenStore.saveMetadata?.(aid, { credential_revocations_v1: state });
568
+ },
569
+ } : {}),
570
+ save: (aid, records, guard) => {
571
+ const version = this._serviceEventIdentityVersion;
572
+ const current = () => version === this._serviceEventIdentityVersion && aid === this._aid && (!guard || guard.current());
573
+ if (!current())
574
+ throw new ValidationError('membership event receiver identity changed');
575
+ const saved = this._tokenStore instanceof LocalTokenStore ? records : Object.fromEntries(Object.entries(records).map(([cid, value]) => {
576
+ const record = { ...value };
577
+ for (const key of ['submitted_at', 'submission_hash', 'registration_submitted_at', 'registration_submission_hash'])
578
+ delete record[key];
579
+ return [cid, record];
580
+ }));
581
+ this._tokenStore.saveMetadata?.(aid, { credential_renewals_v1: saved });
582
+ if (!current())
583
+ throw new ValidationError('membership event receiver identity changed');
584
+ },
585
+ signer: (aid, guard) => this._credentialRenewalSigner(aid, guard),
586
+ certificate: (aid, fingerprint, signedAt) => signedAt === undefined
587
+ ? this._fetchServiceEventCertificate(aid, fingerprint) : this._fetchPeerCert(aid, fingerprint, undefined, signedAt),
588
+ call: (method, params, onSent) => {
589
+ if (!onSent)
590
+ return this._rpcPipeline.call(method, params);
591
+ const version = this._serviceEventIdentityVersion, aid = this._aid;
592
+ return this._rpcPipeline['_callWithSent'](method, params, async () => {
593
+ if (version !== this._serviceEventIdentityVersion || aid !== this._aid)
594
+ throw new ValidationError('credential sender identity changed');
595
+ await onSent();
596
+ });
597
+ },
598
+ send: (to, payload) => this.call('message.send', { to, encrypt: true, payload }),
599
+ });
600
+ }
601
+ _assertCredentialNotRevoked(credential, proofChain) {
602
+ const version = this._serviceEventIdentityVersion, aid = this._aid;
603
+ const current = () => { if (aid !== this._aid || version !== this._serviceEventIdentityVersion)
604
+ throw new ValidationError('credential receiver identity changed'); };
605
+ this._getCredentialRenewal()._assertNotRevoked(credential, proofChain);
606
+ current();
607
+ return current;
608
+ }
609
+ async _credentialRenewalSigner(aid, guard) {
610
+ const check = () => { if (guard && !guard.current())
611
+ throw new ValidationError('credential renewal was superseded'); };
612
+ check();
613
+ const stored = this._aidStore?.load(aid);
614
+ let identity = aid === this._aid && this._identity?.private_key_pem
615
+ ? AID._create({ aid, aunPath: this._configModel.aunPath, certPem: String(this._identity.cert ?? this._currentAid?.certPem ?? ''), privateKeyPem: String(this._identity.private_key_pem), certValid: true, privateKeyValid: true })
616
+ : stored?.ok ? stored.data.aid : undefined;
617
+ if (!identity?.isPrivateKeyValid())
618
+ return null;
619
+ const certificate = new crypto.X509Certificate(identity.certPem);
620
+ const end = Date.parse(certificate.validTo), start = Date.parse(certificate.validFrom);
621
+ if (end > Date.now() && end - Date.now() < (end - start) / 2 && this._aidStore) {
622
+ const renewed = await (guard ? this._aidStore._renewCert(aid, guard) : this._aidStore.renewCert(aid));
623
+ check();
624
+ if (!renewed.ok)
625
+ throw new ValidationError(renewed.error.message);
626
+ const loaded = this._aidStore.load(aid);
627
+ if (!loaded.ok)
628
+ throw new ValidationError(loaded.error.message);
629
+ identity = loaded.data.aid;
630
+ if (aid === this._aid && this._identity) {
631
+ this._identity.cert = identity.certPem;
632
+ this._currentAid = identity;
633
+ }
634
+ }
635
+ return identity;
636
+ }
637
+ _currentCredential(input) {
638
+ const records = this._tokenStore.loadMetadata?.(this._aid ?? '')?.credential_renewals_v1 ?? {};
639
+ let cid = typeof input === 'string' ? input : String(input.cid);
640
+ const seen = new Set();
641
+ while (records[cid]?.successor_cid && !seen.has(cid)) {
642
+ seen.add(cid);
643
+ cid = String(records[cid].successor_cid);
644
+ }
645
+ return records[cid] ? structuredClone(records[cid]) : null;
646
+ }
647
+ _credentialCurrent(input) {
648
+ return this._getCredentialRenewal().current(input);
649
+ }
650
+ async _resumeCredentialRenewalAfterConnect(guard) {
651
+ if (!this._aid || (guard && !guard.current()))
652
+ return;
653
+ await this._safeAsync(guard ? this._credentialRenewalSigner(this._aid, guard) : this._credentialRenewalSigner(this._aid));
654
+ if (!guard || guard.current())
655
+ await this._safeAsync(this._getCredentialRenewal()._syncRevocations?.(guard) ?? Promise.resolve());
656
+ if (!guard || guard.current())
657
+ await this._getCredentialRenewal().resume(guard);
658
+ }
659
+ _startCredentialRenewal() {
660
+ this._stopCredentialRenewal();
661
+ const aid = this._aid, version = this._serviceEventIdentityVersion;
662
+ if (!aid || !['ready', 'connected'].includes(this._state) || this._closing || this._disconnecting)
663
+ return;
664
+ const abort = new AbortController();
665
+ this._credentialRenewalAbort = abort;
666
+ const current = () => this._credentialRenewalAbort === abort && !abort.signal.aborted && aid === this._aid && version === this._serviceEventIdentityVersion && ['ready', 'connected'].includes(this._state) && !this._closing && !this._disconnecting;
667
+ const guard = { current, signal: abort.signal };
668
+ const scan = async () => {
669
+ const task = this._credentialRenewalScan.then(async () => {
670
+ if (!current())
671
+ return;
672
+ await this._resumeCredentialRenewalAfterConnect(guard);
673
+ });
674
+ this._credentialRenewalScan = task.catch(() => { });
675
+ await this._safeAsync(task);
676
+ if (current())
677
+ this._credentialRenewalTimer = setTimeout(() => { void scan(); }, 60_000);
678
+ };
679
+ void scan();
680
+ }
681
+ _stopCredentialRenewal() {
682
+ this._credentialRenewalAbort?.abort();
683
+ this._credentialRenewalAbort = null;
684
+ if (this._credentialRenewalTimer !== null)
685
+ clearTimeout(this._credentialRenewalTimer);
686
+ this._credentialRenewalTimer = null;
687
+ }
688
+ _rememberCredential(credential, material, certificate) {
689
+ if (!material.long_term && certificate) {
690
+ const cert = new crypto.X509Certificate(typeof certificate === 'string' && !certificate.includes('-----BEGIN CERTIFICATE-----') ? Buffer.from(certificate, 'base64') : certificate);
691
+ const parent = Array.isArray(credential.proof_chain) ? credential.proof_chain[0] : undefined;
692
+ const certificateEnd = Math.floor(Date.parse(cert.validTo) / 1000);
693
+ const defaultEnd = credential.parent != null && isJsonObject(parent) && typeof parent.exp === 'number'
694
+ ? Math.min(certificateEnd, Number(parent.exp)) : certificateEnd;
695
+ material = { ...material, long_term: credential.exp === defaultEnd };
696
+ }
697
+ this._safeAsync(this._getCredentialRenewal().remember(credential, material));
698
+ }
699
+ async _verifyIncomingServiceEvent(event, payload) {
700
+ const targetAid = this._identity?.aid ?? '';
701
+ const version = this._serviceEventIdentityVersion;
702
+ const signal = this._serviceEventIdentityAbort.signal;
703
+ const expiresAt = Number(payload?.expires_at);
704
+ const current = () => version === this._serviceEventIdentityVersion && targetAid === (this._identity?.aid ?? '') && Date.now() / 1000 < expiresAt;
705
+ const verified = await verifyServiceEvent(event, payload, { targetAid, resolveEventCertificate: (aid, fingerprint) => this._fetchServiceEventCertificate(aid, fingerprint), resolveCredentialService: this.resolveCredentialService });
706
+ if (!current())
707
+ throw new ValidationError('service event receiver identity changed');
708
+ if (event.replace(/^event\//, '') === 'org.member.changed' && isJsonObject(payload) && Object.hasOwn(payload, 'membership_package')) {
709
+ await this._getCredentialRenewal().importMembershipPackage(payload, { current, signal });
710
+ if (!current())
711
+ throw new ValidationError('service event receiver identity changed');
712
+ }
713
+ return verified;
714
+ }
715
+ async _handleCredentialRenewal(message) {
716
+ if (!isJsonObject(message))
717
+ return false;
718
+ const envelope = message;
719
+ const content = 'payload' in envelope ? envelope.payload : envelope;
720
+ const payload = decryptedOwnershipJsonText(content) ?? content;
721
+ let parsed = payload;
722
+ if (typeof payload === 'string') {
723
+ try {
724
+ parsed = JSON.parse(payload);
725
+ }
726
+ catch {
727
+ return false;
728
+ }
729
+ }
730
+ if (!isJsonObject(parsed) || !['credential.renewal.request', 'credential.renewal.result'].includes(String(parsed.type)))
731
+ return false;
732
+ await this._safeAsync(this._getCredentialRenewal().receive(String(envelope.from ?? envelope.from_aid ?? envelope.sender_aid ?? ''), parsed));
733
+ return true;
734
+ }
552
735
  async _resumeRegistrationIntent() {
553
736
  const intent = this._tokenStore.loadMetadata?.(this._aid)?.registration_intent;
554
737
  if (['group', 'org'].includes(String(intent?.type ?? this.getType())))
@@ -598,11 +781,13 @@ export class AUNClient {
598
781
  _defaultConnectDeliveryMode;
599
782
  /** peer 证书缓存 */
600
783
  _certCache = new Map();
784
+ _certFetchInFlight = new Map();
601
785
  _agentMdManager;
602
786
  _aidStore;
603
787
  _groupIdentityOperations = new Map();
604
788
  _groupIndexMetaCache = new GroupIndexMetaCache();
605
789
  /** 消息序列号跟踪器(群消息 + P2P 空洞检测) */
790
+ _p2pPeerPullEnabled = true;
606
791
  _seqTracker = new SeqTracker();
607
792
  _seqTrackerContext = null;
608
793
  /** 惰性群同步:已同步过的 group_id 集合 */
@@ -685,6 +870,7 @@ export class AUNClient {
685
870
  _storage;
686
871
  _collab;
687
872
  _message;
873
+ _debug;
688
874
  _mail;
689
875
  _group;
690
876
  _stream;
@@ -725,7 +911,7 @@ export class AUNClient {
725
911
  if (debugFlag) {
726
912
  this._clientLog.info(`AUNClient initialized (debug=true, aunPath=${this._configModel.aunPath})`);
727
913
  }
728
- this._dispatcher = new EventDispatcher(this._logger.for('aun_core.events'), () => this._identity?.aid ?? '');
914
+ this._dispatcher = new EventDispatcher(this._logger.for('aun_core.events'), () => this._identity?.aid ?? '', () => this._ensureReadStateIdentity());
729
915
  const dnsNet = new DnsResilientNet({
730
916
  verifySsl: this._configModel.verifySsl,
731
917
  logger: this._clientLog,
@@ -771,7 +957,7 @@ export class AUNClient {
771
957
  },
772
958
  auth: () => this._auth,
773
959
  tokenStore: () => this._tokenStore,
774
- fetchPeerCert: (target, certFingerprint, signedAt) => this._fetchPeerCert(target, certFingerprint || undefined, undefined, signedAt),
960
+ fetchPeerCert: (target, certFingerprint, signedAt) => this._fetchPeerCert(target, certFingerprint || undefined, undefined, signedAt, true),
775
961
  resolveEntityOwner: (target, type) => this._resolveEntityAgentMdOwner(target, type),
776
962
  fetchEntityIdentity: (target, type) => this._freshEntityCertificate(target, type),
777
963
  fetchEntitySigner: (target, fingerprint) => this._freshEntitySigner(target, fingerprint),
@@ -784,9 +970,10 @@ export class AUNClient {
784
970
  dnsNet,
785
971
  });
786
972
  this._transport.setProtocolMetaObserver((meta) => this._observeRpcMeta(meta));
787
- this._transport.setServiceEventVerifier((event, payload) => verifyServiceEvent(event, payload, { targetAid: this._identity?.aid ?? '', resolveEventCertificate: (aid, fingerprint) => this._fetchServiceEventCertificate(aid, fingerprint), resolveCredentialService: this.resolveCredentialService }), () => this._identity?.aid ?? '', () => this._serviceEventIdentityVersion);
973
+ this._transport.setServiceEventVerifier((event, payload) => this._verifyIncomingServiceEvent(event, payload), () => this._identity?.aid ?? '', () => this._serviceEventIdentityVersion);
788
974
  this._transport.setProtocolDisconnectCallback((err, closeCode) => this._handleTransportDisconnect(err, closeCode));
789
975
  this._transport._onForegroundDrained = () => this._delivery.onForegroundPullsDrained();
976
+ this._transport._onRpcResponse = () => this._delivery.requestBackgroundPullRecovery();
790
977
  this._runtime = new ClientRuntime(this);
791
978
  this._identityRuntime = new IdentityRuntimeManager(this._runtime);
792
979
  this._peerDirectory = new PeerDirectory(this._runtime);
@@ -816,7 +1003,8 @@ export class AUNClient {
816
1003
  this._dispatcher.subscribeProtocol('_raw.message.read_changed', (data) => this._onRawReadChanged(data, 'p2p'));
817
1004
  // V2 P2P 推送通知:收到通知后自动走 message.v2.pull 拉取并解密
818
1005
  this._dispatcher.subscribeProtocol('_raw.peer.v2.message_received', (data) => {
819
- const ns = this._aid ? `p2p:${this._aid}` : '';
1006
+ const sender = isJsonObject(data) ? String(data.sender_aid ?? '') : '';
1007
+ const ns = this._p2pPeerPullEnabled ? (sender ? this._delivery.peerNamespace(sender) : '') : this._aid ? `p2p:${this._aid}` : '';
820
1008
  const hasInline = isJsonObject(data)
821
1009
  && isJsonObject(data.inline_message);
822
1010
  const inlineGeneration = hasInline ? this._delivery.captureInlineAckGeneration() : undefined;
@@ -895,6 +1083,21 @@ export class AUNClient {
895
1083
  this._collab = new CollabClient(this);
896
1084
  return this._collab;
897
1085
  }
1086
+ get debug() {
1087
+ if (!this._debug)
1088
+ this._debug = new DebugFacade(this, async (operation) => {
1089
+ const transport = this._transport;
1090
+ const previous = transport.getTraceMode();
1091
+ transport.setTraceMode('diag');
1092
+ try {
1093
+ return await operation();
1094
+ }
1095
+ finally {
1096
+ transport.setTraceMode(previous);
1097
+ }
1098
+ });
1099
+ return this._debug;
1100
+ }
898
1101
  /** Message facade 入口 */
899
1102
  get message() {
900
1103
  if (!this._message)
@@ -973,6 +1176,7 @@ export class AUNClient {
973
1176
  }
974
1177
  async bindOwneraid(ownerAid) { return this._getOwnershipWorkflow().bindOwneraid(ownerAid); }
975
1178
  async bindSubaid(subAid) { return this._getOwnershipWorkflow().bindSubaid(subAid); }
1179
+ async resetOwnershipTask() { return this._getOwnershipWorkflow().resetOwnershipTask(); }
976
1180
  async releaseSubaid(subAid) { return this._getOwnershipWorkflow().releaseSubaid(subAid); }
977
1181
  async transferSubaid(subAid, newOwnerAid) { return this._getOwnershipWorkflow().transferSubaid(subAid, newOwnerAid); }
978
1182
  async checkOwnershipConsensus(subAid) { return this._getOwnershipWorkflow().checkOwnershipConsensus(subAid); }
@@ -1159,7 +1363,7 @@ export class AUNClient {
1159
1363
  },
1160
1364
  auth: () => this._auth,
1161
1365
  tokenStore: () => this._tokenStore,
1162
- fetchPeerCert: (target, certFingerprint, signedAt) => this._fetchPeerCert(target, certFingerprint || undefined, undefined, signedAt),
1366
+ fetchPeerCert: (target, certFingerprint, signedAt) => this._fetchPeerCert(target, certFingerprint || undefined, undefined, signedAt, true),
1163
1367
  resolveEntityOwner: (target, type) => this._resolveEntityAgentMdOwner(target, type),
1164
1368
  fetchEntityIdentity: (target, type) => this._freshEntityCertificate(target, type),
1165
1369
  fetchEntitySigner: (target, fingerprint) => this._freshEntitySigner(target, fingerprint),
@@ -1172,14 +1376,18 @@ export class AUNClient {
1172
1376
  dnsNet,
1173
1377
  });
1174
1378
  this._transport.setProtocolMetaObserver((meta) => this._observeRpcMeta(meta));
1175
- this._transport.setServiceEventVerifier((event, payload) => verifyServiceEvent(event, payload, { targetAid: this._identity?.aid ?? '', resolveEventCertificate: (aid, fingerprint) => this._fetchServiceEventCertificate(aid, fingerprint), resolveCredentialService: this.resolveCredentialService }), () => this._identity?.aid ?? '', () => this._serviceEventIdentityVersion);
1379
+ this._transport.setServiceEventVerifier((event, payload) => this._verifyIncomingServiceEvent(event, payload), () => this._identity?.aid ?? '', () => this._serviceEventIdentityVersion);
1176
1380
  this._transport.setProtocolDisconnectCallback((err, closeCode) => this._handleTransportDisconnect(err, closeCode));
1177
1381
  this._transport._onForegroundDrained = () => this._delivery.onForegroundPullsDrained();
1382
+ this._transport._onRpcResponse = () => this._delivery.requestBackgroundPullRecovery();
1178
1383
  }
1179
1384
  loadIdentity(aid) {
1180
1385
  const previousAid = this._aid;
1181
1386
  this._identityRuntime.loadIdentity(aid);
1387
+ this._stopCredentialRenewal();
1182
1388
  this._serviceEventIdentityVersion++;
1389
+ this._serviceEventIdentityAbort.abort();
1390
+ this._serviceEventIdentityAbort = new AbortController();
1183
1391
  this._ensureReadStateIdentity();
1184
1392
  if (previousAid !== this._aid) {
1185
1393
  this._ownershipRecords.clear();
@@ -1287,6 +1495,7 @@ export class AUNClient {
1287
1495
  ? this._ensureReadStateIdentity() : null;
1288
1496
  try {
1289
1497
  const result = await this._rpcPipeline.call(method, params);
1498
+ await this._safeAsync(this._getCredentialRenewal().observe(method, params ?? {}, result));
1290
1499
  if (readIdentity !== null && readIdentity === this._ensureReadStateIdentity())
1291
1500
  this._observeReadStateResult(method, result, params ?? {});
1292
1501
  return result;
@@ -1316,6 +1525,13 @@ export class AUNClient {
1316
1525
  const groupId = String(params.group_id ?? params.groupId ?? params.group_aid ?? params.groupAid ?? fallback).trim();
1317
1526
  if (!groupId || !store)
1318
1527
  return false;
1528
+ const groupManager = store._agentMdManager ?? this._agentMdManager;
1529
+ const keystore = store._keystore;
1530
+ let pendingBind = keystore?.loadPendingGroupBind?.(`bind:${groupId}`);
1531
+ if (!pendingBind?.public_key_der_b64 || !pendingBind?.private_key_pem)
1532
+ pendingBind = keystore?.loadPendingGroupBind?.(groupId);
1533
+ if (groupManager.pendingUpload(fallback) && (!pendingBind?.public_key_der_b64 || !pendingBind?.private_key_pem))
1534
+ return false;
1319
1535
  try {
1320
1536
  await this._runGroupIdentityOperation(groupId, () => this.bindGroupAid({ group_id: groupId }, { aidStore: store }));
1321
1537
  }
@@ -1333,6 +1549,11 @@ export class AUNClient {
1333
1549
  const loaded = store.load(groupAid);
1334
1550
  if (!loaded.ok || !loaded.data?.aid?.isPrivateKeyValid())
1335
1551
  return false;
1552
+ const groupManager = store._agentMdManager ?? this._agentMdManager;
1553
+ if (groupManager.pendingUpload(groupAid))
1554
+ return false;
1555
+ if (String((groupManager._loadRecord?.(groupAid, true) ?? groupManager.loadRecord(groupAid))?.local_draft_content ?? ''))
1556
+ return false;
1336
1557
  await this._runGroupIdentityOperation(groupAid, () => this._uploadGroupAgentMd(store, groupAid, {}, {}));
1337
1558
  return true;
1338
1559
  }
@@ -1348,17 +1569,15 @@ export class AUNClient {
1348
1569
  const aid = this._aid;
1349
1570
  if (!aid || !this._currentAid)
1350
1571
  return false;
1351
- const observed = await this._getOwnershipFiles().readAgent(aid);
1352
- if (observed.exists)
1572
+ const observed = await this._agentMdManager.readAgentMdFresh(aid);
1573
+ if (observed.exists || String(this._agentMdManager._loadRecord?.(aid, true)?.local_draft_content ?? this._agentMdManager.loadRecord(aid)?.local_draft_content ?? '') || this._agentMdManager.pendingUpload(aid))
1353
1574
  return false;
1354
1575
  let agentMd = buildDefaultAgentMd(aid, { type: this.getType() });
1355
1576
  try {
1356
1577
  agentMd = this._agentMdManager.readContent(aid) || agentMd;
1357
1578
  }
1358
1579
  catch { }
1359
- const payload = updateOwnershipAgentMd(agentMd, {});
1360
- const raw = signOwnershipFile(this._currentAid, payload.endsWith('\n') || payload.endsWith('\r') ? payload : `${payload}\n`, 'agent');
1361
- await this._agentMdManager.submitOwnershipRaw(aid, raw, { if_none_match: '*' });
1580
+ await this._agentMdManager.upload(agentMd, { if_none_match: '*' });
1362
1581
  return true;
1363
1582
  }
1364
1583
  async _checkIdentityAdmissionAfterConnect() {
@@ -1412,13 +1631,18 @@ export class AUNClient {
1412
1631
  await this.completeGroupTransfer({ group_id: groupId, group_aid: groupAid }, { aidStore: store });
1413
1632
  return;
1414
1633
  }
1634
+ const groupManager = store._agentMdManager ?? this._agentMdManager;
1635
+ if (groupManager.pendingUpload(groupAid))
1636
+ return;
1637
+ if (String((groupManager._loadRecord?.(groupAid, true) ?? groupManager.loadRecord(groupAid))?.local_draft_content ?? '')) {
1638
+ await this._agentMdManager.check(groupAid);
1639
+ return;
1640
+ }
1415
1641
  const loaded = store.load(groupAid);
1416
1642
  if (!loaded.ok || !loaded.data?.aid?.isPrivateKeyValid()) {
1417
1643
  await this.bindGroupAid({ group_id: groupId, group_aid: groupAid }, { aidStore: store });
1418
1644
  return;
1419
1645
  }
1420
- if (await this._resumeGroupAgentMdUpload(store, groupAid))
1421
- return;
1422
1646
  const checked = await this._agentMdManager.check(groupAid);
1423
1647
  if (!checked.remote_found) {
1424
1648
  await this._uploadGroupAgentMd(store, groupAid, {}, {});
@@ -1449,12 +1673,45 @@ export class AUNClient {
1449
1673
  this._groupIdentityOperations.delete(key);
1450
1674
  }
1451
1675
  }
1676
+ async _resumeOrgRequest(method, params, target) {
1677
+ const binding = params.request_binding;
1678
+ const now = Math.trunc(Date.now() / 1000);
1679
+ if (!isJsonObject(binding) || now < Number(binding.exp))
1680
+ return params;
1681
+ const credentials = isJsonObject(params.credentials) ? params.credentials : {};
1682
+ const credential = ['member_credential', 'approval_credential', 'audit_credential'].map((field) => credentials[field]).find(isJsonObject) ?? params.credential_template;
1683
+ const cid = isJsonObject(credential) ? String(credential.cid) : '';
1684
+ validateRequestBinding(binding, method, target, params, cid, String(params.nonce), String(params.request_id), Number(binding.iat));
1685
+ let exp = now + 300;
1686
+ for (const material of [credential, ...Object.values(credentials).filter((value) => isJsonObject(value) && value.ver === 'aun-cap/1'), ...[params, params.previous_owner, params.new_owner].filter(isJsonObject).flatMap((value) => ['membership_binding', 'credential_template', 'registration_authorization'].map((field) => value[field]))]) {
1687
+ if (isJsonObject(material)) {
1688
+ if (!Number.isInteger(material.exp) || Number(material.exp) <= now)
1689
+ throw new ValidationError('Org recovery authorization material has expired');
1690
+ exp = Math.min(exp, Number(material.exp));
1691
+ }
1692
+ }
1693
+ const refreshed = { ...params, request_binding: buildRequestBinding(method, target, params, cid, String(params.nonce), String(params.request_id), now, exp) };
1694
+ delete refreshed.client_signature;
1695
+ if (isJsonObject(credentials.representation_proof)) {
1696
+ if (!this._currentAid || !isJsonObject(credential))
1697
+ throw new ValidationError('Org recovery representation signer is unavailable');
1698
+ refreshed.credentials = { ...credentials, representation_proof: createRepresentationProof(credential, refreshed, refreshed.request_binding, this._currentAid, refreshed.proof_chain) };
1699
+ }
1700
+ return refreshed;
1701
+ }
1452
1702
  async createOrg(params = {}, options = {}) {
1453
1703
  const store = options.aidStore;
1454
1704
  const owner = this._currentAid;
1455
1705
  const orgAid = String(params.org_id ?? '').trim();
1456
1706
  if (!store || !owner?.isPrivateKeyValid() || !orgAid)
1457
1707
  throw new ValidationError('createOrg requires aidStore, org_id and current owner identity');
1708
+ const tokens = store._tokenStore;
1709
+ const inputHash = crypto.createHash('sha256').update(canonicalJson(params)).digest('hex');
1710
+ let checkpoint = (typeof tokens._loadMetadataStrict === 'function' ? tokens._loadMetadataStrict(orgAid) : tokens.loadMetadata(orgAid))?.org_identity_creation;
1711
+ if (checkpoint != null && !isJsonObject(checkpoint))
1712
+ throw ownershipFailure('persistence_unavailable', 'Org creation checkpoint is invalid');
1713
+ if (checkpoint && (checkpoint.owner_aid !== owner.aid || checkpoint.input_hash !== inputHash))
1714
+ throw ownershipFailure('conflict', 'pending Org creation context changed');
1458
1715
  this._aidStore = store;
1459
1716
  let identity = store.load(orgAid);
1460
1717
  if (!identity.ok) {
@@ -1466,18 +1723,13 @@ export class AUNClient {
1466
1723
  if (!identity.ok)
1467
1724
  throw ownershipFailure('unavailable', identity.error.message, true);
1468
1725
  const target = options.serviceAid ?? `org.${orgAid.slice(orgAid.indexOf('.') + 1)}`;
1469
- const tokens = store._tokenStore;
1470
- const inputHash = crypto.createHash('sha256').update(canonicalJson(params)).digest('hex');
1471
- let checkpoint = tokens.loadMetadata(orgAid)?.org_identity_creation;
1472
- if (checkpoint && (checkpoint.owner_aid !== owner.aid || checkpoint.input_hash !== inputHash))
1473
- throw ownershipFailure('conflict', 'pending Org creation context changed');
1474
1726
  if (!checkpoint) {
1475
1727
  const prepared = this.org.prepareRequest('org.lifecycle.create', { ...params, owner_aid: owner.aid }, target, owner, { orgSigner: identity.data.aid });
1476
1728
  checkpoint = { owner_aid: owner.aid, input_hash: inputHash, source_params: params, prepared, phase: 'create' };
1477
1729
  tokens.saveMetadata(orgAid, { org_identity_creation: checkpoint });
1478
1730
  }
1479
1731
  if (checkpoint.phase === 'create') {
1480
- const result = await this.call('org.lifecycle.create', checkpoint.prepared);
1732
+ const result = await this.call('org.lifecycle.create', await this._resumeOrgRequest('org.lifecycle.create', checkpoint.prepared, target));
1481
1733
  checkpoint = { ...checkpoint, result, phase: 'upload' };
1482
1734
  tokens.saveMetadata(orgAid, { org_identity_creation: checkpoint });
1483
1735
  }
@@ -1491,9 +1743,11 @@ export class AUNClient {
1491
1743
  if (typeof keyStore?.listIdentities !== 'function' || typeof tokens?.loadMetadata !== 'function')
1492
1744
  return;
1493
1745
  for (const aid of keyStore.listIdentities()) {
1494
- const metadata = tokens.loadMetadata(aid);
1746
+ const metadata = typeof tokens._loadMetadataStrict === 'function' ? tokens._loadMetadataStrict(aid) : tokens.loadMetadata(aid);
1495
1747
  const creation = metadata?.org_identity_creation;
1496
1748
  const transfer = metadata?.org_identity_transfer;
1749
+ if (creation != null && !isJsonObject(creation) || transfer != null && !isJsonObject(transfer))
1750
+ throw ownershipFailure('persistence_unavailable', 'Org identity checkpoint is invalid');
1497
1751
  try {
1498
1752
  if (creation?.owner_aid === this._aid) {
1499
1753
  await this.createOrg(creation.source_params, { aidStore: store });
@@ -1503,18 +1757,46 @@ export class AUNClient {
1503
1757
  await this.completeOrgTransfer(transfer.source_params, { aidStore: store });
1504
1758
  continue;
1505
1759
  }
1506
- const pending = store._agentMdManager.pendingUpload(aid);
1507
- if (!pending)
1508
- continue;
1509
- const fields = validateAgentMdDocument(pending.content, { expectedAid: aid }).frontmatter;
1510
- if (fields.type === 'org' && fields.owner === this._aid)
1511
- await this._resumeGroupAgentMdUpload(store, aid);
1512
1760
  }
1513
1761
  catch (error) {
1514
1762
  this._clientLog.warn(`post-connect Org identity recovery failed: aid=${aid} err=${formatCaughtError(error)}`);
1515
1763
  }
1516
1764
  }
1517
1765
  }
1766
+ async _verifyOrgTransferMaterials(params, ownerAid) {
1767
+ const orgAid = String(params.org_id);
1768
+ const previous = params.previous_owner;
1769
+ const next = params.new_owner;
1770
+ const binding = previous.membership_binding;
1771
+ const nextBinding = next.membership_binding;
1772
+ const credential = previous.credential_template;
1773
+ const ext = isJsonObject(credential.ext) ? credential.ext : {};
1774
+ const capability = Array.isArray(credential.cap) && credential.cap.length === 1 && isJsonObject(credential.cap[0]) ? credential.cap[0] : {};
1775
+ const now = Math.trunc(Date.now() / 1000);
1776
+ if (binding.org_id !== orgAid || nextBinding.org_id !== orgAid || binding.member_aid === ownerAid || nextBinding.member_aid !== ownerAid
1777
+ || binding.role !== 'member' || nextBinding.role !== 'owner' || digest(previous.terms) !== binding.terms_hash || digest(next.terms) !== nextBinding.terms_hash
1778
+ || !Number.isSafeInteger(nextBinding.iat) || !Number.isSafeInteger(nextBinding.exp) || Number(nextBinding.iat) > now || Number(nextBinding.exp) <= now
1779
+ || credential.iss !== orgAid || credential.sub !== binding.member_aid || credential.parent != null || credential.delegate !== false
1780
+ || ext.credential_type !== 'member' || ext.bound_to_membership !== true || ext.org_id !== orgAid || ext.membership_id !== binding.membership_id
1781
+ || ext.binding_version !== binding.binding_version || ext.membership_terms_hash !== binding.terms_hash
1782
+ || capability.ns !== 'org' || capability.action !== 'role.member' || capability.resource !== orgAid)
1783
+ throw ownershipFailure('invalid', 'Org transfer membership materials do not match');
1784
+ validateMembershipBinding(binding);
1785
+ const certificates = new Map();
1786
+ for (const [aid, signature] of [[binding.member_aid, binding.member_signature], [orgAid, binding.org_signature], [orgAid, credential.signature]]) {
1787
+ if (!isJsonObject(signature))
1788
+ throw ownershipFailure('invalid', 'Org transfer membership materials do not match');
1789
+ const fingerprint = String(signature.cert_fingerprint);
1790
+ const certificate = await this._auth.resolveOwnershipCertificate(await this._resolveOwnershipCertificateGateway(String(aid)), String(aid), fingerprint);
1791
+ certificates.set(fingerprint, certificate.cert_pem);
1792
+ }
1793
+ const resolve = (fingerprint) => certificates.get(fingerprint);
1794
+ const checkedAt = Math.trunc(Date.now() / 1000);
1795
+ verifyMembershipBinding(binding, resolve, checkedAt, orgAid);
1796
+ verifyCredentialSignature(credential, resolve, checkedAt, orgAid);
1797
+ if (Number(nextBinding.exp) <= checkedAt)
1798
+ throw ownershipFailure('invalid', 'Org transfer membership materials do not match');
1799
+ }
1518
1800
  async completeOrgTransfer(params = {}, options = {}) {
1519
1801
  const store = options.aidStore;
1520
1802
  const owner = this._currentAid;
@@ -1522,20 +1804,33 @@ export class AUNClient {
1522
1804
  const transferId = String(params.transfer_id ?? '').trim();
1523
1805
  if (!store || !owner?.isPrivateKeyValid() || !orgAid || !transferId)
1524
1806
  throw new ValidationError('completeOrgTransfer requires aidStore, org_id, transfer_id and current accepting owner');
1807
+ for (const field of ['previous_owner', 'new_owner']) {
1808
+ const material = params[field];
1809
+ if (!isJsonObject(material) || !isJsonObject(material.membership_binding) || !isJsonObject(material.terms) || field === 'previous_owner' && !isJsonObject(material.credential_template))
1810
+ throw ownershipFailure('invalid', 'Org transfer requires both replacement membership bindings, terms and previous-owner credential');
1811
+ }
1525
1812
  this._aidStore = store;
1526
1813
  const localStore = store._keystore;
1527
1814
  const tokenStore = store._tokenStore;
1528
1815
  const pendingKey = `org-transfer:${orgAid}:${transferId}`;
1529
1816
  const inputHash = crypto.createHash('sha256').update(canonicalJson(params)).digest('hex');
1817
+ let pending = (typeof tokenStore._loadMetadataStrict === 'function' ? tokenStore._loadMetadataStrict(orgAid) : tokenStore.loadMetadata(orgAid))?.org_identity_transfer;
1818
+ if (pending != null && !isJsonObject(pending))
1819
+ throw ownershipFailure('persistence_unavailable', 'Org transfer checkpoint is invalid');
1820
+ if (pending && (pending.transfer_id !== transferId || pending.owner_aid !== owner.aid || pending.input_hash !== inputHash))
1821
+ throw ownershipFailure('conflict', 'pending Org transfer context changed');
1822
+ if (!pending)
1823
+ await this._verifyOrgTransferMaterials(params, owner.aid);
1530
1824
  let keyPair = localStore.loadPendingGroupBind(pendingKey);
1531
1825
  if (!keyPair) {
1826
+ if (pending)
1827
+ throw ownershipFailure('persistence_unavailable', 'Org transfer private key checkpoint is unavailable');
1532
1828
  keyPair = new CryptoProvider().generateIdentity();
1533
1829
  localStore.savePendingGroupBind(pendingKey, keyPair);
1534
1830
  }
1535
1831
  const target = options.serviceAid ?? `org.${orgAid.slice(orgAid.indexOf('.') + 1)}`;
1536
1832
  const publicKey = crypto.createPublicKey({ key: Buffer.from(keyPair.public_key_der_b64, 'base64'), format: 'der', type: 'spki' }).export({ format: 'pem', type: 'spki' }).toString();
1537
- let pending = tokenStore.loadMetadata(orgAid)?.org_identity_transfer;
1538
- if (pending && (pending.transfer_id !== transferId || pending.owner_aid !== owner.aid || pending.public_key !== publicKey || pending.input_hash !== inputHash))
1833
+ if (pending && pending.public_key !== publicKey)
1539
1834
  throw ownershipFailure('conflict', 'pending Org transfer context changed');
1540
1835
  if (!pending) {
1541
1836
  const rekey = { ...params, stage: 'rekey', public_key: publicKey, curve: 'P-256' };
@@ -1546,7 +1841,7 @@ export class AUNClient {
1546
1841
  tokenStore.saveMetadata(orgAid, { org_identity_transfer: pending });
1547
1842
  }
1548
1843
  if (pending.phase === 'rekey') {
1549
- const result = await this.call('org.lifecycle.transferAccept', pending.rekey_params);
1844
+ const result = await this.call('org.lifecycle.transferAccept', await this._resumeOrgRequest('org.lifecycle.transferAccept', pending.rekey_params, target));
1550
1845
  pending = { ...pending, phase: 'finalize', rekey_result: result };
1551
1846
  tokenStore.saveMetadata(orgAid, { org_identity_transfer: pending });
1552
1847
  }
@@ -1582,10 +1877,10 @@ export class AUNClient {
1582
1877
  pending = { ...pending, finalize_params: this.org.prepareRequest('org.lifecycle.transferAccept', final, target, owner, { orgSigner: orgIdentity }) };
1583
1878
  tokenStore.saveMetadata(orgAid, { org_identity_transfer: pending });
1584
1879
  }
1585
- const result = await this.call('org.lifecycle.transferAccept', pending.finalize_params);
1880
+ const result = await this.call('org.lifecycle.transferAccept', await this._resumeOrgRequest('org.lifecycle.transferAccept', pending.finalize_params, target));
1586
1881
  await this._uploadOrgAgentMd(store, orgAid, pending.source_params);
1587
- localStore.clearPendingGroupBind(pendingKey);
1588
1882
  tokenStore.saveMetadata(orgAid, { org_identity_transfer: null });
1883
+ localStore.clearPendingGroupBind(pendingKey);
1589
1884
  return result;
1590
1885
  }
1591
1886
  async dissolveOrg(params = {}, options = {}) {
@@ -2443,7 +2738,8 @@ export class AUNClient {
2443
2738
  return;
2444
2739
  this._readStateCache.set(key, { method, params: query, state: { ...eventData } });
2445
2740
  }
2446
- this._dispatcher.enqueue(scope === 'group' ? 'group.read_changed' : 'message.read_changed', eventData);
2741
+ const identity = this._ensureReadStateIdentity();
2742
+ this._dispatcher.enqueue(scope === 'group' ? 'group.read_changed' : 'message.read_changed', eventData, () => identity === this._ensureReadStateIdentity());
2447
2743
  }
2448
2744
  async _reconcileReadStates() {
2449
2745
  const identity = this._ensureReadStateIdentity();
@@ -2475,7 +2771,7 @@ export class AUNClient {
2475
2771
  owner_aid: this._aid ?? '',
2476
2772
  states: [...keys].map((key) => ({ ...this._readStateCache.get(key).state })),
2477
2773
  errors,
2478
- });
2774
+ }, () => identity === this._ensureReadStateIdentity());
2479
2775
  }
2480
2776
  async _callRawV2Rpc(method, params, pullGateKey = '') {
2481
2777
  const p = { ...(params ?? {}) };
@@ -2640,7 +2936,10 @@ export class AUNClient {
2640
2936
  if (event === 'message.received') {
2641
2937
  return this._handleOwnershipMessage(payload).then((handled) => {
2642
2938
  if (!handled)
2643
- return this._delivery.publishAppEvent(event, payload, source, deliveryContext);
2939
+ return this._handleCredentialRenewal(payload).then((renewed) => {
2940
+ if (!renewed)
2941
+ return this._delivery.publishAppEvent(event, payload, source, deliveryContext);
2942
+ });
2644
2943
  return undefined;
2645
2944
  });
2646
2945
  }
@@ -3015,8 +3314,13 @@ export class AUNClient {
3015
3314
  try {
3016
3315
  if (opts.signedAt === undefined)
3017
3316
  await this._auth.verifyPeerCertificate(peerGatewayUrl, certPem, aid);
3018
- else
3317
+ else if (opts.agentMdRead) {
3318
+ validateAgentMdCertificate(certPem, aid, opts.signedAt, { requireCurrentValidity: false, readCompatibility: true });
3319
+ await this._auth._verifyAuthCertChain(peerGatewayUrl, new crypto.X509Certificate(certPem), aid, opts.signedAt);
3320
+ }
3321
+ else {
3019
3322
  await this._auth.verifyPeerCertificate(peerGatewayUrl, certPem, aid, opts.signedAt);
3323
+ }
3020
3324
  }
3021
3325
  catch (exc) {
3022
3326
  if (cachedBootstrapChain) {
@@ -3030,7 +3334,7 @@ export class AUNClient {
3030
3334
  validatedAt: nowSec,
3031
3335
  refreshAfter: nowSec + PEER_CERT_CACHE_TTL,
3032
3336
  };
3033
- const cacheKey = AUNClient._certCacheKey(aid, certFingerprint) + (opts.signedAt === undefined ? '' : `:signed:${opts.signedAt}`);
3337
+ const cacheKey = AUNClient._certCacheKey(aid, certFingerprint) + (opts.signedAt === undefined ? '' : `:signed:${opts.signedAt}${opts.agentMdRead ? ':agentmd' : ''}`);
3034
3338
  this._certCache.set(cacheKey, entry);
3035
3339
  if (!certFingerprint && opts.signedAt === undefined) {
3036
3340
  const bareKey = AUNClient._certCacheKey(aid);
@@ -3040,7 +3344,7 @@ export class AUNClient {
3040
3344
  }
3041
3345
  try {
3042
3346
  // peer 证书只存版本目录,不覆盖 cert.pem
3043
- this._tokenStore.saveCert(aid, certPem, certFingerprint, { makeActive: false });
3347
+ this._tokenStore.saveCert(aid, certPem, opts.agentMdRead && opts.signedAt !== undefined ? `sha256:${x509Cert.fingerprint256.replace(/:/g, '').toLowerCase()}` : certFingerprint, { makeActive: false });
3044
3348
  }
3045
3349
  catch (exc) {
3046
3350
  this._clientLog.error(`failed to write cert to keystore (aid=${aid}, fp=${certFingerprint ?? ''}): ${formatCaughtError(exc)}`, exc instanceof Error ? exc : undefined);
@@ -3055,32 +3359,47 @@ export class AUNClient {
3055
3359
  return certificate;
3056
3360
  }
3057
3361
  /** 获取对方证书(带缓存 + 完整 PKI 验证),跨域时自动路由到 peer 所在域。 */
3058
- async _fetchPeerCert(aid, certFingerprint, timeoutMs = 30_000, signedAt) {
3362
+ async _fetchPeerCert(aid, certFingerprint, timeoutMs = 30_000, signedAt, agentMdRead = false) {
3059
3363
  const tStart = Date.now();
3060
3364
  this._clientLog.debug(`_fetchPeerCert enter: aid=${aid}, fp=${certFingerprint ?? ''}`);
3061
3365
  try {
3062
- const cacheKey = AUNClient._certCacheKey(aid, certFingerprint) + (signedAt === undefined ? '' : `:signed:${signedAt}`);
3366
+ const cacheKey = AUNClient._certCacheKey(aid, certFingerprint) + (signedAt === undefined ? '' : `:signed:${signedAt}${agentMdRead ? ':agentmd' : ''}`);
3063
3367
  const cached = this._certCache.get(cacheKey);
3064
3368
  const now = Date.now() / 1000;
3065
3369
  if (cached && now < cached.refreshAfter) {
3066
3370
  this._clientLog.debug(`_fetchPeerCert exit: elapsed=${Date.now() - tStart}ms aid=${aid} (cache_hit)`);
3067
3371
  return cached.certPem;
3068
3372
  }
3069
- const gatewayUrl = this._gatewayUrl;
3070
- if (!gatewayUrl) {
3071
- throw new ValidationError('gateway url unavailable for e2ee cert fetch');
3373
+ const inFlightKey = `${cacheKey}:timeout:${timeoutMs}`;
3374
+ const pending = this._certFetchInFlight.get(inFlightKey);
3375
+ if (pending)
3376
+ return await pending;
3377
+ const request = (async () => {
3378
+ const gatewayUrl = this._gatewayUrl;
3379
+ if (!gatewayUrl) {
3380
+ throw new ValidationError('gateway url unavailable for e2ee cert fetch');
3381
+ }
3382
+ const peerGatewayUrl = AUNClient._resolvePeerGatewayUrl(gatewayUrl, aid);
3383
+ const certPem = await _httpGetText(AUNClient._buildCertUrl(peerGatewayUrl, aid, certFingerprint), this._configModel.verifySsl, timeoutMs);
3384
+ const validated = await this._validateAndCachePeerCert({
3385
+ aid,
3386
+ certPem,
3387
+ certFingerprint,
3388
+ source: 'fetch',
3389
+ signedAt,
3390
+ agentMdRead,
3391
+ });
3392
+ this._clientLog.debug(`_fetchPeerCert exit: elapsed=${Date.now() - tStart}ms aid=${aid} (fetched)`);
3393
+ return validated;
3394
+ })();
3395
+ this._certFetchInFlight.set(inFlightKey, request);
3396
+ try {
3397
+ return await request;
3398
+ }
3399
+ finally {
3400
+ if (this._certFetchInFlight.get(inFlightKey) === request)
3401
+ this._certFetchInFlight.delete(inFlightKey);
3072
3402
  }
3073
- const peerGatewayUrl = AUNClient._resolvePeerGatewayUrl(gatewayUrl, aid);
3074
- const certPem = await _httpGetText(AUNClient._buildCertUrl(peerGatewayUrl, aid, certFingerprint), this._configModel.verifySsl, timeoutMs);
3075
- const validated = await this._validateAndCachePeerCert({
3076
- aid,
3077
- certPem,
3078
- certFingerprint,
3079
- source: 'fetch',
3080
- signedAt,
3081
- });
3082
- this._clientLog.debug(`_fetchPeerCert exit: elapsed=${Date.now() - tStart}ms aid=${aid} (fetched)`);
3083
- return validated;
3084
3403
  }
3085
3404
  catch (err) {
3086
3405
  this._clientLog.debug(`_fetchPeerCert exit (error): elapsed=${Date.now() - tStart}ms aid=${aid} err=${err instanceof Error ? err.message : String(err)}`);
@@ -3656,7 +3975,7 @@ export class AUNClient {
3656
3975
  }
3657
3976
  /** 后台 Promise 统一兜底,避免事件回调里的异步异常变成未处理拒绝。 */
3658
3977
  _safeAsync(promise) {
3659
- promise.catch((exc) => {
3978
+ return promise.then(() => undefined).catch((exc) => {
3660
3979
  this._clientLog.warn(`background task exception: ${formatCaughtError(exc)}`);
3661
3980
  });
3662
3981
  }
@@ -3851,7 +4170,7 @@ export class AUNClient {
3851
4170
  return await this._v2E2EE.ackGroupV2(groupId, upToSeq, groupAid);
3852
4171
  }
3853
4172
  /** 解密单条 V2 pull 消息。缺 sender IK 时先入 pending,后台补齐后重试。 */
3854
- async _decryptV2Message(msg, allowPending = true, emitUndecryptable = true, rotateKeys = true, observeAgentMd = true, deferStatus, deferKeyFetch = false, source = 'pull') {
4173
+ async _decryptV2Message(msg, allowPending = true, emitUndecryptable = true, rotateKeys = true, observeAgentMd = true, deferStatus, deferKeyFetch = false, source = 'pull', deferredEffects) {
3855
4174
  const session = this._v2Session;
3856
4175
  if (!session)
3857
4176
  return null;
@@ -3867,8 +4186,12 @@ export class AUNClient {
3867
4186
  return null;
3868
4187
  }
3869
4188
  const e2eeMeta = this._v2E2eeMeta(envelope);
3870
- if (observeAgentMd)
3871
- this._agentMdManager.observeEnvelope(envelope);
4189
+ if (observeAgentMd) {
4190
+ if (deferredEffects)
4191
+ deferredEffects.push(() => this._agentMdManager.observeEnvelope(envelope));
4192
+ else
4193
+ this._agentMdManager.observeEnvelope(envelope);
4194
+ }
3872
4195
  let spkId = '';
3873
4196
  let recipientKeySource = '';
3874
4197
  if (isJsonObject(envelope.recipient)) {
@@ -3947,8 +4270,12 @@ export class AUNClient {
3947
4270
  };
3948
4271
  this._attachV2EnvelopeMetadata(event, e2eeMeta);
3949
4272
  this._logMessageDebug('decrypt-fail', 'v2.decrypt', undecryptableEvent, event);
3950
- if (emitUndecryptable)
3951
- this._dispatcher.enqueue(undecryptableEvent, event);
4273
+ if (emitUndecryptable) {
4274
+ if (deferredEffects)
4275
+ deferredEffects.push(() => { this._dispatcher.enqueue(undecryptableEvent, event); });
4276
+ else
4277
+ this._dispatcher.enqueue(undecryptableEvent, event);
4278
+ }
3952
4279
  return null;
3953
4280
  }
3954
4281
  this._clientLog.debug(`V2 decrypt key lookup ok: seq=${String(msg.seq ?? '')}, group=${groupIdForKeys || '<p2p>'}, ik_len=${ikPriv.byteLength}, spk_len=${spkPriv?.byteLength ?? 0}`);
@@ -3986,8 +4313,12 @@ export class AUNClient {
3986
4313
  };
3987
4314
  this._attachV2EnvelopeMetadata(event, e2eeMeta);
3988
4315
  this._logMessageDebug('decrypt-fail', 'v2.decrypt', undecryptableEvent, event);
3989
- if (emitUndecryptable)
3990
- this._dispatcher.enqueue(undecryptableEvent, event);
4316
+ if (emitUndecryptable) {
4317
+ if (deferredEffects)
4318
+ deferredEffects.push(() => { this._dispatcher.enqueue(undecryptableEvent, event); });
4319
+ else
4320
+ this._dispatcher.enqueue(undecryptableEvent, event);
4321
+ }
3991
4322
  return null;
3992
4323
  }
3993
4324
  let plaintext;
@@ -4015,16 +4346,24 @@ export class AUNClient {
4015
4346
  };
4016
4347
  this._attachV2EnvelopeMetadata(event, e2eeMeta);
4017
4348
  this._logMessageDebug('decrypt-fail', 'v2.decrypt', undecryptableEvent, event);
4018
- if (emitUndecryptable)
4019
- this._dispatcher.enqueue(undecryptableEvent, event);
4349
+ if (emitUndecryptable) {
4350
+ if (deferredEffects)
4351
+ deferredEffects.push(() => { this._dispatcher.enqueue(undecryptableEvent, event); });
4352
+ else
4353
+ this._dispatcher.enqueue(undecryptableEvent, event);
4354
+ }
4020
4355
  return null;
4021
4356
  }
4022
4357
  if (plaintext === null) {
4023
4358
  this._clientLog.debug(`V2 decrypt returned null plaintext: seq=${String(msg.seq ?? '')}, group=${groupIdForKeys || '<p2p>'}`);
4024
4359
  return null;
4025
4360
  }
4026
- if (rotateKeys)
4027
- this._scheduleV2KeyRotationForMessage(msg, envelope);
4361
+ if (rotateKeys) {
4362
+ if (deferredEffects)
4363
+ deferredEffects.push(() => this._scheduleV2KeyRotationForMessage(msg, envelope));
4364
+ else
4365
+ this._scheduleV2KeyRotationForMessage(msg, envelope);
4366
+ }
4028
4367
  const e2ee = this._v2E2eeMeta(envelope);
4029
4368
  let result = {
4030
4369
  message_id: String(msg.message_id ?? ''),
@@ -4120,6 +4459,8 @@ export class AUNClient {
4120
4459
  if (protectedHeaders && Object.keys(protectedHeaders).length > 0) {
4121
4460
  meta.protected_headers = protectedHeaders;
4122
4461
  }
4462
+ if (typeof envelope.need_render === 'boolean')
4463
+ meta.need_render = envelope.need_render;
4123
4464
  const payloadType = String(envelope.payload_type ?? protectedHeaders?.payload_type ?? '').trim();
4124
4465
  if (payloadType) {
4125
4466
  meta.payload_type = payloadType;
@@ -4135,6 +4476,8 @@ export class AUNClient {
4135
4476
  return meta;
4136
4477
  }
4137
4478
  _attachV2EnvelopeMetadata(message, meta) {
4479
+ if (typeof meta.need_render === 'boolean')
4480
+ message.need_render = meta.need_render;
4138
4481
  const payloadType = typeof meta.payload_type === 'string' ? meta.payload_type.trim() : '';
4139
4482
  if (payloadType)
4140
4483
  message.payload_type = payloadType;
@@ -4492,6 +4835,7 @@ export class AUNClient {
4492
4835
  // ── 内部:后台任务 ────────────────────────────────────────
4493
4836
  /** 启动所有后台任务 */
4494
4837
  _startBackgroundTasks() {
4838
+ this._startCredentialRenewal();
4495
4839
  // WebSocket Ping/Pong 由 Gateway 与传输层处理;SDK 不发送 RPC 心跳。
4496
4840
  // 短连接生命周期短,不需要 token 刷新。
4497
4841
  if (this._sessionOptions.connection_kind !== 'short') {
@@ -4501,6 +4845,7 @@ export class AUNClient {
4501
4845
  }
4502
4846
  /** 停止所有后台任务 */
4503
4847
  _stopBackgroundTasks() {
4848
+ this._stopCredentialRenewal();
4504
4849
  if (this._heartbeatTimer !== null) {
4505
4850
  clearInterval(this._heartbeatTimer);
4506
4851
  this._heartbeatTimer = null;