@agentunion/fastaun-browser 0.5.19 → 0.5.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. package/CHANGELOG.md +176 -78
  2. package/_packed_docs/CHANGELOG.md +176 -78
  3. package/_packed_docs/INDEX.md +288 -269
  4. package/_packed_docs/KITE_DOCS_GUIDE.md +101 -95
  5. package/_packed_docs/agent.md/SCHEMA.md +7 -7
  6. package/_packed_docs/agent.md/working/AID/347/261/273/345/236/213-owner/345/205/263/347/263/273/344/270/216/346/215/242owner/347/274/226/346/216/222/346/226/271/346/241/210.md +530 -0
  7. package/_packed_docs/agent.md/working/AID/347/261/273/345/236/213-owner/345/205/263/347/263/273/345/256/214/346/225/264/350/256/276/350/256/241/344/270/216/345/256/236/346/226/275/350/256/241/345/210/222.md +685 -0
  8. package/_packed_docs/agent.md/working/agent.md-schema/344/270/216/347/255/276/345/217/221/351/252/214/347/255/276/346/265/201/347/250/213/346/276/204/346/270/205.md +349 -0
  9. package/_packed_docs/agent.md/working//346/234/254/345/234/260Docker/346/265/213/350/257/225Bug/346/270/205/345/215/225.md +40 -0
  10. package/_packed_docs/agent.md//350/277/234/347/250/213agent.md/347/274/223/345/255/230/344/270/216etag/351/200/217/344/274/240/346/226/271/346/241/210.md +11 -11
  11. package/_packed_docs/protocol/06-/346/234/215/345/212/241/345/215/217/350/256/256.md +372 -331
  12. package/_packed_docs/protocol/08-AUN-E2EE.md +17 -12
  13. package/_packed_docs/protocol/10-Group-/345/255/220/345/215/217/350/256/256.md +676 -676
  14. package/_packed_docs/protocol/11-Storage-/345/255/220/345/215/217/350/256/256.md +8 -12
  15. package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/211.md +445 -0
  16. package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2112.0.md +161 -0
  17. package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2112.1.md +666 -0
  18. package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2113.0.md +233 -0
  19. package/_packed_docs/protocol/agent.md/SCHEMA.md +1 -1
  20. package/_packed_docs/sdk/0.5.21-SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +368 -0
  21. package/_packed_docs/sdk/0.5.21-SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +350 -0
  22. package/_packed_docs/sdk/01-/345/277/253/351/200/237/345/274/200/345/247/213.md +1 -1
  23. package/_packed_docs/sdk/02-WebSocket/345/215/217/350/256/256.md +5 -1
  24. package/_packed_docs/sdk/03-/346/240/270/345/277/203/346/246/202/345/277/265.md +64 -64
  25. package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +70 -63
  26. package/_packed_docs/sdk/05-E2EE/345/212/240/345/257/206/351/200/232/344/277/241.md +43 -40
  27. package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +179 -19
  28. package/_packed_docs/sdk/08-/346/234/200/344/275/263/345/256/236/350/267/265.md +50 -49
  29. package/_packed_docs/sdk/09-group-rpc-manual.md +261 -254
  30. package/_packed_docs/sdk/09-message-rpc-manual.md +253 -160
  31. package/_packed_docs/sdk/09-storage-rpc-manual.md +18 -26
  32. package/_packed_docs/sdk/09-/345/207/255/350/257/201/346/234/215/345/212/241RPC/346/211/213/345/206/214.md +71 -0
  33. package/_packed_docs/sdk/09-/347/273/204/347/273/207/346/234/215/345/212/241RPC/346/211/213/345/206/214.md +88 -0
  34. package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +105 -92
  35. package/_packed_docs/sdk/INDEX.md +197 -167
  36. package/_packed_docs/sdk/Ownership-SDK/345/256/236/347/216/260/346/214/207/345/215/227.md +169 -0
  37. package/_packed_docs/sdk/Ownership-SDK/347/274/226/346/216/222/346/214/207/345/215/227.md +349 -0
  38. package/_packed_docs/sdk/Ownership-SDK/347/274/226/346/216/222/346/214/207/345/215/2272.1.md +930 -0
  39. package/_packed_docs/sdk/Ownership-SDK/351/233/206/346/210/220/346/214/207/345/215/227.md +270 -0
  40. package/_packed_docs/sdk/Ownership2.1-SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +363 -0
  41. package/_packed_docs/sdk/Ownership2.1-SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +553 -0
  42. package/_packed_docs/sdk/Ownership2.1-TDD/345/210/206/351/230/266/346/256/265/345/256/236/346/226/275/350/256/241/345/210/222.md +708 -0
  43. package/_packed_docs/sdk/Ownership2.1/350/257/246/347/273/206/350/256/276/350/256/241.md +480 -0
  44. package/_packed_docs/sdk/Ownership/347/256/241/347/220/206/344/270/223/351/242/230.md +559 -0
  45. package/_packed_docs/sdk/ProtectedHeaders/347/224/250/346/263/225/344/270/223/351/242/230.md +299 -0
  46. package/_packed_docs/sdk//345/207/255/350/257/201/344/270/216/347/273/204/347/273/207/346/234/215/345/212/241SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +173 -0
  47. package/_packed_docs/sdk//345/207/255/350/257/201/344/270/216/347/273/204/347/273/207/346/234/215/345/212/241SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +211 -0
  48. package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273SDK/345/256/236/347/216/260/347/247/273/346/244/215/346/214/207/345/215/227.md +331 -0
  49. package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273SDK/351/233/206/346/210/220/346/214/207/345/215/227.md +207 -0
  50. package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273/344/273/243/347/240/201/345/256/241/346/237/245/346/212/245/345/221/212.md +252 -0
  51. package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273/345/256/236/346/226/275/350/256/241/345/210/222.md +156 -0
  52. package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273/346/226/271/346/241/210.md +273 -0
  53. package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273/350/257/246/347/273/206/350/256/276/350/256/241.md +211 -0
  54. package/dist/agent-md-schema.d.ts +16 -4
  55. package/dist/agent-md-schema.d.ts.map +1 -1
  56. package/dist/agent-md-schema.js +65 -30
  57. package/dist/agent-md-schema.js.map +1 -1
  58. package/dist/agent-md.d.ts +35 -3
  59. package/dist/agent-md.d.ts.map +1 -1
  60. package/dist/agent-md.js +384 -150
  61. package/dist/agent-md.js.map +1 -1
  62. package/dist/aid-store.d.ts +59 -0
  63. package/dist/aid-store.d.ts.map +1 -1
  64. package/dist/aid-store.js +202 -16
  65. package/dist/aid-store.js.map +1 -1
  66. package/dist/aid.d.ts +4 -1
  67. package/dist/aid.d.ts.map +1 -1
  68. package/dist/aid.js +24 -42
  69. package/dist/aid.js.map +1 -1
  70. package/dist/auth.d.ts +1 -1
  71. package/dist/auth.d.ts.map +1 -1
  72. package/dist/auth.js +89 -18
  73. package/dist/auth.js.map +1 -1
  74. package/dist/bundle.js +6410 -1848
  75. package/dist/cert-utils.d.ts +50 -1
  76. package/dist/cert-utils.d.ts.map +1 -1
  77. package/dist/cert-utils.js +123 -56
  78. package/dist/cert-utils.js.map +1 -1
  79. package/dist/client/delivery.d.ts +108 -16
  80. package/dist/client/delivery.d.ts.map +1 -1
  81. package/dist/client/delivery.js +1411 -184
  82. package/dist/client/delivery.js.map +1 -1
  83. package/dist/client/rpc-pipeline.d.ts +4 -1
  84. package/dist/client/rpc-pipeline.d.ts.map +1 -1
  85. package/dist/client/rpc-pipeline.js +203 -117
  86. package/dist/client/rpc-pipeline.js.map +1 -1
  87. package/dist/client/v2-e2ee.d.ts +22 -3
  88. package/dist/client/v2-e2ee.d.ts.map +1 -1
  89. package/dist/client/v2-e2ee.js +1312 -684
  90. package/dist/client/v2-e2ee.js.map +1 -1
  91. package/dist/client.d.ts +23 -2
  92. package/dist/client.d.ts.map +1 -1
  93. package/dist/client.js +419 -101
  94. package/dist/client.js.map +1 -1
  95. package/dist/config.d.ts +4 -0
  96. package/dist/config.d.ts.map +1 -1
  97. package/dist/config.js +1 -0
  98. package/dist/config.js.map +1 -1
  99. package/dist/crypto.d.ts.map +1 -1
  100. package/dist/crypto.js +8 -6
  101. package/dist/crypto.js.map +1 -1
  102. package/dist/events.d.ts +24 -3
  103. package/dist/events.d.ts.map +1 -1
  104. package/dist/events.js +171 -29
  105. package/dist/events.js.map +1 -1
  106. package/dist/facades.d.ts +42 -1
  107. package/dist/facades.d.ts.map +1 -1
  108. package/dist/facades.js +368 -12
  109. package/dist/facades.js.map +1 -1
  110. package/dist/index.d.ts +6 -4
  111. package/dist/index.d.ts.map +1 -1
  112. package/dist/index.js +1 -1
  113. package/dist/index.js.map +1 -1
  114. package/dist/keystore/index.d.ts +14 -1
  115. package/dist/keystore/index.d.ts.map +1 -1
  116. package/dist/keystore/indexeddb-identity-store.d.ts +4 -0
  117. package/dist/keystore/indexeddb-identity-store.d.ts.map +1 -1
  118. package/dist/keystore/indexeddb-identity-store.js +30 -13
  119. package/dist/keystore/indexeddb-identity-store.js.map +1 -1
  120. package/dist/keystore/indexeddb-shared.d.ts +19 -2
  121. package/dist/keystore/indexeddb-shared.d.ts.map +1 -1
  122. package/dist/keystore/indexeddb-shared.js +193 -6
  123. package/dist/keystore/indexeddb-shared.js.map +1 -1
  124. package/dist/keystore/indexeddb-token-store.d.ts +15 -2
  125. package/dist/keystore/indexeddb-token-store.d.ts.map +1 -1
  126. package/dist/keystore/indexeddb-token-store.js +308 -27
  127. package/dist/keystore/indexeddb-token-store.js.map +1 -1
  128. package/dist/org-protocol.d.ts +45 -0
  129. package/dist/org-protocol.d.ts.map +1 -1
  130. package/dist/org-protocol.js +827 -21
  131. package/dist/org-protocol.js.map +1 -1
  132. package/dist/ownership-v21.d.ts +43 -9
  133. package/dist/ownership-v21.d.ts.map +1 -1
  134. package/dist/ownership-v21.js +183 -81
  135. package/dist/ownership-v21.js.map +1 -1
  136. package/dist/ownership-workflow.d.ts +36 -1
  137. package/dist/ownership-workflow.d.ts.map +1 -1
  138. package/dist/ownership-workflow.js +610 -89
  139. package/dist/ownership-workflow.js.map +1 -1
  140. package/dist/seq-tracker.d.ts +2 -0
  141. package/dist/seq-tracker.d.ts.map +1 -1
  142. package/dist/seq-tracker.js +3 -2
  143. package/dist/seq-tracker.js.map +1 -1
  144. package/dist/tools/cross-sdk-agent.d.ts +61 -3
  145. package/dist/tools/cross-sdk-agent.d.ts.map +1 -1
  146. package/dist/tools/cross-sdk-agent.js +524 -45
  147. package/dist/tools/cross-sdk-agent.js.map +1 -1
  148. package/dist/transport.d.ts +4 -0
  149. package/dist/transport.d.ts.map +1 -1
  150. package/dist/transport.js +64 -30
  151. package/dist/transport.js.map +1 -1
  152. package/dist/version.d.ts +1 -1
  153. package/dist/version.js +1 -1
  154. 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;
@@ -809,7 +978,7 @@ export class AUNClient {
809
978
  this._logDiscovery = this._logger.for('aun_core.discovery');
810
979
  this._logEvents = this._logger.for('aun_core.events');
811
980
  this._clientLog.info(`AUNClient initialized: debug=${_debug} aunPath=${this.configModel.aunPath} aid=${initAid ?? '-'}`);
812
- this._dispatcher = new EventDispatcher();
981
+ this._dispatcher = new EventDispatcher(() => this._ensureReadStateIdentity());
813
982
  this._discovery = new GatewayDiscovery();
814
983
  this._tokenStore = new IndexedDBTokenStore();
815
984
  this._slotId = inputAid?.slotId || 'default';
@@ -845,7 +1014,7 @@ export class AUNClient {
845
1014
  },
846
1015
  auth: () => this._auth,
847
1016
  tokenStore: () => this._tokenStore,
848
- fetchPeerCert: (target, certFingerprint, signedAt) => this._fetchPeerCert(target, certFingerprint || undefined, undefined, signedAt),
1017
+ fetchPeerCert: (target, certFingerprint, signedAt) => this._fetchPeerCert(target, certFingerprint || undefined, undefined, signedAt, true),
849
1018
  entityOwnerResolver: (target, type) => this._resolveEntityAgentMdOwner(target, type),
850
1019
  entityIdentityGetter: (target, type) => this._freshEntityCertificate(target, type),
851
1020
  entitySignerResolver: (target, fingerprint) => this._freshEntitySigner(target, fingerprint),
@@ -857,6 +1026,7 @@ export class AUNClient {
857
1026
  this._transport.setServiceEventVerifier((event, payload) => this._verifyIncomingServiceEvent(event, payload));
858
1027
  this._transport.setProtocolDisconnectCallback((error, closeCode) => this._handleTransportDisconnect(error, closeCode));
859
1028
  this._transport._onForegroundDrained = () => this._delivery.onForegroundPullsDrained();
1029
+ this._transport._onRpcResponse = () => this._delivery.requestBackgroundPullRecovery();
860
1030
  this._transport.setProtocolMetaObserver((meta) => this._observeRpcMeta(meta).catch((exc) => {
861
1031
  this._clientLog.debug(`rpc meta observer skipped: ${String(exc)}`);
862
1032
  }));
@@ -981,6 +1151,21 @@ export class AUNClient {
981
1151
  this._collab = new CollabClient(this);
982
1152
  return this._collab;
983
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
+ }
984
1169
  /** Message RPC facade 入口 */
985
1170
  get message() {
986
1171
  if (!this._messageFacade)
@@ -1230,7 +1415,7 @@ export class AUNClient {
1230
1415
  }
1231
1416
  _ownershipV21() {
1232
1417
  if (!this._ownershipWorkflow) {
1233
- const storage = new OwnershipStorageAdapter({ currentAid: () => this._aid ?? '', call: (method, params) => this.call(method, params) });
1418
+ const storage = new OwnershipStorageAdapter({ currentAid: () => this._aid ?? '', call: (method, params) => this.call(method, params), httpPut: (url, data, headers) => this.storage.lowlevel.httpPut(url, data, headers) });
1234
1419
  const files = new OwnershipFiles({
1235
1420
  currentAidType: () => this.getType(),
1236
1421
  currentAid: () => {
@@ -1239,10 +1424,10 @@ export class AUNClient {
1239
1424
  return this._currentAid;
1240
1425
  },
1241
1426
  readAgentMdFresh: (aid) => this._agentMdManager.readOwnershipFresh(aid),
1242
- submitAgentMdRaw: (aid, raw, condition) => this._agentMdManager.submitOwnershipRaw(aid, raw, condition),
1427
+ submitAgentMdRaw: (aid, raw, condition, guard) => this._agentMdManager.submitOwnershipRaw(aid, raw, condition, guard),
1243
1428
  readOwnerMd: (owner, sub) => storage.readOwnerMd(owner, sub),
1244
- writeOwnerMd: (owner, sub, raw, revision) => storage.writeOwnerMd(owner, sub, raw, revision),
1245
- deleteOwnerMd: (owner, sub, revision) => storage.deleteOwnerMd(owner, sub, revision),
1429
+ writeOwnerMd: (owner, sub, raw, original, guard) => storage.writeOwnerMd(owner, sub, raw, original, guard),
1430
+ deleteOwnerMd: (owner, sub) => storage.deleteOwnerMd(owner, sub),
1246
1431
  resolveCertificate: async (aid, fingerprint, keyPurpose) => this._auth.resolveOwnershipCertificate(await this._resolveOwnershipCertificateGateway(aid), aid, fingerprint, keyPurpose),
1247
1432
  });
1248
1433
  this._ownershipWorkflow = new OwnershipWorkflow({
@@ -1256,6 +1441,7 @@ export class AUNClient {
1256
1441
  }
1257
1442
  async bindOwneraid(ownerAid) { return await this._ownershipV21().bindOwneraid(ownerAid); }
1258
1443
  async bindSubaid(subAid) { return await this._ownershipV21().bindSubaid(subAid); }
1444
+ async resetOwnershipTask() { return await this._ownershipV21().resetOwnershipTask(); }
1259
1445
  async releaseSubaid(subAid) { return await this._ownershipV21().releaseSubaid(subAid); }
1260
1446
  async transferSubaid(subAid, newOwnerAid) { return await this._ownershipV21().transferSubaid(subAid, newOwnerAid); }
1261
1447
  async checkOwnershipConsensus(subAid) { return await this._ownershipV21().checkOwnershipConsensus(subAid); }
@@ -1775,7 +1961,7 @@ export class AUNClient {
1775
1961
  },
1776
1962
  auth: () => this._auth,
1777
1963
  tokenStore: () => this._tokenStore,
1778
- fetchPeerCert: (target, certFingerprint, signedAt) => this._fetchPeerCert(target, certFingerprint || undefined, undefined, signedAt),
1964
+ fetchPeerCert: (target, certFingerprint, signedAt) => this._fetchPeerCert(target, certFingerprint || undefined, undefined, signedAt, true),
1779
1965
  entityOwnerResolver: (target, type) => this._resolveEntityAgentMdOwner(target, type),
1780
1966
  entityIdentityGetter: (target, type) => this._freshEntityCertificate(target, type),
1781
1967
  entitySignerResolver: (target, fingerprint) => this._freshEntitySigner(target, fingerprint),
@@ -1787,6 +1973,7 @@ export class AUNClient {
1787
1973
  this._transport.setServiceEventVerifier((event, payload) => this._verifyIncomingServiceEvent(event, payload));
1788
1974
  this._transport.setProtocolDisconnectCallback((error, closeCode) => this._handleTransportDisconnect(error, closeCode));
1789
1975
  this._transport._onForegroundDrained = () => this._delivery.onForegroundPullsDrained();
1976
+ this._transport._onRpcResponse = () => this._delivery.requestBackgroundPullRecovery();
1790
1977
  this._transport.setProtocolMetaObserver((meta) => this._observeRpcMeta(meta).catch((exc) => {
1791
1978
  this._clientLog.debug(`rpc meta observer skipped: ${String(exc)}`);
1792
1979
  }));
@@ -1803,9 +1990,13 @@ export class AUNClient {
1803
1990
  loadIdentity(aid) {
1804
1991
  const previousAid = this._aid;
1805
1992
  this._identityRuntime.loadIdentity(aid);
1993
+ this._stopCredentialRenewal();
1806
1994
  this._dispatcher.setEventIdentity(this._aid ?? '');
1807
- if (previousAid !== this._aid)
1995
+ if (previousAid !== this._aid) {
1808
1996
  this._serviceEventIdentityVersion++;
1997
+ this._serviceEventIdentityAbort.abort();
1998
+ this._serviceEventIdentityAbort = new AbortController();
1999
+ }
1809
2000
  this._ensureReadStateIdentity();
1810
2001
  if (previousAid !== this._aid) {
1811
2002
  this._ownershipRecords.clear();
@@ -1880,6 +2071,10 @@ export class AUNClient {
1880
2071
  ? this._ensureReadStateIdentity() : null;
1881
2072
  try {
1882
2073
  const result = await this._rpcPipeline.call(method, params);
2074
+ try {
2075
+ await this._getCredentialRenewal().observe(method, params ?? {}, result);
2076
+ }
2077
+ catch { }
1883
2078
  if (readIdentity !== null && readIdentity === this._ensureReadStateIdentity())
1884
2079
  this._observeReadStateResult(method, result, params ?? {});
1885
2080
  return result;
@@ -1923,6 +2118,8 @@ export class AUNClient {
1923
2118
  const store = this._aidStore;
1924
2119
  if (!groupAid || !store)
1925
2120
  return false;
2121
+ if (String((await (store._agentMdManager ?? this._agentMdManager).loadRecord(groupAid, true))?.local_draft_content ?? '').trim())
2122
+ return false;
1926
2123
  const loaded = await store.load(groupAid);
1927
2124
  if (!loaded.ok || !loaded.data?.aid?.isPrivateKeyValid())
1928
2125
  return false;
@@ -1938,6 +2135,8 @@ export class AUNClient {
1938
2135
  const target = message.slice(agentPrefix.length).trim();
1939
2136
  if (!target || target.toLowerCase() !== String(this._aid ?? '').trim().toLowerCase())
1940
2137
  return false;
2138
+ if (String((await this._agentMdManager.loadRecord(target, true))?.local_draft_content ?? '').trim())
2139
+ return false;
1941
2140
  const content = await this._agentMdManager.readContent(target) ?? buildDefaultAgentMd(target);
1942
2141
  if (!this._ordinaryAdmissionAgentMd(content, target))
1943
2142
  return false;
@@ -1953,12 +2152,13 @@ export class AUNClient {
1953
2152
  if (!aid)
1954
2153
  return;
1955
2154
  try {
1956
- const pending = await this._agentMdManager.pendingUpload(aid);
1957
- if (pending) {
2155
+ const draft = String((await this._agentMdManager.loadRecord(aid, true))?.local_draft_content ?? '').trim();
2156
+ const pending = draft ? null : await this._agentMdManager.pendingUpload(aid);
2157
+ if (!draft && pending) {
1958
2158
  if (this._ordinaryAdmissionAgentMd(pending.content, aid))
1959
2159
  await this._agentMdManager.resumePendingUpload(aid);
1960
2160
  }
1961
- else {
2161
+ else if (!draft) {
1962
2162
  const checked = await this._agentMdManager.check(aid, 0);
1963
2163
  if (!checked.remote_found) {
1964
2164
  const content = await this._agentMdManager.readContent(aid) ?? buildDefaultAgentMd(aid);
@@ -2012,6 +2212,8 @@ export class AUNClient {
2012
2212
  await this.bindGroupAid({ group_id: groupId, group_aid: groupAid }, { aidStore: store });
2013
2213
  return;
2014
2214
  }
2215
+ if (String((await (store._agentMdManager ?? this._agentMdManager).loadRecord(groupAid, true))?.local_draft_content ?? '').trim())
2216
+ return;
2015
2217
  if (await this._resumeGroupAgentMdUpload(store, groupAid))
2016
2218
  return;
2017
2219
  const checked = await this._agentMdManager.check(groupAid);
@@ -2044,12 +2246,46 @@ export class AUNClient {
2044
2246
  this._groupIdentityOperations.delete(key);
2045
2247
  }
2046
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
+ }
2047
2275
  async createOrg(params = {}, options = {}) {
2048
2276
  const store = options.aidStore;
2049
2277
  const owner = this._currentAid;
2050
2278
  const orgAid = String(params.org_id ?? '').trim();
2051
2279
  if (!store || !owner?.isPrivateKeyValid() || !orgAid)
2052
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 });
2053
2289
  this._aidStore = store;
2054
2290
  let identity = await store.load(orgAid);
2055
2291
  if (!identity.ok) {
@@ -2061,19 +2297,13 @@ export class AUNClient {
2061
2297
  if (!identity.ok)
2062
2298
  throw new StateError(identity.error.message, { stringCode: 'unavailable', retryable: true });
2063
2299
  const target = options.serviceAid ?? `org.${orgAid.slice(orgAid.indexOf('.') + 1)}`;
2064
- const tokens = store._tokenStore;
2065
- const inputHash = await ownershipSha256(canonicalJson(params));
2066
- const raw = await tokens.getMetadata(orgAid, 'org_identity_creation');
2067
- let checkpoint = raw ? JSON.parse(raw) : undefined;
2068
- if (checkpoint && (checkpoint.owner_aid !== owner.aid || checkpoint.input_hash !== inputHash))
2069
- throw new StateError('pending Org creation context changed', { stringCode: 'conflict', retryable: false });
2070
2300
  if (!checkpoint) {
2071
2301
  const prepared = await this.org.prepareRequest('org.lifecycle.create', { ...params, owner_aid: owner.aid }, target, owner, { orgSigner: identity.data.aid });
2072
2302
  checkpoint = { owner_aid: owner.aid, input_hash: inputHash, source_params: structuredClone(params), prepared, phase: 'create' };
2073
2303
  await tokens.setMetadata(orgAid, 'org_identity_creation', JSON.stringify(checkpoint));
2074
2304
  }
2075
2305
  if (checkpoint.phase === 'create') {
2076
- 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));
2077
2307
  checkpoint = { ...checkpoint, result, phase: 'upload' };
2078
2308
  await tokens.setMetadata(orgAid, 'org_identity_creation', JSON.stringify(checkpoint));
2079
2309
  }
@@ -2088,10 +2318,12 @@ export class AUNClient {
2088
2318
  return;
2089
2319
  for (const aid of await keyStore.listIdentities()) {
2090
2320
  try {
2091
- const creationRaw = await tokens.getMetadata(aid, 'org_identity_creation');
2092
- 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'));
2093
2323
  const creation = creationRaw ? JSON.parse(creationRaw) : undefined;
2094
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' });
2095
2327
  if (creation?.owner_aid === this._aid) {
2096
2328
  await this.createOrg(creation.source_params, { aidStore: store });
2097
2329
  continue;
@@ -2100,6 +2332,8 @@ export class AUNClient {
2100
2332
  await this.completeOrgTransfer(transfer.source_params, { aidStore: store });
2101
2333
  continue;
2102
2334
  }
2335
+ if (String((await (store._agentMdManager ?? this._agentMdManager).loadRecord(aid, true))?.local_draft_content ?? '').trim())
2336
+ continue;
2103
2337
  const pending = await store._agentMdManager.pendingUpload(aid);
2104
2338
  if (!pending)
2105
2339
  continue;
@@ -2112,6 +2346,40 @@ export class AUNClient {
2112
2346
  }
2113
2347
  }
2114
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
+ }
2115
2383
  async completeOrgTransfer(params = {}, options = {}) {
2116
2384
  const store = options.aidStore;
2117
2385
  const owner = this._currentAid;
@@ -2119,19 +2387,23 @@ export class AUNClient {
2119
2387
  const transferId = String(params.transfer_id ?? '').trim();
2120
2388
  if (!store || !owner?.isPrivateKeyValid() || !orgAid || !transferId)
2121
2389
  throw new ValidationError('completeOrgTransfer requires aidStore, org_id, transfer_id and current accepting owner');
2390
+ for (const field of ['previous_owner', 'new_owner']) {
2391
+ const material = params[field];
2392
+ if (!isJsonObject(material) || !isJsonObject(material.membership_binding) || !isJsonObject(material.terms) || field === 'previous_owner' && !isJsonObject(material.credential_template))
2393
+ throw new StateError('Org transfer requires both replacement membership bindings, terms and previous-owner credential', { stringCode: 'invalid', retryable: false });
2394
+ }
2122
2395
  this._aidStore = store;
2123
2396
  const localStore = store._keystore;
2124
2397
  const tokenStore = store._tokenStore;
2125
2398
  const inputHash = await ownershipSha256(canonicalJson(params));
2126
- 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'));
2127
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' });
2128
2403
  if (pending && (pending.transfer_id !== transferId || pending.owner_aid !== owner.aid || pending.input_hash !== inputHash))
2129
2404
  throw new StateError('pending Org transfer context changed', { stringCode: 'conflict', retryable: false });
2130
- for (const field of ['previous_owner', 'new_owner']) {
2131
- const material = params[field];
2132
- if (!isJsonObject(material) || !isJsonObject(material.membership_binding) || !isJsonObject(material.terms) || field === 'previous_owner' && !isJsonObject(material.credential_template))
2133
- throw new StateError('Org transfer requires both replacement membership bindings, terms and previous-owner credential', { stringCode: 'invalid', retryable: false });
2134
- }
2405
+ if (!pending)
2406
+ await this._verifyOrgTransferMaterials(params, owner.aid);
2135
2407
  const pendingKey = `org-transfer:${orgAid}:${transferId}`;
2136
2408
  let keyPair = await localStore.loadPendingGroupBind(pendingKey);
2137
2409
  if (!keyPair) {
@@ -2153,7 +2425,7 @@ export class AUNClient {
2153
2425
  await tokenStore.setMetadata(orgAid, 'org_identity_transfer', JSON.stringify(pending));
2154
2426
  }
2155
2427
  if (pending.phase === 'rekey') {
2156
- 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));
2157
2429
  pending = { ...pending, phase: 'finalize', rekey_result: result };
2158
2430
  await tokenStore.setMetadata(orgAid, 'org_identity_transfer', JSON.stringify(pending));
2159
2431
  }
@@ -2190,7 +2462,7 @@ export class AUNClient {
2190
2462
  pending = { ...pending, finalize_params: await this.org.prepareRequest('org.lifecycle.transferAccept', final, target, owner, { orgSigner: orgIdentity }) };
2191
2463
  await tokenStore.setMetadata(orgAid, 'org_identity_transfer', JSON.stringify(pending));
2192
2464
  }
2193
- 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));
2194
2466
  await this._uploadOrgAgentMd(store, orgAid, pending.source_params);
2195
2467
  await tokenStore.setMetadata(orgAid, 'org_identity_transfer', '');
2196
2468
  await localStore.clearPendingGroupBind(pendingKey);
@@ -2467,6 +2739,7 @@ export class AUNClient {
2467
2739
  return await manager.resumePendingUpload(groupAid);
2468
2740
  }
2469
2741
  async _uploadGroupAgentMd(store, groupAid, params, group, uploaderAid) {
2742
+ this._aidStore = store;
2470
2743
  if (await this._resumeGroupAgentMdUpload(store, groupAid))
2471
2744
  return;
2472
2745
  if (typeof store.uploadAgentMd !== 'function') {
@@ -2474,7 +2747,8 @@ export class AUNClient {
2474
2747
  }
2475
2748
  const uploaded = await store.uploadAgentMd(groupAid, await this._groupAgentMdContent(groupAid, params, group), { uploaderAid, entityOwnerResolver: (aid, type) => this._resolveEntityAgentMdOwner(aid, type), entityIdentityGetter: (aid, type) => this._freshEntityCertificate(aid, type), entitySignerResolver: (aid, fingerprint) => this._freshEntitySigner(aid, fingerprint) });
2476
2749
  if (!uploaded.ok) {
2477
- throw new ValidationError(`group agent.md upload failed: ${uploaded.error.message}`);
2750
+ const code = ['invalid', 'unavailable', 'forbidden', 'conflict', 'persistence_unavailable'].includes(uploaded.error.code) ? uploaded.error.code : uploaded.error.code === 'NETWORK_ERROR' ? 'unavailable' : 'invalid';
2751
+ throw new StateError(`group agent.md upload failed: ${uploaded.error.message}`, { stringCode: code, retryable: code === 'unavailable' });
2478
2752
  }
2479
2753
  }
2480
2754
  async _freshEntityCertificate(aid, type = 'group') {
@@ -3150,13 +3424,15 @@ export class AUNClient {
3150
3424
  async _fillP2pGap() {
3151
3425
  return this._delivery.fillP2pGap();
3152
3426
  }
3153
- _markPublishedSeq(ns, seq) {
3154
- this._delivery.markPublishedSeq(ns, seq);
3427
+ _markPublishedSeq(ns, seq, page) {
3428
+ this._delivery.markPublishedSeq(ns, seq, page);
3155
3429
  }
3156
- async _publishAppEvent(event, payload, source = 'direct', ns = '', batch) {
3430
+ async _publishAppEvent(event, payload, source = 'direct', ns = '', batch, page) {
3157
3431
  if (event === 'message.received' && await this._handleOwnershipMessage(payload))
3158
3432
  return;
3159
- 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);
3160
3436
  }
3161
3437
  _echoTimestamp() {
3162
3438
  const now = new Date();
@@ -3197,14 +3473,14 @@ export class AUNClient {
3197
3473
  const trace = `${this._echoTimestamp()} [AUN-SDK.receive] aid=${this._aid ?? '-'} conn_uptime=${uptime}s`;
3198
3474
  msg.payload = { ...payload, text: payload.text + '\n' + trace };
3199
3475
  }
3200
- async _drainOrderedMessages(ns, beforeSeq, pullResponse = false, persist = true, throughSeq) {
3201
- 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);
3202
3478
  }
3203
- async _publishOrderedMessage(event, ns, seq, payload, source = 'push', batch) {
3204
- 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);
3205
3481
  }
3206
- async _publishPulledMessage(event, ns, seq, payload, persist = true, source = 'pull', batch) {
3207
- 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);
3208
3484
  }
3209
3485
  _extractGroupIdFromResult(result) {
3210
3486
  const group = isJsonObject(result.group) ? result.group : null;
@@ -3667,76 +3943,99 @@ export class AUNClient {
3667
3943
  async _verifyIncomingServiceEvent(event, payload) {
3668
3944
  const targetAid = this._aid || this._currentAid?.aid || '';
3669
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;
3670
3949
  const verified = await verifyServiceEvent(event, payload, { targetAid, resolveEventCertificate: (service, fingerprint) => this._resolveServiceEventCertificate(service, fingerprint), resolveCredentialService: (cid) => this.resolveCredentialService?.(cid) });
3671
- if (version !== this._serviceEventIdentityVersion || targetAid !== (this._aid || this._currentAid?.aid || ''))
3950
+ if (!current())
3672
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
+ }
3673
3957
  return verified;
3674
3958
  }
3675
- async _fetchPeerCert(aid, certFingerprint, timeoutMs = 5000, signedAt) {
3959
+ async _fetchPeerCert(aid, certFingerprint, timeoutMs = 5000, signedAt, agentMdRead = false) {
3676
3960
  const tStart = Date.now();
3677
3961
  this._clientLog.debug(`_fetchPeerCert enter: aid=${aid} fingerprint=${certFingerprint ?? '<none>'}`);
3678
3962
  try {
3679
- const cacheKey = certCacheKey(aid, certFingerprint) + (signedAt === undefined ? '' : `:signed:${signedAt}`);
3963
+ const cacheKey = certCacheKey(aid, certFingerprint) + (signedAt === undefined ? '' : `:signed:${signedAt}`) + (agentMdRead ? ':agentmd' : '');
3680
3964
  const cached = this._certCache.get(cacheKey);
3681
3965
  const now = Date.now() / 1000;
3682
3966
  if (cached && now < cached.refreshAfter) {
3683
3967
  this._clientLog.debug(`_fetchPeerCert exit: elapsed=${Date.now() - tStart}ms aid=${aid} source=cache`);
3684
3968
  return cached.certPem;
3685
3969
  }
3686
- const gatewayUrl = this._gatewayUrl;
3687
- if (!gatewayUrl) {
3688
- throw new ValidationError('gateway url unavailable for e2ee cert fetch');
3689
- }
3690
- // 跨域时用 peer 所在域的 Gateway URL
3691
- const peerGatewayUrl = resolvePeerGatewayUrl(gatewayUrl, aid);
3692
- let certPem;
3693
- const certUrl = buildCertUrl(peerGatewayUrl, aid, certFingerprint);
3694
- // 兼容旧浏览器,不使用 AbortSignal.timeout(Chrome 103+ 才支持)
3695
- const controller = new AbortController();
3696
- const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
3697
- try {
3698
- const resp = await fetch(certUrl, { signal: controller.signal });
3699
- if (!resp.ok)
3700
- throw new ValidationError(`failed to fetch peer cert for ${aid}: HTTP ${resp.status}`);
3701
- certPem = await resp.text();
3702
- }
3703
- finally {
3704
- clearTimeout(timeoutId);
3705
- }
3706
- // H7: 严格校验指纹(DER SHA-256 或 SPKI SHA-256 任一匹配即可)
3707
- if (certFingerprint) {
3708
- const expectedFP = String(certFingerprint).trim().toLowerCase();
3709
- if (!normalizeFingerprintHex(expectedFP)) {
3710
- 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');
3711
3978
  }
3712
- if (!(await certMatchesFingerprint(certPem, expectedFP))) {
3713
- 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();
3714
3991
  }
3715
- }
3716
- // 完整 PKI 验证:链 + CRL + OCSP + AID 绑定
3717
- try {
3718
- if (signedAt === undefined)
3719
- await this._auth.verifyPeerCertificate(peerGatewayUrl, certPem, aid);
3720
- else
3721
- await this._auth.verifyPeerCertificate(peerGatewayUrl, certPem, aid, signedAt);
3722
- }
3723
- catch (exc) {
3724
- throw new ValidationError(`peer cert verification failed for ${aid}: ${exc}`);
3725
- }
3726
- this._certCache.set(cacheKey, {
3727
- certPem,
3728
- validatedAt: now,
3729
- refreshAfter: now + PEER_CERT_CACHE_TTL,
3730
- });
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);
3731
4033
  try {
3732
- // peer 证书只存版本目录,不覆盖 cert.pem
3733
- await this._tokenStore.saveCert(aid, certPem, certFingerprint, { makeActive: false });
4034
+ return await request;
3734
4035
  }
3735
- catch (exc) {
3736
- this._clientLog.error(`write cert to keystore failed (aid=${aid}): ${String(exc)}`, exc instanceof Error ? exc : undefined);
4036
+ finally {
4037
+ this._certFetchInFlight.delete(inFlightKey);
3737
4038
  }
3738
- this._clientLog.debug(`_fetchPeerCert exit: elapsed=${Date.now() - tStart}ms aid=${aid} source=fetched`);
3739
- return certPem;
3740
4039
  }
3741
4040
  catch (err) {
3742
4041
  this._clientLog.debug(`_fetchPeerCert exit (error): elapsed=${Date.now() - tStart}ms aid=${aid} err=${err instanceof Error ? err.message : String(err)}`);
@@ -4155,6 +4454,7 @@ export class AUNClient {
4155
4454
  }
4156
4455
  // ── 内部:后台任务 ────────────────────────────────
4157
4456
  _startBackgroundTasks() {
4457
+ this._startCredentialRenewal();
4158
4458
  this._startOwnershipRecovery();
4159
4459
  // WebSocket Ping/Pong 由 Gateway 与传输层处理;SDK 不发送 RPC 心跳。
4160
4460
  // 短连接生命周期短,不需要 token 刷新。
@@ -4184,6 +4484,7 @@ export class AUNClient {
4184
4484
  }
4185
4485
  }
4186
4486
  _stopBackgroundTasks() {
4487
+ this._stopCredentialRenewal();
4187
4488
  this._ownershipRecoveryGeneration += 1;
4188
4489
  if (this._ownershipRecoveryTimer !== null) {
4189
4490
  clearTimeout(this._ownershipRecoveryTimer);
@@ -4937,7 +5238,7 @@ export class AUNClient {
4937
5238
  * undecryptable 事件、成功消费后触发 SPK 轮换。inline Push 在游标提交和
4938
5239
  * Head/正文绑定校验前调用本入口时必须显式关闭这些副作用。
4939
5240
  */
4940
- 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) {
4941
5242
  const session = this._v2Session;
4942
5243
  if (!session)
4943
5244
  return null;
@@ -4953,8 +5254,12 @@ export class AUNClient {
4953
5254
  return null;
4954
5255
  }
4955
5256
  const e2eeMeta = v2E2eeMeta(envelope);
4956
- if (observeAgentMd)
4957
- await this._observeAgentMdFromEnvelope(envelope);
5257
+ if (observeAgentMd) {
5258
+ if (deferredEffects)
5259
+ deferredEffects.push(() => this._observeAgentMdFromEnvelope(envelope));
5260
+ else
5261
+ await this._observeAgentMdFromEnvelope(envelope);
5262
+ }
4958
5263
  // 确定 spk_id 和 recipient_key_source
4959
5264
  let spkId = '';
4960
5265
  let recipientKeySource = '';
@@ -5039,7 +5344,10 @@ export class AUNClient {
5039
5344
  source,
5040
5345
  };
5041
5346
  attachV2EnvelopeMetadata(event, e2eeMeta);
5042
- this._dispatcher.enqueue(undecryptableEvent, event);
5347
+ if (deferredEffects)
5348
+ deferredEffects.push(() => this._dispatcher.enqueue(undecryptableEvent, event));
5349
+ else
5350
+ this._dispatcher.enqueue(undecryptableEvent, event);
5043
5351
  }
5044
5352
  catch { /* publish 异常不影响主流程 */ }
5045
5353
  }
@@ -5080,7 +5388,10 @@ export class AUNClient {
5080
5388
  source,
5081
5389
  };
5082
5390
  attachV2EnvelopeMetadata(event, e2eeMeta);
5083
- this._dispatcher.enqueue(undecryptableEvent, event);
5391
+ if (deferredEffects)
5392
+ deferredEffects.push(() => this._dispatcher.enqueue(undecryptableEvent, event));
5393
+ else
5394
+ this._dispatcher.enqueue(undecryptableEvent, event);
5084
5395
  }
5085
5396
  catch { /* publish 异常不影响主流程 */ }
5086
5397
  }
@@ -5112,7 +5423,10 @@ export class AUNClient {
5112
5423
  source,
5113
5424
  };
5114
5425
  attachV2EnvelopeMetadata(event, e2eeMeta);
5115
- this._dispatcher.enqueue(undecryptableEvent, event);
5426
+ if (deferredEffects)
5427
+ deferredEffects.push(() => this._dispatcher.enqueue(undecryptableEvent, event));
5428
+ else
5429
+ this._dispatcher.enqueue(undecryptableEvent, event);
5116
5430
  }
5117
5431
  catch { /* publish 异常不影响主流程 */ }
5118
5432
  }
@@ -5121,8 +5435,12 @@ export class AUNClient {
5121
5435
  if (plaintext == null)
5122
5436
  return null;
5123
5437
  // 消费触发 SPK 轮换(fire-and-forget,不阻塞消息处理)
5124
- if (rotateKeys)
5125
- 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
+ }
5126
5444
  const e2ee = v2E2eeMeta(envelope);
5127
5445
  let result = {
5128
5446
  message_id: String(msg.message_id ?? ''),