@agentunion/fastaun 0.5.20 → 0.5.22

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 (128) hide show
  1. package/CHANGELOG.md +104 -0
  2. package/_packed_docs/CHANGELOG.md +104 -0
  3. package/_packed_docs/INDEX.md +59 -34
  4. package/_packed_docs/KITE_DOCS_GUIDE.md +59 -52
  5. 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
  6. 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
  7. 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
  8. 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
  9. 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 +307 -67
  10. package/_packed_docs/aun/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md +41 -15
  11. package/_packed_docs/protocol/06-/346/234/215/345/212/241/345/215/217/350/256/256.md +1433 -1392
  12. package/_packed_docs/protocol/08-AUN-E2EE.md +17 -12
  13. package/_packed_docs/protocol/11-Storage-/345/255/220/345/215/217/350/256/256.md +30 -8
  14. package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/211.md +2 -0
  15. package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2112.1.md +33 -22
  16. 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
  17. 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
  18. package/_packed_docs/sdk/0.5.22-SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +212 -0
  19. package/_packed_docs/sdk/0.5.22-SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +199 -0
  20. package/_packed_docs/sdk/01-/345/277/253/351/200/237/345/274/200/345/247/213.md +1 -1
  21. package/_packed_docs/sdk/02-WebSocket/345/215/217/350/256/256.md +5 -1
  22. package/_packed_docs/sdk/03-/346/240/270/345/277/203/346/246/202/345/277/265.md +64 -64
  23. package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +4 -1
  24. package/_packed_docs/sdk/05-E2EE/345/212/240/345/257/206/351/200/232/344/277/241.md +42 -39
  25. package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +163 -14
  26. package/_packed_docs/sdk/08-/346/234/200/344/275/263/345/256/236/350/267/265.md +48 -47
  27. package/_packed_docs/sdk/09-group-rpc-manual.md +15 -2
  28. package/_packed_docs/sdk/09-message-rpc-manual.md +250 -160
  29. package/_packed_docs/sdk/09-storage-rpc-manual.md +64 -7
  30. package/_packed_docs/sdk/09-/345/207/255/350/257/201/346/234/215/345/212/241RPC/346/211/213/345/206/214.md +73 -0
  31. package/_packed_docs/sdk/09-/347/273/204/347/273/207/346/234/215/345/212/241RPC/346/211/213/345/206/214.md +90 -0
  32. package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +33 -25
  33. package/_packed_docs/sdk/INDEX.md +67 -33
  34. package/_packed_docs/sdk/Ownership-SDK/347/274/226/346/216/222/346/214/207/345/215/2272.1.md +74 -57
  35. 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 +312 -300
  36. 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 +322 -382
  37. 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 -708
  38. package/_packed_docs/sdk/Ownership2.1/350/257/246/347/273/206/350/256/276/350/256/241.md +28 -20
  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 +190 -97
  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 +158 -127
  41. package/dist/agent-md-schema.js +6 -4
  42. package/dist/agent-md-schema.js.map +1 -1
  43. package/dist/agent-md.d.ts +22 -2
  44. package/dist/agent-md.js +92 -68
  45. package/dist/agent-md.js.map +1 -1
  46. package/dist/aid-store.d.ts +13 -0
  47. package/dist/aid-store.js +50 -15
  48. package/dist/aid-store.js.map +1 -1
  49. package/dist/auth.d.ts +1 -0
  50. package/dist/auth.js +22 -2
  51. package/dist/auth.js.map +1 -1
  52. package/dist/cert-utils.d.ts +1 -0
  53. package/dist/cert-utils.js +13 -0
  54. package/dist/cert-utils.js.map +1 -1
  55. package/dist/client/delivery.d.ts +87 -1
  56. package/dist/client/delivery.js +1387 -139
  57. package/dist/client/delivery.js.map +1 -1
  58. package/dist/client/group-state.js +3 -0
  59. package/dist/client/group-state.js.map +1 -1
  60. package/dist/client/rpc-pipeline.d.ts +4 -1
  61. package/dist/client/rpc-pipeline.js +149 -37
  62. package/dist/client/rpc-pipeline.js.map +1 -1
  63. package/dist/client/v2-e2ee.d.ts +12 -2
  64. package/dist/client/v2-e2ee.js +831 -304
  65. package/dist/client/v2-e2ee.js.map +1 -1
  66. package/dist/client.d.ts +26 -2
  67. package/dist/client.js +412 -68
  68. package/dist/client.js.map +1 -1
  69. package/dist/config.d.ts +4 -0
  70. package/dist/config.js +1 -0
  71. package/dist/config.js.map +1 -1
  72. package/dist/events.d.ts +18 -1
  73. package/dist/events.js +148 -9
  74. package/dist/events.js.map +1 -1
  75. package/dist/facades.d.ts +43 -1
  76. package/dist/facades.js +363 -7
  77. package/dist/facades.js.map +1 -1
  78. package/dist/group-fs.d.ts +1 -0
  79. package/dist/group-fs.js +37 -19
  80. package/dist/group-fs.js.map +1 -1
  81. package/dist/index.d.ts +5 -3
  82. package/dist/index.js +1 -1
  83. package/dist/index.js.map +1 -1
  84. package/dist/keystore/aid-db.d.ts +6 -1
  85. package/dist/keystore/aid-db.js +184 -42
  86. package/dist/keystore/aid-db.js.map +1 -1
  87. package/dist/keystore/index.d.ts +14 -0
  88. package/dist/keystore/local-token-store.d.ts +9 -1
  89. package/dist/keystore/local-token-store.js +79 -14
  90. package/dist/keystore/local-token-store.js.map +1 -1
  91. package/dist/org-protocol.d.ts +46 -0
  92. package/dist/org-protocol.js +802 -12
  93. package/dist/org-protocol.js.map +1 -1
  94. package/dist/ownership-v21.d.ts +36 -5
  95. package/dist/ownership-v21.js +168 -52
  96. package/dist/ownership-v21.js.map +1 -1
  97. package/dist/ownership-workflow.d.ts +24 -2
  98. package/dist/ownership-workflow.js +682 -41
  99. package/dist/ownership-workflow.js.map +1 -1
  100. package/dist/secret-store/file-store.js +84 -0
  101. package/dist/secret-store/file-store.js.map +1 -1
  102. package/dist/seq-tracker.d.ts +1 -0
  103. package/dist/seq-tracker.js +2 -2
  104. package/dist/seq-tracker.js.map +1 -1
  105. package/dist/storage/lowlevel.d.ts +6 -0
  106. package/dist/storage/lowlevel.js +24 -7
  107. package/dist/storage/lowlevel.js.map +1 -1
  108. package/dist/storage/read-cache.d.ts +70 -0
  109. package/dist/storage/read-cache.js +625 -0
  110. package/dist/storage/read-cache.js.map +1 -0
  111. package/dist/storage/vfs.d.ts +2 -0
  112. package/dist/storage/vfs.js +50 -15
  113. package/dist/storage/vfs.js.map +1 -1
  114. package/dist/tools/cross-sdk-agent.d.ts +86 -3
  115. package/dist/tools/cross-sdk-agent.js +758 -46
  116. package/dist/tools/cross-sdk-agent.js.map +1 -1
  117. package/dist/transport.d.ts +3 -0
  118. package/dist/transport.js +104 -46
  119. package/dist/transport.js.map +1 -1
  120. package/dist/types.d.ts +1 -0
  121. package/dist/v2/e2ee/encrypt-group.js +1 -0
  122. package/dist/v2/e2ee/encrypt-group.js.map +1 -1
  123. package/dist/v2/e2ee/encrypt-p2p.js +1 -0
  124. package/dist/v2/e2ee/encrypt-p2p.js.map +1 -1
  125. package/dist/v2/e2ee/types.d.ts +1 -0
  126. package/dist/version.d.ts +1 -1
  127. package/dist/version.js +1 -1
  128. 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';
@@ -48,9 +48,9 @@ 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
50
  import { validateAgentMdCertificate, validateAgentMdDocument } from './agent-md-schema.js';
51
- import { OwnershipFiles, OwnershipStorageAdapter, ownershipFailure, signOwnershipFile } from './ownership-v21.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
  }
@@ -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';
@@ -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, stage) => this._agentMdManager.submitOwnershipRaw(subAid, raw, condition, assertActive, stage),
498
499
  readOwnerMd: (ownerAid, subAid) => this._getOwnershipStorage().readOwnerMd(ownerAid, subAid),
499
- writeOwnerMd: (ownerAid, subAid, raw) => this._getOwnershipStorage().writeOwnerMd(ownerAid, subAid, raw),
500
- deleteOwnerMd: (ownerAid, subAid) => this._getOwnershipStorage().deleteOwnerMd(ownerAid, subAid),
500
+ writeOwnerMd: (ownerAid, subAid, raw, original, assertActive) => this._getOwnershipStorage().writeOwnerMd(ownerAid, subAid, raw, original, assertActive),
501
+ deleteOwnerMd: (ownerAid, subAid, expectedVersion) => this._getOwnershipStorage().deleteOwnerMd(ownerAid, subAid, expectedVersion),
501
502
  resolveCertificate: async (aid, fingerprint, purpose) => this._auth.resolveOwnershipCertificate(await this._resolveOwnershipCertificateGateway(aid), aid, fingerprint, purpose),
502
503
  });
503
504
  }
@@ -505,7 +506,7 @@ export class AUNClient {
505
506
  return this._ownershipWorkflow ??= new OwnershipWorkflow({
506
507
  currentAid: () => this._aid, store: this._tokenStore, files: this._getOwnershipFiles(),
507
508
  currentType: () => this.getType(),
508
- send: (to, payload) => this.call('message.send', { to, encrypt: true, payload: payload }),
509
+ send: (to, payload, messageId) => this.call('message.send', { to, encrypt: true, message_id: messageId, payload: payload }),
509
510
  emit: (event, payload) => this._dispatcher.enqueue(event, payload),
510
511
  });
511
512
  }
@@ -549,6 +550,196 @@ 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, syncRevocations = false) {
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 (syncRevocations && (!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
+ let nextRevocationSync = Date.now() + 3_600_000;
669
+ const scan = async () => {
670
+ const task = this._credentialRenewalScan.then(async () => {
671
+ if (!current())
672
+ return;
673
+ const syncRevocations = Date.now() >= nextRevocationSync;
674
+ if (syncRevocations)
675
+ nextRevocationSync = Date.now() + 3_600_000;
676
+ await this._resumeCredentialRenewalAfterConnect(guard, syncRevocations);
677
+ });
678
+ this._credentialRenewalScan = task.catch(() => { });
679
+ await this._safeAsync(task);
680
+ if (current())
681
+ this._credentialRenewalTimer = setTimeout(() => { void scan(); }, 60_000);
682
+ };
683
+ void scan();
684
+ }
685
+ _stopCredentialRenewal() {
686
+ this._credentialRenewalAbort?.abort();
687
+ this._credentialRenewalAbort = null;
688
+ if (this._credentialRenewalTimer !== null)
689
+ clearTimeout(this._credentialRenewalTimer);
690
+ this._credentialRenewalTimer = null;
691
+ }
692
+ _rememberCredential(credential, material, certificate) {
693
+ if (!material.long_term && certificate) {
694
+ const cert = new crypto.X509Certificate(typeof certificate === 'string' && !certificate.includes('-----BEGIN CERTIFICATE-----') ? Buffer.from(certificate, 'base64') : certificate);
695
+ const parent = Array.isArray(credential.proof_chain) ? credential.proof_chain[0] : undefined;
696
+ const certificateEnd = Math.floor(Date.parse(cert.validTo) / 1000);
697
+ const defaultEnd = credential.parent != null && isJsonObject(parent) && typeof parent.exp === 'number'
698
+ ? Math.min(certificateEnd, Number(parent.exp)) : certificateEnd;
699
+ material = { ...material, long_term: credential.exp === defaultEnd };
700
+ }
701
+ this._safeAsync(this._getCredentialRenewal().remember(credential, material));
702
+ }
703
+ async _verifyIncomingServiceEvent(event, payload) {
704
+ const targetAid = this._identity?.aid ?? '';
705
+ const version = this._serviceEventIdentityVersion;
706
+ const signal = this._serviceEventIdentityAbort.signal;
707
+ const expiresAt = Number(payload?.expires_at);
708
+ const transport = this._transport, connectionGeneration = transport?._connectionGeneration, workerGeneration = transport?._workerGeneration;
709
+ const current = () => version === this._serviceEventIdentityVersion && targetAid === (this._identity?.aid ?? '') && transport === this._transport && connectionGeneration === this._transport?._connectionGeneration && workerGeneration === this._transport?._workerGeneration && Date.now() / 1000 < expiresAt;
710
+ const verified = await verifyServiceEvent(event, payload, { targetAid, resolveEventCertificate: (aid, fingerprint) => this._fetchServiceEventCertificate(aid, fingerprint), resolveCredentialService: this.resolveCredentialService });
711
+ if (!current())
712
+ throw new ValidationError('service event receiver identity changed');
713
+ if (['credential.revoked', 'credential.revocationList'].includes(event.replace(/^event\//, ''))) {
714
+ await this._getCredentialRenewal()._receiveRevocations(payload, { current, signal });
715
+ }
716
+ if (event.replace(/^event\//, '') === 'org.member.changed' && isJsonObject(payload) && Object.hasOwn(payload, 'membership_package')) {
717
+ await this._getCredentialRenewal().importMembershipPackage(payload, { current, signal });
718
+ if (!current())
719
+ throw new ValidationError('service event receiver identity changed');
720
+ }
721
+ return verified;
722
+ }
723
+ async _handleCredentialRenewal(message) {
724
+ if (!isJsonObject(message))
725
+ return false;
726
+ const envelope = message;
727
+ const content = 'payload' in envelope ? envelope.payload : envelope;
728
+ const payload = decryptedOwnershipJsonText(content) ?? content;
729
+ let parsed = payload;
730
+ if (typeof payload === 'string') {
731
+ try {
732
+ parsed = JSON.parse(payload);
733
+ }
734
+ catch {
735
+ return false;
736
+ }
737
+ }
738
+ if (!isJsonObject(parsed) || !['credential.renewal.request', 'credential.renewal.result'].includes(String(parsed.type)))
739
+ return false;
740
+ await this._safeAsync(this._getCredentialRenewal().receive(String(envelope.from ?? envelope.from_aid ?? envelope.sender_aid ?? ''), parsed));
741
+ return true;
742
+ }
552
743
  async _resumeRegistrationIntent() {
553
744
  const intent = this._tokenStore.loadMetadata?.(this._aid)?.registration_intent;
554
745
  if (['group', 'org'].includes(String(intent?.type ?? this.getType())))
@@ -598,11 +789,13 @@ export class AUNClient {
598
789
  _defaultConnectDeliveryMode;
599
790
  /** peer 证书缓存 */
600
791
  _certCache = new Map();
792
+ _certFetchInFlight = new Map();
601
793
  _agentMdManager;
602
794
  _aidStore;
603
795
  _groupIdentityOperations = new Map();
604
796
  _groupIndexMetaCache = new GroupIndexMetaCache();
605
797
  /** 消息序列号跟踪器(群消息 + P2P 空洞检测) */
798
+ _p2pPeerPullEnabled = true;
606
799
  _seqTracker = new SeqTracker();
607
800
  _seqTrackerContext = null;
608
801
  /** 惰性群同步:已同步过的 group_id 集合 */
@@ -685,6 +878,7 @@ export class AUNClient {
685
878
  _storage;
686
879
  _collab;
687
880
  _message;
881
+ _debug;
688
882
  _mail;
689
883
  _group;
690
884
  _stream;
@@ -784,7 +978,7 @@ export class AUNClient {
784
978
  dnsNet,
785
979
  });
786
980
  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);
981
+ this._transport.setServiceEventVerifier((event, payload) => this._verifyIncomingServiceEvent(event, payload), () => this._identity?.aid ?? '', () => this._serviceEventIdentityVersion);
788
982
  this._transport.setProtocolDisconnectCallback((err, closeCode) => this._handleTransportDisconnect(err, closeCode));
789
983
  this._transport._onForegroundDrained = () => this._delivery.onForegroundPullsDrained();
790
984
  this._transport._onRpcResponse = () => this._delivery.requestBackgroundPullRecovery();
@@ -817,7 +1011,8 @@ export class AUNClient {
817
1011
  this._dispatcher.subscribeProtocol('_raw.message.read_changed', (data) => this._onRawReadChanged(data, 'p2p'));
818
1012
  // V2 P2P 推送通知:收到通知后自动走 message.v2.pull 拉取并解密
819
1013
  this._dispatcher.subscribeProtocol('_raw.peer.v2.message_received', (data) => {
820
- const ns = this._aid ? `p2p:${this._aid}` : '';
1014
+ const sender = isJsonObject(data) ? String(data.sender_aid ?? '') : '';
1015
+ const ns = this._p2pPeerPullEnabled ? (sender ? this._delivery.peerNamespace(sender) : '') : this._aid ? `p2p:${this._aid}` : '';
821
1016
  const hasInline = isJsonObject(data)
822
1017
  && isJsonObject(data.inline_message);
823
1018
  const inlineGeneration = hasInline ? this._delivery.captureInlineAckGeneration() : undefined;
@@ -896,6 +1091,21 @@ export class AUNClient {
896
1091
  this._collab = new CollabClient(this);
897
1092
  return this._collab;
898
1093
  }
1094
+ get debug() {
1095
+ if (!this._debug)
1096
+ this._debug = new DebugFacade(this, async (operation) => {
1097
+ const transport = this._transport;
1098
+ const previous = transport.getTraceMode();
1099
+ transport.setTraceMode('diag');
1100
+ try {
1101
+ return await operation();
1102
+ }
1103
+ finally {
1104
+ transport.setTraceMode(previous);
1105
+ }
1106
+ });
1107
+ return this._debug;
1108
+ }
899
1109
  /** Message facade 入口 */
900
1110
  get message() {
901
1111
  if (!this._message)
@@ -974,6 +1184,7 @@ export class AUNClient {
974
1184
  }
975
1185
  async bindOwneraid(ownerAid) { return this._getOwnershipWorkflow().bindOwneraid(ownerAid); }
976
1186
  async bindSubaid(subAid) { return this._getOwnershipWorkflow().bindSubaid(subAid); }
1187
+ async resetOwnershipTask() { return this._getOwnershipWorkflow().resetOwnershipTask(); }
977
1188
  async releaseSubaid(subAid) { return this._getOwnershipWorkflow().releaseSubaid(subAid); }
978
1189
  async transferSubaid(subAid, newOwnerAid) { return this._getOwnershipWorkflow().transferSubaid(subAid, newOwnerAid); }
979
1190
  async checkOwnershipConsensus(subAid) { return this._getOwnershipWorkflow().checkOwnershipConsensus(subAid); }
@@ -1173,7 +1384,7 @@ export class AUNClient {
1173
1384
  dnsNet,
1174
1385
  });
1175
1386
  this._transport.setProtocolMetaObserver((meta) => this._observeRpcMeta(meta));
1176
- 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);
1387
+ this._transport.setServiceEventVerifier((event, payload) => this._verifyIncomingServiceEvent(event, payload), () => this._identity?.aid ?? '', () => this._serviceEventIdentityVersion);
1177
1388
  this._transport.setProtocolDisconnectCallback((err, closeCode) => this._handleTransportDisconnect(err, closeCode));
1178
1389
  this._transport._onForegroundDrained = () => this._delivery.onForegroundPullsDrained();
1179
1390
  this._transport._onRpcResponse = () => this._delivery.requestBackgroundPullRecovery();
@@ -1181,7 +1392,10 @@ export class AUNClient {
1181
1392
  loadIdentity(aid) {
1182
1393
  const previousAid = this._aid;
1183
1394
  this._identityRuntime.loadIdentity(aid);
1395
+ this._stopCredentialRenewal();
1184
1396
  this._serviceEventIdentityVersion++;
1397
+ this._serviceEventIdentityAbort.abort();
1398
+ this._serviceEventIdentityAbort = new AbortController();
1185
1399
  this._ensureReadStateIdentity();
1186
1400
  if (previousAid !== this._aid) {
1187
1401
  this._ownershipRecords.clear();
@@ -1289,6 +1503,7 @@ export class AUNClient {
1289
1503
  ? this._ensureReadStateIdentity() : null;
1290
1504
  try {
1291
1505
  const result = await this._rpcPipeline.call(method, params);
1506
+ await this._safeAsync(this._getCredentialRenewal().observe(method, params ?? {}, result));
1292
1507
  if (readIdentity !== null && readIdentity === this._ensureReadStateIdentity())
1293
1508
  this._observeReadStateResult(method, result, params ?? {});
1294
1509
  return result;
@@ -1318,6 +1533,13 @@ export class AUNClient {
1318
1533
  const groupId = String(params.group_id ?? params.groupId ?? params.group_aid ?? params.groupAid ?? fallback).trim();
1319
1534
  if (!groupId || !store)
1320
1535
  return false;
1536
+ const groupManager = store._agentMdManager ?? this._agentMdManager;
1537
+ const keystore = store._keystore;
1538
+ let pendingBind = keystore?.loadPendingGroupBind?.(`bind:${groupId}`);
1539
+ if (!pendingBind?.public_key_der_b64 || !pendingBind?.private_key_pem)
1540
+ pendingBind = keystore?.loadPendingGroupBind?.(groupId);
1541
+ if (groupManager.pendingUpload(fallback) && (!pendingBind?.public_key_der_b64 || !pendingBind?.private_key_pem))
1542
+ return false;
1321
1543
  try {
1322
1544
  await this._runGroupIdentityOperation(groupId, () => this.bindGroupAid({ group_id: groupId }, { aidStore: store }));
1323
1545
  }
@@ -1336,6 +1558,8 @@ export class AUNClient {
1336
1558
  if (!loaded.ok || !loaded.data?.aid?.isPrivateKeyValid())
1337
1559
  return false;
1338
1560
  const groupManager = store._agentMdManager ?? this._agentMdManager;
1561
+ if (groupManager.pendingUpload(groupAid))
1562
+ return false;
1339
1563
  if (String((groupManager._loadRecord?.(groupAid, true) ?? groupManager.loadRecord(groupAid))?.local_draft_content ?? ''))
1340
1564
  return false;
1341
1565
  await this._runGroupIdentityOperation(groupAid, () => this._uploadGroupAgentMd(store, groupAid, {}, {}));
@@ -1353,17 +1577,15 @@ export class AUNClient {
1353
1577
  const aid = this._aid;
1354
1578
  if (!aid || !this._currentAid)
1355
1579
  return false;
1356
- const observed = await this._getOwnershipFiles().readAgent(aid);
1357
- if (observed.exists || String(this._agentMdManager._loadRecord?.(aid, true)?.local_draft_content ?? this._agentMdManager.loadRecord(aid)?.local_draft_content ?? ''))
1580
+ const observed = await this._agentMdManager.readAgentMdFresh(aid);
1581
+ if (observed.exists || String(this._agentMdManager._loadRecord?.(aid, true)?.local_draft_content ?? this._agentMdManager.loadRecord(aid)?.local_draft_content ?? '') || this._agentMdManager.pendingUpload(aid))
1358
1582
  return false;
1359
1583
  let agentMd = buildDefaultAgentMd(aid, { type: this.getType() });
1360
1584
  try {
1361
1585
  agentMd = this._agentMdManager.readContent(aid) || agentMd;
1362
1586
  }
1363
1587
  catch { }
1364
- const payload = updateOwnershipAgentMd(agentMd, {});
1365
- const raw = signOwnershipFile(this._currentAid, payload.endsWith('\n') || payload.endsWith('\r') ? payload : `${payload}\n`, 'agent');
1366
- await this._agentMdManager.submitOwnershipRaw(aid, raw, { if_none_match: '*' });
1588
+ await this._agentMdManager.upload(agentMd, { if_none_match: '*' });
1367
1589
  return true;
1368
1590
  }
1369
1591
  async _checkIdentityAdmissionAfterConnect() {
@@ -1400,6 +1622,9 @@ export class AUNClient {
1400
1622
  if (!isJsonObject(raw))
1401
1623
  continue;
1402
1624
  const group = raw;
1625
+ const status = String(group.status ?? '').trim().toLowerCase();
1626
+ if (status && status !== 'active')
1627
+ continue;
1403
1628
  const role = String(group.role ?? group.my_role ?? '').trim().toLowerCase();
1404
1629
  const groupId = String(group.group_id ?? group.groupId ?? group.group_aid ?? group.groupAid ?? '').trim();
1405
1630
  const groupAid = String(group.group_aid ?? group.groupAid ?? groupId).trim();
@@ -1418,6 +1643,8 @@ export class AUNClient {
1418
1643
  return;
1419
1644
  }
1420
1645
  const groupManager = store._agentMdManager ?? this._agentMdManager;
1646
+ if (groupManager.pendingUpload(groupAid))
1647
+ return;
1421
1648
  if (String((groupManager._loadRecord?.(groupAid, true) ?? groupManager.loadRecord(groupAid))?.local_draft_content ?? '')) {
1422
1649
  await this._agentMdManager.check(groupAid);
1423
1650
  return;
@@ -1427,8 +1654,6 @@ export class AUNClient {
1427
1654
  await this.bindGroupAid({ group_id: groupId, group_aid: groupAid }, { aidStore: store });
1428
1655
  return;
1429
1656
  }
1430
- if (await this._resumeGroupAgentMdUpload(store, groupAid))
1431
- return;
1432
1657
  const checked = await this._agentMdManager.check(groupAid);
1433
1658
  if (!checked.remote_found) {
1434
1659
  await this._uploadGroupAgentMd(store, groupAid, {}, {});
@@ -1459,12 +1684,45 @@ export class AUNClient {
1459
1684
  this._groupIdentityOperations.delete(key);
1460
1685
  }
1461
1686
  }
1687
+ async _resumeOrgRequest(method, params, target) {
1688
+ const binding = params.request_binding;
1689
+ const now = Math.trunc(Date.now() / 1000);
1690
+ if (!isJsonObject(binding) || now < Number(binding.exp))
1691
+ return params;
1692
+ const credentials = isJsonObject(params.credentials) ? params.credentials : {};
1693
+ const credential = ['member_credential', 'approval_credential', 'audit_credential'].map((field) => credentials[field]).find(isJsonObject) ?? params.credential_template;
1694
+ const cid = isJsonObject(credential) ? String(credential.cid) : '';
1695
+ validateRequestBinding(binding, method, target, params, cid, String(params.nonce), String(params.request_id), Number(binding.iat));
1696
+ let exp = now + 300;
1697
+ 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]))]) {
1698
+ if (isJsonObject(material)) {
1699
+ if (!Number.isInteger(material.exp) || Number(material.exp) <= now)
1700
+ throw new ValidationError('Org recovery authorization material has expired');
1701
+ exp = Math.min(exp, Number(material.exp));
1702
+ }
1703
+ }
1704
+ const refreshed = { ...params, request_binding: buildRequestBinding(method, target, params, cid, String(params.nonce), String(params.request_id), now, exp) };
1705
+ delete refreshed.client_signature;
1706
+ if (isJsonObject(credentials.representation_proof)) {
1707
+ if (!this._currentAid || !isJsonObject(credential))
1708
+ throw new ValidationError('Org recovery representation signer is unavailable');
1709
+ refreshed.credentials = { ...credentials, representation_proof: createRepresentationProof(credential, refreshed, refreshed.request_binding, this._currentAid, refreshed.proof_chain) };
1710
+ }
1711
+ return refreshed;
1712
+ }
1462
1713
  async createOrg(params = {}, options = {}) {
1463
1714
  const store = options.aidStore;
1464
1715
  const owner = this._currentAid;
1465
1716
  const orgAid = String(params.org_id ?? '').trim();
1466
1717
  if (!store || !owner?.isPrivateKeyValid() || !orgAid)
1467
1718
  throw new ValidationError('createOrg requires aidStore, org_id and current owner identity');
1719
+ const tokens = store._tokenStore;
1720
+ const inputHash = crypto.createHash('sha256').update(canonicalJson(params)).digest('hex');
1721
+ let checkpoint = (typeof tokens._loadMetadataStrict === 'function' ? tokens._loadMetadataStrict(orgAid) : tokens.loadMetadata(orgAid))?.org_identity_creation;
1722
+ if (checkpoint != null && !isJsonObject(checkpoint))
1723
+ throw ownershipFailure('persistence_unavailable', 'Org creation checkpoint is invalid');
1724
+ if (checkpoint && (checkpoint.owner_aid !== owner.aid || checkpoint.input_hash !== inputHash))
1725
+ throw ownershipFailure('conflict', 'pending Org creation context changed');
1468
1726
  this._aidStore = store;
1469
1727
  let identity = store.load(orgAid);
1470
1728
  if (!identity.ok) {
@@ -1476,18 +1734,13 @@ export class AUNClient {
1476
1734
  if (!identity.ok)
1477
1735
  throw ownershipFailure('unavailable', identity.error.message, true);
1478
1736
  const target = options.serviceAid ?? `org.${orgAid.slice(orgAid.indexOf('.') + 1)}`;
1479
- const tokens = store._tokenStore;
1480
- const inputHash = crypto.createHash('sha256').update(canonicalJson(params)).digest('hex');
1481
- let checkpoint = tokens.loadMetadata(orgAid)?.org_identity_creation;
1482
- if (checkpoint && (checkpoint.owner_aid !== owner.aid || checkpoint.input_hash !== inputHash))
1483
- throw ownershipFailure('conflict', 'pending Org creation context changed');
1484
1737
  if (!checkpoint) {
1485
1738
  const prepared = this.org.prepareRequest('org.lifecycle.create', { ...params, owner_aid: owner.aid }, target, owner, { orgSigner: identity.data.aid });
1486
1739
  checkpoint = { owner_aid: owner.aid, input_hash: inputHash, source_params: params, prepared, phase: 'create' };
1487
1740
  tokens.saveMetadata(orgAid, { org_identity_creation: checkpoint });
1488
1741
  }
1489
1742
  if (checkpoint.phase === 'create') {
1490
- const result = await this.call('org.lifecycle.create', checkpoint.prepared);
1743
+ const result = await this.call('org.lifecycle.create', await this._resumeOrgRequest('org.lifecycle.create', checkpoint.prepared, target));
1491
1744
  checkpoint = { ...checkpoint, result, phase: 'upload' };
1492
1745
  tokens.saveMetadata(orgAid, { org_identity_creation: checkpoint });
1493
1746
  }
@@ -1501,33 +1754,60 @@ export class AUNClient {
1501
1754
  if (typeof keyStore?.listIdentities !== 'function' || typeof tokens?.loadMetadata !== 'function')
1502
1755
  return;
1503
1756
  for (const aid of keyStore.listIdentities()) {
1504
- const metadata = tokens.loadMetadata(aid);
1757
+ const metadata = typeof tokens._loadMetadataStrict === 'function' ? tokens._loadMetadataStrict(aid) : tokens.loadMetadata(aid);
1505
1758
  const creation = metadata?.org_identity_creation;
1506
1759
  const transfer = metadata?.org_identity_transfer;
1760
+ if (creation != null && !isJsonObject(creation) || transfer != null && !isJsonObject(transfer))
1761
+ throw ownershipFailure('persistence_unavailable', 'Org identity checkpoint is invalid');
1507
1762
  try {
1508
1763
  if (creation?.owner_aid === this._aid) {
1509
1764
  await this.createOrg(creation.source_params, { aidStore: store });
1510
1765
  continue;
1511
1766
  }
1512
- if (transfer?.owner_aid === this._aid) {
1767
+ if (transfer?.owner_aid === this._aid && transfer.phase !== 'completed') {
1513
1768
  await this.completeOrgTransfer(transfer.source_params, { aidStore: store });
1514
1769
  continue;
1515
1770
  }
1516
- const orgManager = store._agentMdManager;
1517
- if (String((orgManager?._loadRecord?.(aid, true) ?? orgManager?.loadRecord?.(aid))?.local_draft_content ?? ''))
1518
- continue;
1519
- const pending = store._agentMdManager.pendingUpload(aid);
1520
- if (!pending)
1521
- continue;
1522
- const fields = validateAgentMdDocument(pending.content, { expectedAid: aid }).frontmatter;
1523
- if (fields.type === 'org' && fields.owner === this._aid)
1524
- await this._resumeGroupAgentMdUpload(store, aid);
1525
1771
  }
1526
1772
  catch (error) {
1527
1773
  this._clientLog.warn(`post-connect Org identity recovery failed: aid=${aid} err=${formatCaughtError(error)}`);
1528
1774
  }
1529
1775
  }
1530
1776
  }
1777
+ async _verifyOrgTransferMaterials(params, ownerAid) {
1778
+ const orgAid = String(params.org_id);
1779
+ const previous = params.previous_owner;
1780
+ const next = params.new_owner;
1781
+ const binding = previous.membership_binding;
1782
+ const nextBinding = next.membership_binding;
1783
+ const credential = previous.credential_template;
1784
+ const ext = isJsonObject(credential.ext) ? credential.ext : {};
1785
+ const capability = Array.isArray(credential.cap) && credential.cap.length === 1 && isJsonObject(credential.cap[0]) ? credential.cap[0] : {};
1786
+ const now = Math.trunc(Date.now() / 1000);
1787
+ if (binding.org_id !== orgAid || nextBinding.org_id !== orgAid || binding.member_aid === ownerAid || nextBinding.member_aid !== ownerAid
1788
+ || binding.role !== 'member' || nextBinding.role !== 'owner' || digest(previous.terms) !== binding.terms_hash || digest(next.terms) !== nextBinding.terms_hash
1789
+ || !Number.isSafeInteger(nextBinding.iat) || !Number.isSafeInteger(nextBinding.exp) || Number(nextBinding.iat) > now || Number(nextBinding.exp) <= now
1790
+ || credential.iss !== orgAid || credential.sub !== binding.member_aid || credential.parent != null || credential.delegate !== false
1791
+ || ext.credential_type !== 'member' || ext.bound_to_membership !== true || ext.org_id !== orgAid || ext.membership_id !== binding.membership_id
1792
+ || ext.binding_version !== binding.binding_version || ext.membership_terms_hash !== binding.terms_hash
1793
+ || capability.ns !== 'org' || capability.action !== 'role.member' || capability.resource !== orgAid)
1794
+ throw ownershipFailure('invalid', 'Org transfer membership materials do not match');
1795
+ validateMembershipBinding(binding);
1796
+ const certificates = new Map();
1797
+ for (const [aid, signature] of [[binding.member_aid, binding.member_signature], [orgAid, binding.org_signature], [orgAid, credential.signature]]) {
1798
+ if (!isJsonObject(signature))
1799
+ throw ownershipFailure('invalid', 'Org transfer membership materials do not match');
1800
+ const fingerprint = String(signature.cert_fingerprint);
1801
+ const certificate = await this._auth.resolveOwnershipCertificate(await this._resolveOwnershipCertificateGateway(String(aid)), String(aid), fingerprint);
1802
+ certificates.set(fingerprint, certificate.cert_pem);
1803
+ }
1804
+ const resolve = (fingerprint) => certificates.get(fingerprint);
1805
+ const checkedAt = Math.trunc(Date.now() / 1000);
1806
+ verifyMembershipBinding(binding, resolve, checkedAt, orgAid);
1807
+ verifyCredentialSignature(credential, resolve, checkedAt, orgAid);
1808
+ if (Number(nextBinding.exp) <= checkedAt)
1809
+ throw ownershipFailure('invalid', 'Org transfer membership materials do not match');
1810
+ }
1531
1811
  async completeOrgTransfer(params = {}, options = {}) {
1532
1812
  const store = options.aidStore;
1533
1813
  const owner = this._currentAid;
@@ -1545,9 +1825,19 @@ export class AUNClient {
1545
1825
  const tokenStore = store._tokenStore;
1546
1826
  const pendingKey = `org-transfer:${orgAid}:${transferId}`;
1547
1827
  const inputHash = crypto.createHash('sha256').update(canonicalJson(params)).digest('hex');
1548
- let pending = tokenStore.loadMetadata(orgAid)?.org_identity_transfer;
1828
+ let pending = (typeof tokenStore._loadMetadataStrict === 'function' ? tokenStore._loadMetadataStrict(orgAid) : tokenStore.loadMetadata(orgAid))?.org_identity_transfer;
1829
+ if (pending != null && !isJsonObject(pending))
1830
+ throw ownershipFailure('persistence_unavailable', 'Org transfer checkpoint is invalid');
1831
+ if (pending?.phase === 'completed' && pending.transfer_id !== transferId)
1832
+ pending = undefined;
1549
1833
  if (pending && (pending.transfer_id !== transferId || pending.owner_aid !== owner.aid || pending.input_hash !== inputHash))
1550
1834
  throw ownershipFailure('conflict', 'pending Org transfer context changed');
1835
+ if (pending?.phase === 'completed') {
1836
+ localStore.clearPendingGroupBind(pendingKey);
1837
+ return pending.result;
1838
+ }
1839
+ if (!pending)
1840
+ await this._verifyOrgTransferMaterials(params, owner.aid);
1551
1841
  let keyPair = localStore.loadPendingGroupBind(pendingKey);
1552
1842
  if (!keyPair) {
1553
1843
  if (pending)
@@ -1568,7 +1858,7 @@ export class AUNClient {
1568
1858
  tokenStore.saveMetadata(orgAid, { org_identity_transfer: pending });
1569
1859
  }
1570
1860
  if (pending.phase === 'rekey') {
1571
- const result = await this.call('org.lifecycle.transferAccept', pending.rekey_params);
1861
+ const result = await this.call('org.lifecycle.transferAccept', await this._resumeOrgRequest('org.lifecycle.transferAccept', pending.rekey_params, target));
1572
1862
  pending = { ...pending, phase: 'finalize', rekey_result: result };
1573
1863
  tokenStore.saveMetadata(orgAid, { org_identity_transfer: pending });
1574
1864
  }
@@ -1604,9 +1894,9 @@ export class AUNClient {
1604
1894
  pending = { ...pending, finalize_params: this.org.prepareRequest('org.lifecycle.transferAccept', final, target, owner, { orgSigner: orgIdentity }) };
1605
1895
  tokenStore.saveMetadata(orgAid, { org_identity_transfer: pending });
1606
1896
  }
1607
- const result = await this.call('org.lifecycle.transferAccept', pending.finalize_params);
1897
+ const result = await this.call('org.lifecycle.transferAccept', await this._resumeOrgRequest('org.lifecycle.transferAccept', pending.finalize_params, target));
1608
1898
  await this._uploadOrgAgentMd(store, orgAid, pending.source_params);
1609
- tokenStore.saveMetadata(orgAid, { org_identity_transfer: null });
1899
+ tokenStore.saveMetadata(orgAid, { org_identity_transfer: { transfer_id: transferId, owner_aid: owner.aid, input_hash: inputHash, phase: 'completed', result } });
1610
1900
  localStore.clearPendingGroupBind(pendingKey);
1611
1901
  return result;
1612
1902
  }
@@ -1859,7 +2149,13 @@ export class AUNClient {
1859
2149
  entityIdentityGetter: (aid, type) => this._freshEntityCertificate(aid, type),
1860
2150
  entitySignerResolver: (aid, fingerprint) => this._freshEntitySigner(aid, fingerprint),
1861
2151
  gatewayResolver: (aid) => identityStore._resolveGateway(aid),
1862
- accessTokenResolver: (_aid, gatewayUrl) => identityStore._uploadAgentMdToken(identity, gatewayUrl),
2152
+ accessTokenResolver: async () => {
2153
+ const state = identityStore._tokenStore?.loadInstanceState?.(identity.aid, store.deviceId, store.slotId) ?? {};
2154
+ const token = AuthFlow._getCachedAccessToken(state);
2155
+ if (token && String(state.refresh_token ?? '') && !this._auth._validateCachedCredentials(identityStore._authIdentityFromAid(identity)))
2156
+ return token;
2157
+ return identityStore._uploadAgentMdToken(identity, await identityStore._resolveGateway(identity.aid));
2158
+ },
1863
2159
  aidValidator: (aid) => identityStore._registerFlow.validateAidName(aid),
1864
2160
  });
1865
2161
  return await manager.resumePendingUpload(groupAid);
@@ -2334,6 +2630,11 @@ export class AUNClient {
2334
2630
  const tStart = Date.now();
2335
2631
  this._clientLog.debug(`on enter: event=${event}`);
2336
2632
  const result = this._dispatcher.subscribe(event, handler);
2633
+ if (event === 'message.received' || event === 'message.recalled' || event === 'message.batch') {
2634
+ if (this._p2pPeerPullEnabled)
2635
+ this._delivery.refreshPendingPeers();
2636
+ this._delivery.requestBackgroundPullRecovery();
2637
+ }
2337
2638
  this._clientLog.debug(`on exit: elapsed=${Date.now() - tStart}ms event=${event}`);
2338
2639
  return result;
2339
2640
  }
@@ -2663,7 +2964,10 @@ export class AUNClient {
2663
2964
  if (event === 'message.received') {
2664
2965
  return this._handleOwnershipMessage(payload).then((handled) => {
2665
2966
  if (!handled)
2666
- return this._delivery.publishAppEvent(event, payload, source, deliveryContext);
2967
+ return this._handleCredentialRenewal(payload).then((renewed) => {
2968
+ if (!renewed)
2969
+ return this._delivery.publishAppEvent(event, payload, source, deliveryContext);
2970
+ });
2667
2971
  return undefined;
2668
2972
  });
2669
2973
  }
@@ -3094,22 +3398,36 @@ export class AUNClient {
3094
3398
  this._clientLog.debug(`_fetchPeerCert exit: elapsed=${Date.now() - tStart}ms aid=${aid} (cache_hit)`);
3095
3399
  return cached.certPem;
3096
3400
  }
3097
- const gatewayUrl = this._gatewayUrl;
3098
- if (!gatewayUrl) {
3099
- throw new ValidationError('gateway url unavailable for e2ee cert fetch');
3401
+ const inFlightKey = `${cacheKey}:timeout:${timeoutMs}`;
3402
+ const pending = this._certFetchInFlight.get(inFlightKey);
3403
+ if (pending)
3404
+ return await pending;
3405
+ const request = (async () => {
3406
+ const gatewayUrl = this._gatewayUrl;
3407
+ if (!gatewayUrl) {
3408
+ throw new ValidationError('gateway url unavailable for e2ee cert fetch');
3409
+ }
3410
+ const peerGatewayUrl = AUNClient._resolvePeerGatewayUrl(gatewayUrl, aid);
3411
+ const certPem = await _httpGetText(AUNClient._buildCertUrl(peerGatewayUrl, aid, certFingerprint), this._configModel.verifySsl, timeoutMs);
3412
+ const validated = await this._validateAndCachePeerCert({
3413
+ aid,
3414
+ certPem,
3415
+ certFingerprint,
3416
+ source: 'fetch',
3417
+ signedAt,
3418
+ agentMdRead,
3419
+ });
3420
+ this._clientLog.debug(`_fetchPeerCert exit: elapsed=${Date.now() - tStart}ms aid=${aid} (fetched)`);
3421
+ return validated;
3422
+ })();
3423
+ this._certFetchInFlight.set(inFlightKey, request);
3424
+ try {
3425
+ return await request;
3426
+ }
3427
+ finally {
3428
+ if (this._certFetchInFlight.get(inFlightKey) === request)
3429
+ this._certFetchInFlight.delete(inFlightKey);
3100
3430
  }
3101
- const peerGatewayUrl = AUNClient._resolvePeerGatewayUrl(gatewayUrl, aid);
3102
- const certPem = await _httpGetText(AUNClient._buildCertUrl(peerGatewayUrl, aid, certFingerprint), this._configModel.verifySsl, timeoutMs);
3103
- const validated = await this._validateAndCachePeerCert({
3104
- aid,
3105
- certPem,
3106
- certFingerprint,
3107
- source: 'fetch',
3108
- signedAt,
3109
- agentMdRead,
3110
- });
3111
- this._clientLog.debug(`_fetchPeerCert exit: elapsed=${Date.now() - tStart}ms aid=${aid} (fetched)`);
3112
- return validated;
3113
3431
  }
3114
3432
  catch (err) {
3115
3433
  this._clientLog.debug(`_fetchPeerCert exit (error): elapsed=${Date.now() - tStart}ms aid=${aid} err=${err instanceof Error ? err.message : String(err)}`);
@@ -3685,7 +4003,7 @@ export class AUNClient {
3685
4003
  }
3686
4004
  /** 后台 Promise 统一兜底,避免事件回调里的异步异常变成未处理拒绝。 */
3687
4005
  _safeAsync(promise) {
3688
- promise.catch((exc) => {
4006
+ return promise.then(() => undefined).catch((exc) => {
3689
4007
  this._clientLog.warn(`background task exception: ${formatCaughtError(exc)}`);
3690
4008
  });
3691
4009
  }
@@ -3880,7 +4198,7 @@ export class AUNClient {
3880
4198
  return await this._v2E2EE.ackGroupV2(groupId, upToSeq, groupAid);
3881
4199
  }
3882
4200
  /** 解密单条 V2 pull 消息。缺 sender IK 时先入 pending,后台补齐后重试。 */
3883
- async _decryptV2Message(msg, allowPending = true, emitUndecryptable = true, rotateKeys = true, observeAgentMd = true, deferStatus, deferKeyFetch = false, source = 'pull') {
4201
+ async _decryptV2Message(msg, allowPending = true, emitUndecryptable = true, rotateKeys = true, observeAgentMd = true, deferStatus, deferKeyFetch = false, source = 'pull', deferredEffects) {
3884
4202
  const session = this._v2Session;
3885
4203
  if (!session)
3886
4204
  return null;
@@ -3896,8 +4214,12 @@ export class AUNClient {
3896
4214
  return null;
3897
4215
  }
3898
4216
  const e2eeMeta = this._v2E2eeMeta(envelope);
3899
- if (observeAgentMd)
3900
- this._agentMdManager.observeEnvelope(envelope);
4217
+ if (observeAgentMd) {
4218
+ if (deferredEffects)
4219
+ deferredEffects.push(() => this._agentMdManager.observeEnvelope(envelope));
4220
+ else
4221
+ this._agentMdManager.observeEnvelope(envelope);
4222
+ }
3901
4223
  let spkId = '';
3902
4224
  let recipientKeySource = '';
3903
4225
  if (isJsonObject(envelope.recipient)) {
@@ -3976,8 +4298,12 @@ export class AUNClient {
3976
4298
  };
3977
4299
  this._attachV2EnvelopeMetadata(event, e2eeMeta);
3978
4300
  this._logMessageDebug('decrypt-fail', 'v2.decrypt', undecryptableEvent, event);
3979
- if (emitUndecryptable)
3980
- this._dispatcher.enqueue(undecryptableEvent, event);
4301
+ if (emitUndecryptable) {
4302
+ if (deferredEffects)
4303
+ deferredEffects.push(() => { this._dispatcher.enqueue(undecryptableEvent, event); });
4304
+ else
4305
+ this._dispatcher.enqueue(undecryptableEvent, event);
4306
+ }
3981
4307
  return null;
3982
4308
  }
3983
4309
  this._clientLog.debug(`V2 decrypt key lookup ok: seq=${String(msg.seq ?? '')}, group=${groupIdForKeys || '<p2p>'}, ik_len=${ikPriv.byteLength}, spk_len=${spkPriv?.byteLength ?? 0}`);
@@ -4015,8 +4341,12 @@ export class AUNClient {
4015
4341
  };
4016
4342
  this._attachV2EnvelopeMetadata(event, e2eeMeta);
4017
4343
  this._logMessageDebug('decrypt-fail', 'v2.decrypt', undecryptableEvent, event);
4018
- if (emitUndecryptable)
4019
- this._dispatcher.enqueue(undecryptableEvent, event);
4344
+ if (emitUndecryptable) {
4345
+ if (deferredEffects)
4346
+ deferredEffects.push(() => { this._dispatcher.enqueue(undecryptableEvent, event); });
4347
+ else
4348
+ this._dispatcher.enqueue(undecryptableEvent, event);
4349
+ }
4020
4350
  return null;
4021
4351
  }
4022
4352
  let plaintext;
@@ -4044,16 +4374,24 @@ export class AUNClient {
4044
4374
  };
4045
4375
  this._attachV2EnvelopeMetadata(event, e2eeMeta);
4046
4376
  this._logMessageDebug('decrypt-fail', 'v2.decrypt', undecryptableEvent, event);
4047
- if (emitUndecryptable)
4048
- this._dispatcher.enqueue(undecryptableEvent, event);
4377
+ if (emitUndecryptable) {
4378
+ if (deferredEffects)
4379
+ deferredEffects.push(() => { this._dispatcher.enqueue(undecryptableEvent, event); });
4380
+ else
4381
+ this._dispatcher.enqueue(undecryptableEvent, event);
4382
+ }
4049
4383
  return null;
4050
4384
  }
4051
4385
  if (plaintext === null) {
4052
4386
  this._clientLog.debug(`V2 decrypt returned null plaintext: seq=${String(msg.seq ?? '')}, group=${groupIdForKeys || '<p2p>'}`);
4053
4387
  return null;
4054
4388
  }
4055
- if (rotateKeys)
4056
- this._scheduleV2KeyRotationForMessage(msg, envelope);
4389
+ if (rotateKeys) {
4390
+ if (deferredEffects)
4391
+ deferredEffects.push(() => this._scheduleV2KeyRotationForMessage(msg, envelope));
4392
+ else
4393
+ this._scheduleV2KeyRotationForMessage(msg, envelope);
4394
+ }
4057
4395
  const e2ee = this._v2E2eeMeta(envelope);
4058
4396
  let result = {
4059
4397
  message_id: String(msg.message_id ?? ''),
@@ -4149,6 +4487,8 @@ export class AUNClient {
4149
4487
  if (protectedHeaders && Object.keys(protectedHeaders).length > 0) {
4150
4488
  meta.protected_headers = protectedHeaders;
4151
4489
  }
4490
+ if (typeof envelope.need_render === 'boolean')
4491
+ meta.need_render = envelope.need_render;
4152
4492
  const payloadType = String(envelope.payload_type ?? protectedHeaders?.payload_type ?? '').trim();
4153
4493
  if (payloadType) {
4154
4494
  meta.payload_type = payloadType;
@@ -4164,6 +4504,8 @@ export class AUNClient {
4164
4504
  return meta;
4165
4505
  }
4166
4506
  _attachV2EnvelopeMetadata(message, meta) {
4507
+ if (typeof meta.need_render === 'boolean')
4508
+ message.need_render = meta.need_render;
4167
4509
  const payloadType = typeof meta.payload_type === 'string' ? meta.payload_type.trim() : '';
4168
4510
  if (payloadType)
4169
4511
  message.payload_type = payloadType;
@@ -4521,6 +4863,7 @@ export class AUNClient {
4521
4863
  // ── 内部:后台任务 ────────────────────────────────────────
4522
4864
  /** 启动所有后台任务 */
4523
4865
  _startBackgroundTasks() {
4866
+ this._startCredentialRenewal();
4524
4867
  // WebSocket Ping/Pong 由 Gateway 与传输层处理;SDK 不发送 RPC 心跳。
4525
4868
  // 短连接生命周期短,不需要 token 刷新。
4526
4869
  if (this._sessionOptions.connection_kind !== 'short') {
@@ -4530,6 +4873,7 @@ export class AUNClient {
4530
4873
  }
4531
4874
  /** 停止所有后台任务 */
4532
4875
  _stopBackgroundTasks() {
4876
+ this._stopCredentialRenewal();
4533
4877
  if (this._heartbeatTimer !== null) {
4534
4878
  clearInterval(this._heartbeatTimer);
4535
4879
  this._heartbeatTimer = null;