@agentunion/fastaun-browser 0.5.20 → 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 (130) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/_packed_docs/CHANGELOG.md +47 -0
  3. package/_packed_docs/INDEX.md +453 -440
  4. package/_packed_docs/KITE_DOCS_GUIDE.md +164 -161
  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/protocol/06-/346/234/215/345/212/241/345/215/217/350/256/256.md +1433 -1392
  10. package/_packed_docs/protocol/08-AUN-E2EE.md +17 -12
  11. package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/211.md +2 -0
  12. package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2112.1.md +666 -658
  13. 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
  14. 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
  15. package/_packed_docs/sdk/01-/345/277/253/351/200/237/345/274/200/345/247/213.md +1 -1
  16. package/_packed_docs/sdk/02-WebSocket/345/215/217/350/256/256.md +5 -1
  17. package/_packed_docs/sdk/03-/346/240/270/345/277/203/346/246/202/345/277/265.md +64 -64
  18. package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +4 -1
  19. package/_packed_docs/sdk/05-E2EE/345/212/240/345/257/206/351/200/232/344/277/241.md +42 -39
  20. package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +147 -14
  21. package/_packed_docs/sdk/08-/346/234/200/344/275/263/345/256/236/350/267/265.md +48 -47
  22. package/_packed_docs/sdk/09-group-rpc-manual.md +5 -2
  23. package/_packed_docs/sdk/09-message-rpc-manual.md +250 -160
  24. 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
  25. 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
  26. package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +134 -130
  27. package/_packed_docs/sdk/INDEX.md +239 -221
  28. package/_packed_docs/sdk/Ownership-SDK/347/274/226/346/216/222/346/214/207/345/215/2272.1.md +930 -922
  29. 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 +106 -43
  30. 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 +180 -9
  31. 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
  32. package/_packed_docs/sdk/Ownership2.1/350/257/246/347/273/206/350/256/276/350/256/241.md +480 -476
  33. 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 +1 -1
  34. 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 +2 -2
  35. package/dist/agent-md-schema.d.ts +1 -0
  36. package/dist/agent-md-schema.d.ts.map +1 -1
  37. package/dist/agent-md-schema.js +5 -4
  38. package/dist/agent-md-schema.js.map +1 -1
  39. package/dist/agent-md.d.ts +25 -1
  40. package/dist/agent-md.d.ts.map +1 -1
  41. package/dist/agent-md.js +148 -71
  42. package/dist/agent-md.js.map +1 -1
  43. package/dist/aid-store.d.ts +13 -0
  44. package/dist/aid-store.d.ts.map +1 -1
  45. package/dist/aid-store.js +55 -8
  46. package/dist/aid-store.js.map +1 -1
  47. package/dist/aid.d.ts.map +1 -1
  48. package/dist/aid.js +2 -1
  49. package/dist/aid.js.map +1 -1
  50. package/dist/auth.d.ts.map +1 -1
  51. package/dist/auth.js +4 -0
  52. package/dist/auth.js.map +1 -1
  53. package/dist/bundle.js +4981 -1219
  54. package/dist/cert-utils.d.ts +1 -0
  55. package/dist/cert-utils.d.ts.map +1 -1
  56. package/dist/cert-utils.js +21 -0
  57. package/dist/cert-utils.js.map +1 -1
  58. package/dist/client/delivery.d.ts +101 -16
  59. package/dist/client/delivery.d.ts.map +1 -1
  60. package/dist/client/delivery.js +1172 -160
  61. package/dist/client/delivery.js.map +1 -1
  62. package/dist/client/rpc-pipeline.d.ts +4 -1
  63. package/dist/client/rpc-pipeline.d.ts.map +1 -1
  64. package/dist/client/rpc-pipeline.js +122 -48
  65. package/dist/client/rpc-pipeline.js.map +1 -1
  66. package/dist/client/v2-e2ee.d.ts +22 -3
  67. package/dist/client/v2-e2ee.d.ts.map +1 -1
  68. package/dist/client/v2-e2ee.js +1258 -649
  69. package/dist/client/v2-e2ee.js.map +1 -1
  70. package/dist/client.d.ts +23 -2
  71. package/dist/client.d.ts.map +1 -1
  72. package/dist/client.js +391 -87
  73. package/dist/client.js.map +1 -1
  74. package/dist/config.d.ts +4 -0
  75. package/dist/config.d.ts.map +1 -1
  76. package/dist/config.js +1 -0
  77. package/dist/config.js.map +1 -1
  78. package/dist/events.d.ts +19 -1
  79. package/dist/events.d.ts.map +1 -1
  80. package/dist/events.js +146 -23
  81. package/dist/events.js.map +1 -1
  82. package/dist/facades.d.ts +42 -1
  83. package/dist/facades.d.ts.map +1 -1
  84. package/dist/facades.js +363 -7
  85. package/dist/facades.js.map +1 -1
  86. package/dist/index.d.ts +5 -3
  87. package/dist/index.d.ts.map +1 -1
  88. package/dist/index.js +1 -1
  89. package/dist/index.js.map +1 -1
  90. package/dist/keystore/index.d.ts +14 -1
  91. package/dist/keystore/index.d.ts.map +1 -1
  92. package/dist/keystore/indexeddb-identity-store.d.ts +4 -0
  93. package/dist/keystore/indexeddb-identity-store.d.ts.map +1 -1
  94. package/dist/keystore/indexeddb-identity-store.js +30 -13
  95. package/dist/keystore/indexeddb-identity-store.js.map +1 -1
  96. package/dist/keystore/indexeddb-shared.d.ts +19 -2
  97. package/dist/keystore/indexeddb-shared.d.ts.map +1 -1
  98. package/dist/keystore/indexeddb-shared.js +193 -6
  99. package/dist/keystore/indexeddb-shared.js.map +1 -1
  100. package/dist/keystore/indexeddb-token-store.d.ts +15 -2
  101. package/dist/keystore/indexeddb-token-store.d.ts.map +1 -1
  102. package/dist/keystore/indexeddb-token-store.js +308 -27
  103. package/dist/keystore/indexeddb-token-store.js.map +1 -1
  104. package/dist/org-protocol.d.ts +45 -0
  105. package/dist/org-protocol.d.ts.map +1 -1
  106. package/dist/org-protocol.js +757 -11
  107. package/dist/org-protocol.js.map +1 -1
  108. package/dist/ownership-v21.d.ts +21 -4
  109. package/dist/ownership-v21.d.ts.map +1 -1
  110. package/dist/ownership-v21.js +49 -16
  111. package/dist/ownership-v21.js.map +1 -1
  112. package/dist/ownership-workflow.d.ts +30 -0
  113. package/dist/ownership-workflow.d.ts.map +1 -1
  114. package/dist/ownership-workflow.js +412 -54
  115. package/dist/ownership-workflow.js.map +1 -1
  116. package/dist/seq-tracker.d.ts +2 -0
  117. package/dist/seq-tracker.d.ts.map +1 -1
  118. package/dist/seq-tracker.js +3 -2
  119. package/dist/seq-tracker.js.map +1 -1
  120. package/dist/tools/cross-sdk-agent.d.ts +60 -3
  121. package/dist/tools/cross-sdk-agent.d.ts.map +1 -1
  122. package/dist/tools/cross-sdk-agent.js +433 -37
  123. package/dist/tools/cross-sdk-agent.js.map +1 -1
  124. package/dist/transport.d.ts +3 -0
  125. package/dist/transport.d.ts.map +1 -1
  126. package/dist/transport.js +62 -30
  127. package/dist/transport.js.map +1 -1
  128. package/dist/version.d.ts +1 -1
  129. package/dist/version.js +1 -1
  130. package/package.json +1 -1
package/dist/client.js CHANGED
@@ -20,7 +20,7 @@ import { IdentityRuntimeManager } from './client/identity.js';
20
20
  import { LifecycleController } from './client/lifecycle.js';
21
21
  import { PeerDirectory } from './client/peers.js';
22
22
  import { RpcPipeline } from './client/rpc-pipeline.js';
23
- import { CredentialFacade, GroupFacade, MailFacade, MessageFacade, OrgFacade, StreamFacade } from './facades.js';
23
+ import { DebugFacade, CredentialFacade, GroupFacade, MailFacade, MessageFacade, OrgFacade, StreamFacade } from './facades.js';
24
24
  import { StorageVFS } from './storage/vfs.js';
25
25
  import { CollabClient } from './collab/client.js';
26
26
  import { V2E2EECoordinator } from './client/v2-e2ee.js';
@@ -39,7 +39,7 @@ import { AUNError, AuthError, ConnectionError, E2EEError, NotFoundError, Permiss
39
39
  import { isJsonObject, ConnectionState, STATE_TO_PUBLIC, } from './types.js';
40
40
  import { AID } from './aid.js';
41
41
  import { OwnershipFiles, OwnershipStorageAdapter, ownershipSha256 } from './ownership-v21.js';
42
- import { canonicalJson, createCredential, createMemberCredential, signMembershipBinding, verifyServiceEvent } from './org-protocol.js';
42
+ import { canonicalJson, createCredential, createMemberCredential, signMembershipBinding, digest, validateMembershipBinding, verifyMembershipBinding, verifyCredentialSignature, createRepresentationProof, verifyServiceEvent, buildRequestBinding, validateRequestBinding, CredentialRenewal } from './org-protocol.js';
43
43
  import { OwnershipWorkflow, parseOwnershipRegistrationIntent } from './ownership-workflow.js';
44
44
  import { buildOwnershipMessage, encryptIdentity, decryptIdentity, } from './ownership-proof.js';
45
45
  function getV2DeviceId(dev) {
@@ -466,6 +466,7 @@ function v2E2eeMeta(envelope) {
466
466
  suite,
467
467
  encryption_mode: `v2_${modeSuite}`,
468
468
  forward_secrecy: true,
469
+ need_render: envelope.need_render !== false,
469
470
  };
470
471
  const protectedHeaders = metadataWithoutAuth(envelope.protected_headers);
471
472
  if (protectedHeaders && Object.keys(protectedHeaders).length > 0) {
@@ -488,6 +489,8 @@ function v2E2eeMeta(envelope) {
488
489
  function attachV2EnvelopeMetadata(message, meta) {
489
490
  if (!meta)
490
491
  return;
492
+ if (typeof meta.need_render === 'boolean')
493
+ message.need_render = meta.need_render;
491
494
  const payloadType = typeof meta.payload_type === 'string' ? meta.payload_type.trim() : '';
492
495
  if (payloadType)
493
496
  message.payload_type = payloadType;
@@ -589,6 +592,7 @@ export class AUNClient {
589
592
  config;
590
593
  _aid = null;
591
594
  _serviceEventIdentityVersion = 0;
595
+ _serviceEventIdentityAbort = new AbortController();
592
596
  _identity = null;
593
597
  _state = 'idle';
594
598
  _currentAid = null;
@@ -674,6 +678,7 @@ export class AUNClient {
674
678
  _transport;
675
679
  // E2EE 编排状态(内存缓存)
676
680
  _certCache = new Map();
681
+ _certFetchInFlight = new Map();
677
682
  // 后台任务 handle(浏览器 setInterval/setTimeout)
678
683
  _heartbeatTimer = null;
679
684
  _heartbeatCount = 0;
@@ -714,6 +719,7 @@ export class AUNClient {
714
719
  _groupIndexCacheLoaded = new Set();
715
720
  /** 消息序列号跟踪器(群消息 + P2P 空洞检测) */
716
721
  _seqTracker = new SeqTracker();
722
+ _p2pPeerPullEnabled = true;
717
723
  _seqTrackerContext = null;
718
724
  /** 补洞去重:已完成/进行中的 key 集合,防止重复 pull 同一区间 */
719
725
  _gapFillDone = new Set();
@@ -772,10 +778,173 @@ export class AUNClient {
772
778
  _storage;
773
779
  _collab;
774
780
  _messageFacade;
781
+ _debugFacade;
775
782
  _mailFacade;
776
783
  _groupFacade;
777
784
  _streamFacade;
778
785
  _credentialFacade;
786
+ _credentialRenewal;
787
+ _credentialRenewalTimer = null;
788
+ _credentialRenewalAbort = null;
789
+ _credentialRenewalScan = Promise.resolve();
790
+ _getCredentialRenewal() {
791
+ return this._credentialRenewal ??= new CredentialRenewal({
792
+ currentAid: () => this._aid ?? '',
793
+ atomicCheckpointSave: () => this._tokenStore instanceof IndexedDBTokenStore,
794
+ load: async (aid) => JSON.parse(await (this._tokenStore._getMetadataStrict?.(aid, 'credential_renewals_v1') ?? this._tokenStore.getMetadata?.(aid, 'credential_renewals_v1')) || '{}'),
795
+ ...(typeof this._tokenStore._getMetadataStrict === 'function' ? {
796
+ loadRevocations: async (aid) => JSON.parse(await this._tokenStore._getMetadataStrict(aid, 'credential_revocations_v1') || '{}'),
797
+ saveRevocations: async (aid, state, guard) => {
798
+ if (guard)
799
+ await this._tokenStore._setMetadataIfCurrent(aid, 'credential_revocations_v1', JSON.stringify(state), guard);
800
+ else
801
+ await this._tokenStore.setMetadata?.(aid, 'credential_revocations_v1', JSON.stringify(state));
802
+ },
803
+ } : {}),
804
+ save: async (aid, records, guard) => {
805
+ const version = this._serviceEventIdentityVersion;
806
+ const current = () => version === this._serviceEventIdentityVersion && aid === this._aid && (!guard || guard.current());
807
+ if (!current())
808
+ throw new ValidationError('membership event receiver identity changed');
809
+ const saved = this._tokenStore instanceof IndexedDBTokenStore ? records : Object.fromEntries(Object.entries(records).map(([cid, value]) => {
810
+ const record = { ...value };
811
+ for (const key of ['submitted_at', 'submission_hash', 'registration_submitted_at', 'registration_submission_hash'])
812
+ delete record[key];
813
+ return [cid, record];
814
+ }));
815
+ if (typeof this._tokenStore._setMetadataIfCurrent === 'function')
816
+ await this._tokenStore._setMetadataIfCurrent(aid, 'credential_renewals_v1', JSON.stringify(saved), { current, signal: guard?.signal });
817
+ else
818
+ await this._tokenStore.setMetadata?.(aid, 'credential_renewals_v1', JSON.stringify(saved));
819
+ if (!current())
820
+ throw new ValidationError('membership event receiver identity changed');
821
+ },
822
+ signer: (aid, guard) => this._credentialSigningIdentity(aid, guard),
823
+ certificate: (aid, fingerprint, signedAt) => signedAt === undefined
824
+ ? this._resolveServiceEventCertificate(aid, fingerprint)
825
+ : this._fetchPeerCert(aid, fingerprint, undefined, signedAt),
826
+ call: (method, params, onSent) => {
827
+ if (!onSent)
828
+ return this._rpcPipeline.call(method, params);
829
+ const version = this._serviceEventIdentityVersion, aid = this._aid;
830
+ return this._rpcPipeline['_callWithSent'](method, params, async () => {
831
+ if (version !== this._serviceEventIdentityVersion || aid !== this._aid)
832
+ throw new ValidationError('credential sender identity changed');
833
+ await onSent();
834
+ });
835
+ },
836
+ send: (to, payload) => this.call('message.send', { to, encrypt: true, payload }),
837
+ });
838
+ }
839
+ async _assertCredentialNotRevoked(credential, proofChain) {
840
+ const version = this._serviceEventIdentityVersion, aid = this._aid;
841
+ const current = () => { if (aid !== this._aid || version !== this._serviceEventIdentityVersion)
842
+ throw new ValidationError('credential receiver identity changed'); };
843
+ await this._getCredentialRenewal()._assertNotRevoked(credential, proofChain);
844
+ current();
845
+ return current;
846
+ }
847
+ async _credentialSigningIdentity(aid, guard) {
848
+ const check = () => { if (guard && !guard.current())
849
+ throw new ValidationError('credential renewal was superseded'); };
850
+ check();
851
+ const stored = await this._aidStore?.load(aid);
852
+ check();
853
+ let identity = aid === this._aid && this._identity?.private_key_pem
854
+ ? await 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 })
855
+ : stored?.ok ? stored.data.aid : undefined;
856
+ if (!identity?.isPrivateKeyValid())
857
+ return null;
858
+ check();
859
+ const certificate = parseCertMetadata(identity.certPem);
860
+ const end = certificate.notAfter.getTime(), start = certificate.notBefore.getTime();
861
+ if (end > Date.now() && end - Date.now() < (end - start) / 2 && this._aidStore) {
862
+ const renewed = await (guard ? this._aidStore._renewCert(aid, guard) : this._aidStore.renewCert(aid));
863
+ check();
864
+ if (!renewed.ok)
865
+ throw new ValidationError(renewed.error.message);
866
+ const loaded = await this._aidStore.load(aid);
867
+ check();
868
+ if (!loaded.ok)
869
+ throw new ValidationError(loaded.error.message);
870
+ identity = loaded.data.aid;
871
+ if (aid === this._aid && this._identity) {
872
+ this._identity.cert = identity.certPem;
873
+ this._currentAid = identity;
874
+ }
875
+ }
876
+ return identity;
877
+ }
878
+ _startCredentialRenewal() {
879
+ this._stopCredentialRenewal();
880
+ const aid = this._aid, version = this._serviceEventIdentityVersion;
881
+ if (!aid || this._state !== 'connected' || this._closing || this._disconnecting)
882
+ return;
883
+ const abort = new AbortController();
884
+ this._credentialRenewalAbort = abort;
885
+ const current = () => this._credentialRenewalAbort === abort && !abort.signal.aborted && aid === this._aid && version === this._serviceEventIdentityVersion && this._state === 'connected' && !this._closing && !this._disconnecting;
886
+ const guard = { current, signal: abort.signal };
887
+ const scan = async () => {
888
+ const task = this._credentialRenewalScan.then(async () => {
889
+ if (!current())
890
+ return;
891
+ await this._credentialSigningIdentity(aid, guard);
892
+ if (current())
893
+ await this._safeAsync(this._getCredentialRenewal()._syncRevocations?.(guard) ?? Promise.resolve());
894
+ if (current())
895
+ await this._getCredentialRenewal().resume(guard);
896
+ });
897
+ this._credentialRenewalScan = task.catch(() => { });
898
+ this._safeAsync(task);
899
+ await task.catch(() => { });
900
+ if (current())
901
+ this._credentialRenewalTimer = setTimeout(() => { void scan(); }, 60_000);
902
+ };
903
+ void scan();
904
+ }
905
+ _stopCredentialRenewal() {
906
+ this._credentialRenewalAbort?.abort();
907
+ this._credentialRenewalAbort = null;
908
+ if (this._credentialRenewalTimer !== null)
909
+ clearTimeout(this._credentialRenewalTimer);
910
+ this._credentialRenewalTimer = null;
911
+ }
912
+ _rememberCredential(credential, material, certificate) {
913
+ if (material.long_term === undefined && certificate) {
914
+ const pem = typeof certificate === 'string' ? certificate : arrayBufferToPem(exactArrayBuffer(certificate), 'CERTIFICATE');
915
+ const parent = Array.isArray(credential.proof_chain) ? credential.proof_chain[0] : undefined;
916
+ material = { ...material, long_term: credential.exp === Math.min(Math.floor(parseCertMetadata(pem).notAfter.getTime() / 1000), credential.parent != null && parent ? Number(parent.exp) : Infinity) };
917
+ }
918
+ this._safeAsync(this._getCredentialRenewal().remember(credential, material));
919
+ }
920
+ _credentialCurrent(input) {
921
+ return this._getCredentialRenewal().current(input);
922
+ }
923
+ async _handleCredentialRenewal(message) {
924
+ if (!isJsonObject(message))
925
+ return false;
926
+ const envelope = message;
927
+ const content = 'payload' in envelope ? envelope.payload : envelope;
928
+ const payload = decryptedOwnershipJsonText(content) ?? content;
929
+ let parsed = payload;
930
+ if (typeof payload === 'string') {
931
+ try {
932
+ parsed = JSON.parse(payload);
933
+ }
934
+ catch {
935
+ return false;
936
+ }
937
+ }
938
+ if (!isJsonObject(parsed) || !['credential.renewal.request', 'credential.renewal.result'].includes(String(parsed.type)))
939
+ return false;
940
+ if (Number(parsed.exp) <= Date.now() / 1000)
941
+ return true;
942
+ try {
943
+ await this._getCredentialRenewal().receive(String(envelope.from ?? envelope.from_aid ?? envelope.sender_aid ?? ''), parsed);
944
+ }
945
+ catch { }
946
+ return true;
947
+ }
779
948
  _orgFacade;
780
949
  _groupInvitePending = new Map();
781
950
  _groupInviteStateAid = null;
@@ -982,6 +1151,21 @@ export class AUNClient {
982
1151
  this._collab = new CollabClient(this);
983
1152
  return this._collab;
984
1153
  }
1154
+ get debug() {
1155
+ if (!this._debugFacade)
1156
+ this._debugFacade = new DebugFacade(this, async (operation) => {
1157
+ const transport = this._transport;
1158
+ const previous = transport.getTraceMode();
1159
+ transport.setTraceMode('diag');
1160
+ try {
1161
+ return await operation();
1162
+ }
1163
+ finally {
1164
+ transport.setTraceMode(previous);
1165
+ }
1166
+ });
1167
+ return this._debugFacade;
1168
+ }
985
1169
  /** Message RPC facade 入口 */
986
1170
  get message() {
987
1171
  if (!this._messageFacade)
@@ -1240,9 +1424,9 @@ export class AUNClient {
1240
1424
  return this._currentAid;
1241
1425
  },
1242
1426
  readAgentMdFresh: (aid) => this._agentMdManager.readOwnershipFresh(aid),
1243
- submitAgentMdRaw: (aid, raw, condition) => this._agentMdManager.submitOwnershipRaw(aid, raw, condition),
1427
+ submitAgentMdRaw: (aid, raw, condition, guard) => this._agentMdManager.submitOwnershipRaw(aid, raw, condition, guard),
1244
1428
  readOwnerMd: (owner, sub) => storage.readOwnerMd(owner, sub),
1245
- writeOwnerMd: (owner, sub, raw) => storage.writeOwnerMd(owner, sub, raw),
1429
+ writeOwnerMd: (owner, sub, raw, original, guard) => storage.writeOwnerMd(owner, sub, raw, original, guard),
1246
1430
  deleteOwnerMd: (owner, sub) => storage.deleteOwnerMd(owner, sub),
1247
1431
  resolveCertificate: async (aid, fingerprint, keyPurpose) => this._auth.resolveOwnershipCertificate(await this._resolveOwnershipCertificateGateway(aid), aid, fingerprint, keyPurpose),
1248
1432
  });
@@ -1257,6 +1441,7 @@ export class AUNClient {
1257
1441
  }
1258
1442
  async bindOwneraid(ownerAid) { return await this._ownershipV21().bindOwneraid(ownerAid); }
1259
1443
  async bindSubaid(subAid) { return await this._ownershipV21().bindSubaid(subAid); }
1444
+ async resetOwnershipTask() { return await this._ownershipV21().resetOwnershipTask(); }
1260
1445
  async releaseSubaid(subAid) { return await this._ownershipV21().releaseSubaid(subAid); }
1261
1446
  async transferSubaid(subAid, newOwnerAid) { return await this._ownershipV21().transferSubaid(subAid, newOwnerAid); }
1262
1447
  async checkOwnershipConsensus(subAid) { return await this._ownershipV21().checkOwnershipConsensus(subAid); }
@@ -1805,9 +1990,13 @@ export class AUNClient {
1805
1990
  loadIdentity(aid) {
1806
1991
  const previousAid = this._aid;
1807
1992
  this._identityRuntime.loadIdentity(aid);
1993
+ this._stopCredentialRenewal();
1808
1994
  this._dispatcher.setEventIdentity(this._aid ?? '');
1809
- if (previousAid !== this._aid)
1995
+ if (previousAid !== this._aid) {
1810
1996
  this._serviceEventIdentityVersion++;
1997
+ this._serviceEventIdentityAbort.abort();
1998
+ this._serviceEventIdentityAbort = new AbortController();
1999
+ }
1811
2000
  this._ensureReadStateIdentity();
1812
2001
  if (previousAid !== this._aid) {
1813
2002
  this._ownershipRecords.clear();
@@ -1882,6 +2071,10 @@ export class AUNClient {
1882
2071
  ? this._ensureReadStateIdentity() : null;
1883
2072
  try {
1884
2073
  const result = await this._rpcPipeline.call(method, params);
2074
+ try {
2075
+ await this._getCredentialRenewal().observe(method, params ?? {}, result);
2076
+ }
2077
+ catch { }
1885
2078
  if (readIdentity !== null && readIdentity === this._ensureReadStateIdentity())
1886
2079
  this._observeReadStateResult(method, result, params ?? {});
1887
2080
  return result;
@@ -2053,12 +2246,46 @@ export class AUNClient {
2053
2246
  this._groupIdentityOperations.delete(key);
2054
2247
  }
2055
2248
  }
2249
+ async _resumeOrgRequest(method, params, target) {
2250
+ const binding = params.request_binding;
2251
+ const now = Math.trunc(Date.now() / 1000);
2252
+ if (!isJsonObject(binding) || now < Number(binding.exp))
2253
+ return params;
2254
+ const credentials = isJsonObject(params.credentials) ? params.credentials : {};
2255
+ const credential = ['member_credential', 'approval_credential', 'audit_credential'].map((field) => credentials[field]).find(isJsonObject) ?? params.credential_template;
2256
+ const cid = isJsonObject(credential) ? String(credential.cid) : '';
2257
+ validateRequestBinding(binding, method, target, params, cid, String(params.nonce), String(params.request_id), Number(binding.iat));
2258
+ let exp = now + 300;
2259
+ 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]))]) {
2260
+ if (isJsonObject(material)) {
2261
+ if (!Number.isInteger(material.exp) || Number(material.exp) <= now)
2262
+ throw new ValidationError('Org recovery authorization material has expired');
2263
+ exp = Math.min(exp, Number(material.exp));
2264
+ }
2265
+ }
2266
+ const refreshed = { ...params, request_binding: buildRequestBinding(method, target, params, cid, String(params.nonce), String(params.request_id), now, exp) };
2267
+ delete refreshed.client_signature;
2268
+ if (isJsonObject(credentials.representation_proof)) {
2269
+ if (!this._currentAid || !isJsonObject(credential))
2270
+ throw new ValidationError('Org recovery representation signer is unavailable');
2271
+ refreshed.credentials = { ...credentials, representation_proof: await createRepresentationProof(credential, refreshed, refreshed.request_binding, this._currentAid, refreshed.proof_chain) };
2272
+ }
2273
+ return refreshed;
2274
+ }
2056
2275
  async createOrg(params = {}, options = {}) {
2057
2276
  const store = options.aidStore;
2058
2277
  const owner = this._currentAid;
2059
2278
  const orgAid = String(params.org_id ?? '').trim();
2060
2279
  if (!store || !owner?.isPrivateKeyValid() || !orgAid)
2061
2280
  throw new ValidationError('createOrg requires aidStore, org_id and current owner identity');
2281
+ const tokens = store._tokenStore;
2282
+ const inputHash = await ownershipSha256(canonicalJson(params));
2283
+ const raw = await (typeof tokens._getMetadataStrict === 'function' ? tokens._getMetadataStrict(orgAid, 'org_identity_creation') : tokens.getMetadata(orgAid, 'org_identity_creation'));
2284
+ let checkpoint = raw ? JSON.parse(raw) : undefined;
2285
+ if (raw && !isJsonObject(checkpoint))
2286
+ throw new StateError('Org creation checkpoint is invalid', { stringCode: 'persistence_unavailable' });
2287
+ if (checkpoint && (checkpoint.owner_aid !== owner.aid || checkpoint.input_hash !== inputHash))
2288
+ throw new StateError('pending Org creation context changed', { stringCode: 'conflict', retryable: false });
2062
2289
  this._aidStore = store;
2063
2290
  let identity = await store.load(orgAid);
2064
2291
  if (!identity.ok) {
@@ -2070,19 +2297,13 @@ export class AUNClient {
2070
2297
  if (!identity.ok)
2071
2298
  throw new StateError(identity.error.message, { stringCode: 'unavailable', retryable: true });
2072
2299
  const target = options.serviceAid ?? `org.${orgAid.slice(orgAid.indexOf('.') + 1)}`;
2073
- const tokens = store._tokenStore;
2074
- const inputHash = await ownershipSha256(canonicalJson(params));
2075
- const raw = await tokens.getMetadata(orgAid, 'org_identity_creation');
2076
- let checkpoint = raw ? JSON.parse(raw) : undefined;
2077
- if (checkpoint && (checkpoint.owner_aid !== owner.aid || checkpoint.input_hash !== inputHash))
2078
- throw new StateError('pending Org creation context changed', { stringCode: 'conflict', retryable: false });
2079
2300
  if (!checkpoint) {
2080
2301
  const prepared = await this.org.prepareRequest('org.lifecycle.create', { ...params, owner_aid: owner.aid }, target, owner, { orgSigner: identity.data.aid });
2081
2302
  checkpoint = { owner_aid: owner.aid, input_hash: inputHash, source_params: structuredClone(params), prepared, phase: 'create' };
2082
2303
  await tokens.setMetadata(orgAid, 'org_identity_creation', JSON.stringify(checkpoint));
2083
2304
  }
2084
2305
  if (checkpoint.phase === 'create') {
2085
- const result = await this.call('org.lifecycle.create', checkpoint.prepared);
2306
+ const result = await this.call('org.lifecycle.create', await this._resumeOrgRequest('org.lifecycle.create', checkpoint.prepared, target));
2086
2307
  checkpoint = { ...checkpoint, result, phase: 'upload' };
2087
2308
  await tokens.setMetadata(orgAid, 'org_identity_creation', JSON.stringify(checkpoint));
2088
2309
  }
@@ -2097,10 +2318,12 @@ export class AUNClient {
2097
2318
  return;
2098
2319
  for (const aid of await keyStore.listIdentities()) {
2099
2320
  try {
2100
- const creationRaw = await tokens.getMetadata(aid, 'org_identity_creation');
2101
- const transferRaw = await tokens.getMetadata(aid, 'org_identity_transfer');
2321
+ const creationRaw = await (typeof tokens._getMetadataStrict === 'function' ? tokens._getMetadataStrict(aid, 'org_identity_creation') : tokens.getMetadata(aid, 'org_identity_creation'));
2322
+ const transferRaw = await (typeof tokens._getMetadataStrict === 'function' ? tokens._getMetadataStrict(aid, 'org_identity_transfer') : tokens.getMetadata(aid, 'org_identity_transfer'));
2102
2323
  const creation = creationRaw ? JSON.parse(creationRaw) : undefined;
2103
2324
  const transfer = transferRaw ? JSON.parse(transferRaw) : undefined;
2325
+ if (creationRaw && !isJsonObject(creation) || transferRaw && !isJsonObject(transfer))
2326
+ throw new StateError('Org identity checkpoint is invalid', { stringCode: 'persistence_unavailable' });
2104
2327
  if (creation?.owner_aid === this._aid) {
2105
2328
  await this.createOrg(creation.source_params, { aidStore: store });
2106
2329
  continue;
@@ -2123,6 +2346,40 @@ export class AUNClient {
2123
2346
  }
2124
2347
  }
2125
2348
  }
2349
+ async _verifyOrgTransferMaterials(params, ownerAid) {
2350
+ const orgAid = String(params.org_id);
2351
+ const previous = params.previous_owner;
2352
+ const next = params.new_owner;
2353
+ const binding = previous.membership_binding;
2354
+ const nextBinding = next.membership_binding;
2355
+ const credential = previous.credential_template;
2356
+ const ext = isJsonObject(credential.ext) ? credential.ext : {};
2357
+ const capability = Array.isArray(credential.cap) && credential.cap.length === 1 && isJsonObject(credential.cap[0]) ? credential.cap[0] : {};
2358
+ const now = Math.trunc(Date.now() / 1000);
2359
+ if (binding.org_id !== orgAid || nextBinding.org_id !== orgAid || binding.member_aid === ownerAid || nextBinding.member_aid !== ownerAid
2360
+ || binding.role !== 'member' || nextBinding.role !== 'owner' || digest(previous.terms) !== binding.terms_hash || digest(next.terms) !== nextBinding.terms_hash
2361
+ || !Number.isSafeInteger(nextBinding.iat) || !Number.isSafeInteger(nextBinding.exp) || Number(nextBinding.iat) > now || Number(nextBinding.exp) <= now
2362
+ || credential.iss !== orgAid || credential.sub !== binding.member_aid || credential.parent != null || credential.delegate !== false
2363
+ || ext.credential_type !== 'member' || ext.bound_to_membership !== true || ext.org_id !== orgAid || ext.membership_id !== binding.membership_id
2364
+ || ext.binding_version !== binding.binding_version || ext.membership_terms_hash !== binding.terms_hash
2365
+ || capability.ns !== 'org' || capability.action !== 'role.member' || capability.resource !== orgAid)
2366
+ throw new StateError('Org transfer membership materials do not match', { stringCode: 'invalid', retryable: false });
2367
+ validateMembershipBinding(binding);
2368
+ const certificates = new Map();
2369
+ for (const [aid, signature] of [[binding.member_aid, binding.member_signature], [orgAid, binding.org_signature], [orgAid, credential.signature]]) {
2370
+ if (!isJsonObject(signature))
2371
+ throw new StateError('Org transfer membership materials do not match', { stringCode: 'invalid', retryable: false });
2372
+ const fingerprint = String(signature.cert_fingerprint);
2373
+ const certificate = await this._auth.resolveOwnershipCertificate(await this._resolveOwnershipCertificateGateway(String(aid)), String(aid), fingerprint);
2374
+ certificates.set(fingerprint, certificate.cert_pem);
2375
+ }
2376
+ const resolve = (fingerprint) => certificates.get(fingerprint);
2377
+ const checkedAt = Math.trunc(Date.now() / 1000);
2378
+ await verifyMembershipBinding(binding, resolve, checkedAt, orgAid);
2379
+ await verifyCredentialSignature(credential, resolve, checkedAt, orgAid);
2380
+ if (Number(nextBinding.exp) <= checkedAt)
2381
+ throw new StateError('Org transfer membership materials do not match', { stringCode: 'invalid', retryable: false });
2382
+ }
2126
2383
  async completeOrgTransfer(params = {}, options = {}) {
2127
2384
  const store = options.aidStore;
2128
2385
  const owner = this._currentAid;
@@ -2139,10 +2396,14 @@ export class AUNClient {
2139
2396
  const localStore = store._keystore;
2140
2397
  const tokenStore = store._tokenStore;
2141
2398
  const inputHash = await ownershipSha256(canonicalJson(params));
2142
- const raw = await tokenStore.getMetadata(orgAid, 'org_identity_transfer');
2399
+ const raw = await (typeof tokenStore._getMetadataStrict === 'function' ? tokenStore._getMetadataStrict(orgAid, 'org_identity_transfer') : tokenStore.getMetadata(orgAid, 'org_identity_transfer'));
2143
2400
  let pending = raw ? JSON.parse(raw) : undefined;
2401
+ if (raw && !isJsonObject(pending))
2402
+ throw new StateError('Org transfer checkpoint is invalid', { stringCode: 'persistence_unavailable' });
2144
2403
  if (pending && (pending.transfer_id !== transferId || pending.owner_aid !== owner.aid || pending.input_hash !== inputHash))
2145
2404
  throw new StateError('pending Org transfer context changed', { stringCode: 'conflict', retryable: false });
2405
+ if (!pending)
2406
+ await this._verifyOrgTransferMaterials(params, owner.aid);
2146
2407
  const pendingKey = `org-transfer:${orgAid}:${transferId}`;
2147
2408
  let keyPair = await localStore.loadPendingGroupBind(pendingKey);
2148
2409
  if (!keyPair) {
@@ -2164,7 +2425,7 @@ export class AUNClient {
2164
2425
  await tokenStore.setMetadata(orgAid, 'org_identity_transfer', JSON.stringify(pending));
2165
2426
  }
2166
2427
  if (pending.phase === 'rekey') {
2167
- const result = await this.call('org.lifecycle.transferAccept', pending.rekey_params);
2428
+ const result = await this.call('org.lifecycle.transferAccept', await this._resumeOrgRequest('org.lifecycle.transferAccept', pending.rekey_params, target));
2168
2429
  pending = { ...pending, phase: 'finalize', rekey_result: result };
2169
2430
  await tokenStore.setMetadata(orgAid, 'org_identity_transfer', JSON.stringify(pending));
2170
2431
  }
@@ -2201,7 +2462,7 @@ export class AUNClient {
2201
2462
  pending = { ...pending, finalize_params: await this.org.prepareRequest('org.lifecycle.transferAccept', final, target, owner, { orgSigner: orgIdentity }) };
2202
2463
  await tokenStore.setMetadata(orgAid, 'org_identity_transfer', JSON.stringify(pending));
2203
2464
  }
2204
- const result = await this.call('org.lifecycle.transferAccept', pending.finalize_params);
2465
+ const result = await this.call('org.lifecycle.transferAccept', await this._resumeOrgRequest('org.lifecycle.transferAccept', pending.finalize_params, target));
2205
2466
  await this._uploadOrgAgentMd(store, orgAid, pending.source_params);
2206
2467
  await tokenStore.setMetadata(orgAid, 'org_identity_transfer', '');
2207
2468
  await localStore.clearPendingGroupBind(pendingKey);
@@ -2478,6 +2739,7 @@ export class AUNClient {
2478
2739
  return await manager.resumePendingUpload(groupAid);
2479
2740
  }
2480
2741
  async _uploadGroupAgentMd(store, groupAid, params, group, uploaderAid) {
2742
+ this._aidStore = store;
2481
2743
  if (await this._resumeGroupAgentMdUpload(store, groupAid))
2482
2744
  return;
2483
2745
  if (typeof store.uploadAgentMd !== 'function') {
@@ -3162,13 +3424,15 @@ export class AUNClient {
3162
3424
  async _fillP2pGap() {
3163
3425
  return this._delivery.fillP2pGap();
3164
3426
  }
3165
- _markPublishedSeq(ns, seq) {
3166
- this._delivery.markPublishedSeq(ns, seq);
3427
+ _markPublishedSeq(ns, seq, page) {
3428
+ this._delivery.markPublishedSeq(ns, seq, page);
3167
3429
  }
3168
- async _publishAppEvent(event, payload, source = 'direct', ns = '', batch) {
3430
+ async _publishAppEvent(event, payload, source = 'direct', ns = '', batch, page) {
3169
3431
  if (event === 'message.received' && await this._handleOwnershipMessage(payload))
3170
3432
  return;
3171
- await this._delivery.publishAppEvent(event, payload, source, ns, batch);
3433
+ if (event === 'message.received' && await this._handleCredentialRenewal(payload))
3434
+ return;
3435
+ await this._delivery.publishAppEvent(event, payload, source, ns, batch, page);
3172
3436
  }
3173
3437
  _echoTimestamp() {
3174
3438
  const now = new Date();
@@ -3209,14 +3473,14 @@ export class AUNClient {
3209
3473
  const trace = `${this._echoTimestamp()} [AUN-SDK.receive] aid=${this._aid ?? '-'} conn_uptime=${uptime}s`;
3210
3474
  msg.payload = { ...payload, text: payload.text + '\n' + trace };
3211
3475
  }
3212
- async _drainOrderedMessages(ns, beforeSeq, pullResponse = false, persist = true, throughSeq) {
3213
- await this._delivery.drainOrderedMessages(ns, beforeSeq, pullResponse, persist, undefined, 'pull', throughSeq);
3476
+ async _drainOrderedMessages(ns, beforeSeq, pullResponse = false, persist = true, throughSeq, page) {
3477
+ await this._delivery.drainOrderedMessages(ns, beforeSeq, pullResponse, persist, undefined, 'pull', throughSeq, page);
3214
3478
  }
3215
- async _publishOrderedMessage(event, ns, seq, payload, source = 'push', batch) {
3216
- return this._delivery.publishOrderedMessage(event, ns, seq, payload, source, batch);
3479
+ async _publishOrderedMessage(event, ns, seq, payload, source = 'push', batch, page) {
3480
+ return this._delivery.publishOrderedMessage(event, ns, seq, payload, source, batch, page);
3217
3481
  }
3218
- async _publishPulledMessage(event, ns, seq, payload, persist = true, source = 'pull', batch) {
3219
- return this._delivery.publishPulledMessage(event, ns, seq, payload, persist, source, batch);
3482
+ async _publishPulledMessage(event, ns, seq, payload, persist = true, source = 'pull', batch, page) {
3483
+ return this._delivery.publishPulledMessage(event, ns, seq, payload, persist, source, batch, page);
3220
3484
  }
3221
3485
  _extractGroupIdFromResult(result) {
3222
3486
  const group = isJsonObject(result.group) ? result.group : null;
@@ -3679,9 +3943,17 @@ export class AUNClient {
3679
3943
  async _verifyIncomingServiceEvent(event, payload) {
3680
3944
  const targetAid = this._aid || this._currentAid?.aid || '';
3681
3945
  const version = this._serviceEventIdentityVersion;
3946
+ const signal = this._serviceEventIdentityAbort.signal;
3947
+ const expiresAt = Number(payload?.expires_at);
3948
+ const current = () => version === this._serviceEventIdentityVersion && targetAid === (this._aid || this._currentAid?.aid || '') && Date.now() / 1000 < expiresAt;
3682
3949
  const verified = await verifyServiceEvent(event, payload, { targetAid, resolveEventCertificate: (service, fingerprint) => this._resolveServiceEventCertificate(service, fingerprint), resolveCredentialService: (cid) => this.resolveCredentialService?.(cid) });
3683
- if (version !== this._serviceEventIdentityVersion || targetAid !== (this._aid || this._currentAid?.aid || ''))
3950
+ if (!current())
3684
3951
  throw new ValidationError('service event receiver identity changed');
3952
+ if (event.replace(/^event\//, '') === 'org.member.changed' && isJsonObject(payload) && Object.hasOwn(payload, 'membership_package')) {
3953
+ await this._getCredentialRenewal().importMembershipPackage(payload, { current, signal });
3954
+ if (!current())
3955
+ throw new ValidationError('service event receiver identity changed');
3956
+ }
3685
3957
  return verified;
3686
3958
  }
3687
3959
  async _fetchPeerCert(aid, certFingerprint, timeoutMs = 5000, signedAt, agentMdRead = false) {
@@ -3695,62 +3967,75 @@ export class AUNClient {
3695
3967
  this._clientLog.debug(`_fetchPeerCert exit: elapsed=${Date.now() - tStart}ms aid=${aid} source=cache`);
3696
3968
  return cached.certPem;
3697
3969
  }
3698
- const gatewayUrl = this._gatewayUrl;
3699
- if (!gatewayUrl) {
3700
- throw new ValidationError('gateway url unavailable for e2ee cert fetch');
3701
- }
3702
- // 跨域时用 peer 所在域的 Gateway URL
3703
- const peerGatewayUrl = resolvePeerGatewayUrl(gatewayUrl, aid);
3704
- let certPem;
3705
- const certUrl = buildCertUrl(peerGatewayUrl, aid, certFingerprint);
3706
- // 兼容旧浏览器,不使用 AbortSignal.timeout(Chrome 103+ 才支持)
3707
- const controller = new AbortController();
3708
- const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
3709
- try {
3710
- const resp = await fetch(certUrl, { signal: controller.signal });
3711
- if (!resp.ok)
3712
- throw new ValidationError(`failed to fetch peer cert for ${aid}: HTTP ${resp.status}`);
3713
- certPem = await resp.text();
3714
- }
3715
- finally {
3716
- clearTimeout(timeoutId);
3717
- }
3718
- // H7: 严格校验指纹(DER SHA-256 或 SPKI SHA-256 任一匹配即可)
3719
- if (certFingerprint) {
3720
- const expectedFP = String(certFingerprint).trim().toLowerCase();
3721
- if (!normalizeFingerprintHex(expectedFP)) {
3722
- throw new ValidationError(`unsupported cert_fingerprint format for ${aid}: ${expectedFP.slice(0, 24)}`);
3970
+ const inFlightKey = `${cacheKey}:timeout:${timeoutMs}`;
3971
+ const pending = this._certFetchInFlight.get(inFlightKey);
3972
+ if (pending)
3973
+ return await pending;
3974
+ const request = (async () => {
3975
+ const gatewayUrl = this._gatewayUrl;
3976
+ if (!gatewayUrl) {
3977
+ throw new ValidationError('gateway url unavailable for e2ee cert fetch');
3723
3978
  }
3724
- if (!(await certMatchesFingerprint(certPem, expectedFP))) {
3725
- throw new ValidationError(`peer cert fingerprint mismatch for ${aid}: expected=${expectedFP.slice(0, 24)}...`);
3979
+ // 跨域时用 peer 所在域的 Gateway URL
3980
+ const peerGatewayUrl = resolvePeerGatewayUrl(gatewayUrl, aid);
3981
+ let certPem;
3982
+ const certUrl = buildCertUrl(peerGatewayUrl, aid, certFingerprint);
3983
+ // 兼容旧浏览器,不使用 AbortSignal.timeout(Chrome 103+ 才支持)
3984
+ const controller = new AbortController();
3985
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
3986
+ try {
3987
+ const resp = await fetch(certUrl, { signal: controller.signal });
3988
+ if (!resp.ok)
3989
+ throw new ValidationError(`failed to fetch peer cert for ${aid}: HTTP ${resp.status}`);
3990
+ certPem = await resp.text();
3726
3991
  }
3727
- }
3728
- // 完整 PKI 验证:链 + CRL + OCSP + AID 绑定
3729
- try {
3730
- if (signedAt === undefined)
3731
- await this._auth.verifyPeerCertificate(peerGatewayUrl, certPem, aid);
3732
- else if (agentMdRead)
3733
- await this._auth.verifyPeerCertificate(peerGatewayUrl, certPem, aid, signedAt, false, true);
3734
- else
3735
- await this._auth.verifyPeerCertificate(peerGatewayUrl, certPem, aid, signedAt);
3736
- }
3737
- catch (exc) {
3738
- throw new ValidationError(`peer cert verification failed for ${aid}: ${exc}`);
3739
- }
3740
- this._certCache.set(cacheKey, {
3741
- certPem,
3742
- validatedAt: now,
3743
- refreshAfter: now + PEER_CERT_CACHE_TTL,
3744
- });
3992
+ finally {
3993
+ clearTimeout(timeoutId);
3994
+ }
3995
+ // H7: 严格校验指纹(DER SHA-256 或 SPKI SHA-256 任一匹配即可)
3996
+ if (certFingerprint) {
3997
+ const expectedFP = String(certFingerprint).trim().toLowerCase();
3998
+ if (!normalizeFingerprintHex(expectedFP)) {
3999
+ throw new ValidationError(`unsupported cert_fingerprint format for ${aid}: ${expectedFP.slice(0, 24)}`);
4000
+ }
4001
+ if (!(await certMatchesFingerprint(certPem, expectedFP))) {
4002
+ throw new ValidationError(`peer cert fingerprint mismatch for ${aid}: expected=${expectedFP.slice(0, 24)}...`);
4003
+ }
4004
+ }
4005
+ // 完整 PKI 验证:链 + CRL + OCSP + AID 绑定
4006
+ try {
4007
+ if (signedAt === undefined)
4008
+ await this._auth.verifyPeerCertificate(peerGatewayUrl, certPem, aid);
4009
+ else if (agentMdRead)
4010
+ await this._auth.verifyPeerCertificate(peerGatewayUrl, certPem, aid, signedAt, false, true);
4011
+ else
4012
+ await this._auth.verifyPeerCertificate(peerGatewayUrl, certPem, aid, signedAt);
4013
+ }
4014
+ catch (exc) {
4015
+ throw new ValidationError(`peer cert verification failed for ${aid}: ${exc}`);
4016
+ }
4017
+ this._certCache.set(cacheKey, {
4018
+ certPem,
4019
+ validatedAt: now,
4020
+ refreshAfter: now + PEER_CERT_CACHE_TTL,
4021
+ });
4022
+ try {
4023
+ // peer 证书只存版本目录,不覆盖 cert.pem
4024
+ await this._tokenStore.saveCert(aid, certPem, certFingerprint, { makeActive: false });
4025
+ }
4026
+ catch (exc) {
4027
+ this._clientLog.error(`write cert to keystore failed (aid=${aid}): ${String(exc)}`, exc instanceof Error ? exc : undefined);
4028
+ }
4029
+ this._clientLog.debug(`_fetchPeerCert exit: elapsed=${Date.now() - tStart}ms aid=${aid} source=fetched`);
4030
+ return certPem;
4031
+ })();
4032
+ this._certFetchInFlight.set(inFlightKey, request);
3745
4033
  try {
3746
- // peer 证书只存版本目录,不覆盖 cert.pem
3747
- await this._tokenStore.saveCert(aid, certPem, certFingerprint, { makeActive: false });
4034
+ return await request;
3748
4035
  }
3749
- catch (exc) {
3750
- this._clientLog.error(`write cert to keystore failed (aid=${aid}): ${String(exc)}`, exc instanceof Error ? exc : undefined);
4036
+ finally {
4037
+ this._certFetchInFlight.delete(inFlightKey);
3751
4038
  }
3752
- this._clientLog.debug(`_fetchPeerCert exit: elapsed=${Date.now() - tStart}ms aid=${aid} source=fetched`);
3753
- return certPem;
3754
4039
  }
3755
4040
  catch (err) {
3756
4041
  this._clientLog.debug(`_fetchPeerCert exit (error): elapsed=${Date.now() - tStart}ms aid=${aid} err=${err instanceof Error ? err.message : String(err)}`);
@@ -4169,6 +4454,7 @@ export class AUNClient {
4169
4454
  }
4170
4455
  // ── 内部:后台任务 ────────────────────────────────
4171
4456
  _startBackgroundTasks() {
4457
+ this._startCredentialRenewal();
4172
4458
  this._startOwnershipRecovery();
4173
4459
  // WebSocket Ping/Pong 由 Gateway 与传输层处理;SDK 不发送 RPC 心跳。
4174
4460
  // 短连接生命周期短,不需要 token 刷新。
@@ -4198,6 +4484,7 @@ export class AUNClient {
4198
4484
  }
4199
4485
  }
4200
4486
  _stopBackgroundTasks() {
4487
+ this._stopCredentialRenewal();
4201
4488
  this._ownershipRecoveryGeneration += 1;
4202
4489
  if (this._ownershipRecoveryTimer !== null) {
4203
4490
  clearTimeout(this._ownershipRecoveryTimer);
@@ -4951,7 +5238,7 @@ export class AUNClient {
4951
5238
  * undecryptable 事件、成功消费后触发 SPK 轮换。inline Push 在游标提交和
4952
5239
  * Head/正文绑定校验前调用本入口时必须显式关闭这些副作用。
4953
5240
  */
4954
- async _decryptV2Message(msg, allowPending = true, emitUndecryptable = true, rotateKeys = true, observeAgentMd = true, deferStatus, deferKeyFetch = false, source = 'pull') {
5241
+ async _decryptV2Message(msg, allowPending = true, emitUndecryptable = true, rotateKeys = true, observeAgentMd = true, deferStatus, deferKeyFetch = false, source = 'pull', deferredEffects) {
4955
5242
  const session = this._v2Session;
4956
5243
  if (!session)
4957
5244
  return null;
@@ -4967,8 +5254,12 @@ export class AUNClient {
4967
5254
  return null;
4968
5255
  }
4969
5256
  const e2eeMeta = v2E2eeMeta(envelope);
4970
- if (observeAgentMd)
4971
- await this._observeAgentMdFromEnvelope(envelope);
5257
+ if (observeAgentMd) {
5258
+ if (deferredEffects)
5259
+ deferredEffects.push(() => this._observeAgentMdFromEnvelope(envelope));
5260
+ else
5261
+ await this._observeAgentMdFromEnvelope(envelope);
5262
+ }
4972
5263
  // 确定 spk_id 和 recipient_key_source
4973
5264
  let spkId = '';
4974
5265
  let recipientKeySource = '';
@@ -5053,7 +5344,10 @@ export class AUNClient {
5053
5344
  source,
5054
5345
  };
5055
5346
  attachV2EnvelopeMetadata(event, e2eeMeta);
5056
- this._dispatcher.enqueue(undecryptableEvent, event);
5347
+ if (deferredEffects)
5348
+ deferredEffects.push(() => this._dispatcher.enqueue(undecryptableEvent, event));
5349
+ else
5350
+ this._dispatcher.enqueue(undecryptableEvent, event);
5057
5351
  }
5058
5352
  catch { /* publish 异常不影响主流程 */ }
5059
5353
  }
@@ -5094,7 +5388,10 @@ export class AUNClient {
5094
5388
  source,
5095
5389
  };
5096
5390
  attachV2EnvelopeMetadata(event, e2eeMeta);
5097
- this._dispatcher.enqueue(undecryptableEvent, event);
5391
+ if (deferredEffects)
5392
+ deferredEffects.push(() => this._dispatcher.enqueue(undecryptableEvent, event));
5393
+ else
5394
+ this._dispatcher.enqueue(undecryptableEvent, event);
5098
5395
  }
5099
5396
  catch { /* publish 异常不影响主流程 */ }
5100
5397
  }
@@ -5126,7 +5423,10 @@ export class AUNClient {
5126
5423
  source,
5127
5424
  };
5128
5425
  attachV2EnvelopeMetadata(event, e2eeMeta);
5129
- this._dispatcher.enqueue(undecryptableEvent, event);
5426
+ if (deferredEffects)
5427
+ deferredEffects.push(() => this._dispatcher.enqueue(undecryptableEvent, event));
5428
+ else
5429
+ this._dispatcher.enqueue(undecryptableEvent, event);
5130
5430
  }
5131
5431
  catch { /* publish 异常不影响主流程 */ }
5132
5432
  }
@@ -5135,8 +5435,12 @@ export class AUNClient {
5135
5435
  if (plaintext == null)
5136
5436
  return null;
5137
5437
  // 消费触发 SPK 轮换(fire-and-forget,不阻塞消息处理)
5138
- if (rotateKeys)
5139
- this._scheduleV2KeyRotationForMessage(msg, envelope);
5438
+ if (rotateKeys) {
5439
+ if (deferredEffects)
5440
+ deferredEffects.push(() => this._scheduleV2KeyRotationForMessage(msg, envelope));
5441
+ else
5442
+ this._scheduleV2KeyRotationForMessage(msg, envelope);
5443
+ }
5140
5444
  const e2ee = v2E2eeMeta(envelope);
5141
5445
  let result = {
5142
5446
  message_id: String(msg.message_id ?? ''),