@agentunion/fastaun 0.5.21 → 0.5.23

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 (75) hide show
  1. package/CHANGELOG.md +58 -1
  2. package/_packed_docs/CHANGELOG.md +58 -1
  3. package/_packed_docs/INDEX.md +469 -453
  4. package/_packed_docs/KITE_DOCS_GUIDE.md +169 -164
  5. package/_packed_docs/aun/345/210/206/345/270/203/345/274/217/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md +307 -67
  6. package/_packed_docs/aun/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md +41 -15
  7. package/_packed_docs/protocol/06-/346/234/215/345/212/241/345/215/217/350/256/256.md +5 -5
  8. package/_packed_docs/protocol/11-Storage-/345/255/220/345/215/217/350/256/256.md +30 -8
  9. package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2112.1.md +669 -666
  10. package/_packed_docs/sdk/0.5.22-SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +212 -0
  11. package/_packed_docs/sdk/0.5.22-SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +199 -0
  12. package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +16 -0
  13. package/_packed_docs/sdk/09-group-rpc-manual.md +10 -0
  14. package/_packed_docs/sdk/09-storage-rpc-manual.md +64 -7
  15. package/_packed_docs/sdk/09-/345/207/255/350/257/201/346/234/215/345/212/241RPC/346/211/213/345/206/214.md +3 -1
  16. package/_packed_docs/sdk/09-/347/273/204/347/273/207/346/234/215/345/212/241RPC/346/211/213/345/206/214.md +2 -0
  17. package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +138 -134
  18. package/_packed_docs/sdk/INDEX.md +255 -239
  19. package/_packed_docs/sdk/Ownership-SDK/347/274/226/346/216/222/346/214/207/345/215/2272.1.md +939 -930
  20. package/_packed_docs/sdk/Ownership2.1-SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +312 -363
  21. package/_packed_docs/sdk/Ownership2.1-SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +322 -553
  22. 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 +4 -4
  23. package/_packed_docs/sdk/Ownership2.1/350/257/246/347/273/206/350/256/276/350/256/241.md +484 -480
  24. package/_packed_docs/sdk//345/207/255/350/257/201/344/270/216/347/273/204/347/273/207/346/234/215/345/212/241SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +190 -97
  25. package/_packed_docs/sdk//345/207/255/350/257/201/344/270/216/347/273/204/347/273/207/346/234/215/345/212/241SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +158 -127
  26. package/dist/agent-md.d.ts +16 -2
  27. package/dist/agent-md.js +59 -23
  28. package/dist/agent-md.js.map +1 -1
  29. package/dist/aid-store.d.ts +1 -0
  30. package/dist/aid-store.js +43 -8
  31. package/dist/aid-store.js.map +1 -1
  32. package/dist/auth.d.ts +7 -0
  33. package/dist/auth.js +222 -70
  34. package/dist/auth.js.map +1 -1
  35. package/dist/client/delivery.d.ts +28 -0
  36. package/dist/client/delivery.js +399 -44
  37. package/dist/client/delivery.js.map +1 -1
  38. package/dist/client/group-state.js +3 -0
  39. package/dist/client/group-state.js.map +1 -1
  40. package/dist/client/rpc-pipeline.js +28 -2
  41. package/dist/client/rpc-pipeline.js.map +1 -1
  42. package/dist/client/v2-e2ee.js +109 -39
  43. package/dist/client/v2-e2ee.js.map +1 -1
  44. package/dist/client.d.ts +3 -0
  45. package/dist/client.js +114 -26
  46. package/dist/client.js.map +1 -1
  47. package/dist/group-fs.d.ts +1 -0
  48. package/dist/group-fs.js +37 -19
  49. package/dist/group-fs.js.map +1 -1
  50. package/dist/org-protocol.d.ts +11 -0
  51. package/dist/org-protocol.js +154 -19
  52. package/dist/org-protocol.js.map +1 -1
  53. package/dist/ownership-v21.d.ts +21 -3
  54. package/dist/ownership-v21.js +135 -37
  55. package/dist/ownership-v21.js.map +1 -1
  56. package/dist/ownership-workflow.d.ts +7 -2
  57. package/dist/ownership-workflow.js +173 -17
  58. package/dist/ownership-workflow.js.map +1 -1
  59. package/dist/storage/lowlevel.d.ts +6 -0
  60. package/dist/storage/lowlevel.js +24 -7
  61. package/dist/storage/lowlevel.js.map +1 -1
  62. package/dist/storage/read-cache.d.ts +70 -0
  63. package/dist/storage/read-cache.js +625 -0
  64. package/dist/storage/read-cache.js.map +1 -0
  65. package/dist/storage/vfs.d.ts +2 -0
  66. package/dist/storage/vfs.js +50 -15
  67. package/dist/storage/vfs.js.map +1 -1
  68. package/dist/tools/cross-sdk-agent.d.ts +21 -1
  69. package/dist/tools/cross-sdk-agent.js +301 -19
  70. package/dist/tools/cross-sdk-agent.js.map +1 -1
  71. package/dist/transport.js +6 -2
  72. package/dist/transport.js.map +1 -1
  73. package/dist/version.d.ts +1 -1
  74. package/dist/version.js +1 -1
  75. package/package.json +1 -1
package/dist/client.d.ts CHANGED
@@ -90,6 +90,7 @@ export declare class AUNClient {
90
90
  private _ownershipWorkflow;
91
91
  private _getOwnershipStorage;
92
92
  private _getOwnershipFiles;
93
+ private _createOwnershipFiles;
93
94
  private _getOwnershipWorkflow;
94
95
  private _persistOwnershipRecords;
95
96
  private _restoreOwnershipRecords;
@@ -98,6 +99,7 @@ export declare class AUNClient {
98
99
  private _credentialRenewalTimer;
99
100
  private _credentialRenewalAbort;
100
101
  private _credentialRenewalScan;
102
+ private _nextCredentialRevocationSyncAt;
101
103
  private _getCredentialRenewal;
102
104
  _assertCredentialNotRevoked(credential: JsonObject, proofChain?: JsonObject[]): () => void;
103
105
  private _credentialRenewalSigner;
@@ -274,6 +276,7 @@ export declare class AUNClient {
274
276
  releaseSubaid(subAid: string): Promise<OwnershipTaskResult>;
275
277
  transferSubaid(subAid: string, newOwnerAid: string): Promise<OwnershipTaskResult>;
276
278
  checkOwnershipConsensus(subAid?: string): Promise<OwnershipConsensusResult>;
279
+ private _checkOwnershipConsensusFresh;
277
280
  bindAid(subjectAid: string, options?: {
278
281
  subjectType?: string;
279
282
  subject_type?: string;
package/dist/client.js CHANGED
@@ -48,7 +48,7 @@ import { isJsonObject, ConnectionState, STATE_TO_PUBLIC, } from './types.js';
48
48
  import { AID } from './aid.js';
49
49
  import { certFingerprint, certMatchesFingerprint, normalizeFingerprintHex, publicKeyMatchesFingerprint } from './cert-utils.js';
50
50
  import { validateAgentMdCertificate, validateAgentMdDocument } from './agent-md-schema.js';
51
- import { OwnershipFiles, OwnershipStorageAdapter, ownershipFailure } from './ownership-v21.js';
51
+ import { OwnershipFiles, OwnershipStorageAdapter, ownershipAid, ownershipFailure } from './ownership-v21.js';
52
52
  import { OwnershipWorkflow } from './ownership-workflow.js';
53
53
  import { canonicalJson, createCredential, createMemberCredential, signMembershipBinding, digest, validateMembershipBinding, verifyMembershipBinding, verifyCredentialSignature, createRepresentationProof, verifyServiceEvent, buildRequestBinding, validateRequestBinding, CredentialRenewal } from './org-protocol.js';
54
54
  function isPromiseLike(value) {
@@ -489,16 +489,19 @@ export class AUNClient {
489
489
  return this._ownershipStorage ??= new OwnershipStorageAdapter({ currentAid: () => this._aid, call: (method, params) => this.call(method, params), httpPut: (url, data, headers) => this.storage.lowlevel.httpPut(url, data, headers) });
490
490
  }
491
491
  _getOwnershipFiles() {
492
- return this._ownershipFiles ??= new OwnershipFiles({
492
+ return this._ownershipFiles ??= this._createOwnershipFiles((subAid) => this._agentMdManager.readOwnershipFresh(subAid));
493
+ }
494
+ _createOwnershipFiles(readAgentMdFresh) {
495
+ return new OwnershipFiles({
493
496
  currentAid: () => { if (!this._currentAid)
494
497
  throw ownershipFailure('forbidden', 'ownership requires a loaded identity'); return this._currentAid; },
495
498
  currentType: () => this.getType(),
496
499
  now: () => Math.trunc(Date.now() / 1000),
497
- readAgentMdFresh: (subAid) => this._agentMdManager.readOwnershipFresh(subAid),
498
- submitAgentMdRaw: (subAid, raw, condition, assertActive) => this._agentMdManager.submitOwnershipRaw(subAid, raw, condition, assertActive),
500
+ readAgentMdFresh,
501
+ submitAgentMdRaw: (subAid, raw, condition, assertActive, stage) => this._agentMdManager.submitOwnershipRaw(subAid, raw, condition, assertActive, stage),
499
502
  readOwnerMd: (ownerAid, subAid) => this._getOwnershipStorage().readOwnerMd(ownerAid, subAid),
500
503
  writeOwnerMd: (ownerAid, subAid, raw, original, assertActive) => this._getOwnershipStorage().writeOwnerMd(ownerAid, subAid, raw, original, assertActive),
501
- deleteOwnerMd: (ownerAid, subAid) => this._getOwnershipStorage().deleteOwnerMd(ownerAid, subAid),
504
+ deleteOwnerMd: (ownerAid, subAid, expectedVersion) => this._getOwnershipStorage().deleteOwnerMd(ownerAid, subAid, expectedVersion),
502
505
  resolveCertificate: async (aid, fingerprint, purpose) => this._auth.resolveOwnershipCertificate(await this._resolveOwnershipCertificateGateway(aid), aid, fingerprint, purpose),
503
506
  });
504
507
  }
@@ -506,7 +509,7 @@ export class AUNClient {
506
509
  return this._ownershipWorkflow ??= new OwnershipWorkflow({
507
510
  currentAid: () => this._aid, store: this._tokenStore, files: this._getOwnershipFiles(),
508
511
  currentType: () => this.getType(),
509
- send: (to, payload) => this.call('message.send', { to, encrypt: true, payload: payload }),
512
+ send: (to, payload, messageId) => this.call('message.send', { to, encrypt: true, message_id: messageId, payload: payload }),
510
513
  emit: (event, payload) => this._dispatcher.enqueue(event, payload),
511
514
  });
512
515
  }
@@ -554,9 +557,11 @@ export class AUNClient {
554
557
  _credentialRenewalTimer = null;
555
558
  _credentialRenewalAbort = null;
556
559
  _credentialRenewalScan = Promise.resolve();
560
+ _nextCredentialRevocationSyncAt = 0;
557
561
  _getCredentialRenewal() {
558
562
  return this._credentialRenewal ??= new CredentialRenewal({
559
563
  currentAid: () => this._aid ?? '',
564
+ queryContext: () => JSON.stringify([this._serviceEventIdentityVersion, this._gatewayUrl, this._transport?._connectionGeneration, this._transport?._workerGeneration]),
560
565
  atomicCheckpointSave: () => this._tokenStore instanceof LocalTokenStore,
561
566
  load: (aid) => (this._tokenStore._loadMetadataStrict?.(aid) ?? this._tokenStore.loadMetadata?.(aid))?.credential_renewals_v1 ?? {},
562
567
  ...(typeof this._tokenStore._loadMetadataStrict === 'function' ? {
@@ -630,6 +635,7 @@ export class AUNClient {
630
635
  if (aid === this._aid && this._identity) {
631
636
  this._identity.cert = identity.certPem;
632
637
  this._currentAid = identity;
638
+ this._auth.setIdentity(this._identity);
633
639
  }
634
640
  }
635
641
  return identity;
@@ -647,11 +653,11 @@ export class AUNClient {
647
653
  _credentialCurrent(input) {
648
654
  return this._getCredentialRenewal().current(input);
649
655
  }
650
- async _resumeCredentialRenewalAfterConnect(guard) {
656
+ async _resumeCredentialRenewalAfterConnect(guard, syncRevocations = false) {
651
657
  if (!this._aid || (guard && !guard.current()))
652
658
  return;
653
659
  await this._safeAsync(guard ? this._credentialRenewalSigner(this._aid, guard) : this._credentialRenewalSigner(this._aid));
654
- if (!guard || guard.current())
660
+ if (syncRevocations && (!guard || guard.current()))
655
661
  await this._safeAsync(this._getCredentialRenewal()._syncRevocations?.(guard) ?? Promise.resolve());
656
662
  if (!guard || guard.current())
657
663
  await this._getCredentialRenewal().resume(guard);
@@ -665,11 +671,15 @@ export class AUNClient {
665
671
  this._credentialRenewalAbort = abort;
666
672
  const current = () => this._credentialRenewalAbort === abort && !abort.signal.aborted && aid === this._aid && version === this._serviceEventIdentityVersion && ['ready', 'connected'].includes(this._state) && !this._closing && !this._disconnecting;
667
673
  const guard = { current, signal: abort.signal };
674
+ this._nextCredentialRevocationSyncAt ||= Date.now() + 3_600_000;
668
675
  const scan = async () => {
669
676
  const task = this._credentialRenewalScan.then(async () => {
670
677
  if (!current())
671
678
  return;
672
- await this._resumeCredentialRenewalAfterConnect(guard);
679
+ const syncRevocations = Date.now() >= this._nextCredentialRevocationSyncAt;
680
+ if (syncRevocations)
681
+ this._nextCredentialRevocationSyncAt = Date.now() + 3_600_000;
682
+ await this._resumeCredentialRenewalAfterConnect(guard, syncRevocations);
673
683
  });
674
684
  this._credentialRenewalScan = task.catch(() => { });
675
685
  await this._safeAsync(task);
@@ -701,10 +711,18 @@ export class AUNClient {
701
711
  const version = this._serviceEventIdentityVersion;
702
712
  const signal = this._serviceEventIdentityAbort.signal;
703
713
  const expiresAt = Number(payload?.expires_at);
704
- const current = () => version === this._serviceEventIdentityVersion && targetAid === (this._identity?.aid ?? '') && Date.now() / 1000 < expiresAt;
714
+ const transport = this._transport, connectionGeneration = transport?._connectionGeneration, workerGeneration = transport?._workerGeneration;
715
+ const current = () => version === this._serviceEventIdentityVersion && targetAid === (this._identity?.aid ?? '') && transport === this._transport && connectionGeneration === this._transport?._connectionGeneration && workerGeneration === this._transport?._workerGeneration && Date.now() / 1000 < expiresAt;
705
716
  const verified = await verifyServiceEvent(event, payload, { targetAid, resolveEventCertificate: (aid, fingerprint) => this._fetchServiceEventCertificate(aid, fingerprint), resolveCredentialService: this.resolveCredentialService });
706
717
  if (!current())
707
718
  throw new ValidationError('service event receiver identity changed');
719
+ const eventName = event.replace(/^event\//, '');
720
+ if (eventName.startsWith('org.') || ['credential.revoked', 'credential.revocationList'].includes(eventName)) {
721
+ this._getCredentialRenewal().invalidateQueries?.();
722
+ }
723
+ if (['credential.revoked', 'credential.revocationList'].includes(event.replace(/^event\//, ''))) {
724
+ await this._getCredentialRenewal()._receiveRevocations(payload, { current, signal });
725
+ }
708
726
  if (event.replace(/^event\//, '') === 'org.member.changed' && isJsonObject(payload) && Object.hasOwn(payload, 'membership_package')) {
709
727
  await this._getCredentialRenewal().importMembershipPackage(payload, { current, signal });
710
728
  if (!current())
@@ -1179,7 +1197,12 @@ export class AUNClient {
1179
1197
  async resetOwnershipTask() { return this._getOwnershipWorkflow().resetOwnershipTask(); }
1180
1198
  async releaseSubaid(subAid) { return this._getOwnershipWorkflow().releaseSubaid(subAid); }
1181
1199
  async transferSubaid(subAid, newOwnerAid) { return this._getOwnershipWorkflow().transferSubaid(subAid, newOwnerAid); }
1182
- async checkOwnershipConsensus(subAid) { return this._getOwnershipWorkflow().checkOwnershipConsensus(subAid); }
1200
+ async checkOwnershipConsensus(subAid) {
1201
+ return this._createOwnershipFiles((aid) => this._agentMdManager._readOwnershipQuery(aid)).checkConsensus(ownershipAid(subAid ?? this._aid, 'sub_aid'));
1202
+ }
1203
+ async _checkOwnershipConsensusFresh(subAid) {
1204
+ return this._getOwnershipWorkflow().checkOwnershipConsensus(subAid);
1205
+ }
1183
1206
  async bindAid(subjectAid, options = {}) {
1184
1207
  throw ownershipFailure('unsupported_protocol', 'legacy bindAid is unsupported; use bindSubaid');
1185
1208
  }
@@ -1386,6 +1409,9 @@ export class AUNClient {
1386
1409
  this._identityRuntime.loadIdentity(aid);
1387
1410
  this._stopCredentialRenewal();
1388
1411
  this._serviceEventIdentityVersion++;
1412
+ this._auth.invalidateStrictPkiCache();
1413
+ this._certCache.clear();
1414
+ this._certFetchInFlight.clear();
1389
1415
  this._serviceEventIdentityAbort.abort();
1390
1416
  this._serviceEventIdentityAbort = new AbortController();
1391
1417
  this._ensureReadStateIdentity();
@@ -1614,6 +1640,9 @@ export class AUNClient {
1614
1640
  if (!isJsonObject(raw))
1615
1641
  continue;
1616
1642
  const group = raw;
1643
+ const status = String(group.status ?? '').trim().toLowerCase();
1644
+ if (status && status !== 'active')
1645
+ continue;
1617
1646
  const role = String(group.role ?? group.my_role ?? '').trim().toLowerCase();
1618
1647
  const groupId = String(group.group_id ?? group.groupId ?? group.group_aid ?? group.groupAid ?? '').trim();
1619
1648
  const groupAid = String(group.group_aid ?? group.groupAid ?? groupId).trim();
@@ -1753,7 +1782,7 @@ export class AUNClient {
1753
1782
  await this.createOrg(creation.source_params, { aidStore: store });
1754
1783
  continue;
1755
1784
  }
1756
- if (transfer?.owner_aid === this._aid) {
1785
+ if (transfer?.owner_aid === this._aid && transfer.phase !== 'completed') {
1757
1786
  await this.completeOrgTransfer(transfer.source_params, { aidStore: store });
1758
1787
  continue;
1759
1788
  }
@@ -1817,8 +1846,14 @@ export class AUNClient {
1817
1846
  let pending = (typeof tokenStore._loadMetadataStrict === 'function' ? tokenStore._loadMetadataStrict(orgAid) : tokenStore.loadMetadata(orgAid))?.org_identity_transfer;
1818
1847
  if (pending != null && !isJsonObject(pending))
1819
1848
  throw ownershipFailure('persistence_unavailable', 'Org transfer checkpoint is invalid');
1849
+ if (pending?.phase === 'completed' && pending.transfer_id !== transferId)
1850
+ pending = undefined;
1820
1851
  if (pending && (pending.transfer_id !== transferId || pending.owner_aid !== owner.aid || pending.input_hash !== inputHash))
1821
1852
  throw ownershipFailure('conflict', 'pending Org transfer context changed');
1853
+ if (pending?.phase === 'completed') {
1854
+ localStore.clearPendingGroupBind(pendingKey);
1855
+ return pending.result;
1856
+ }
1822
1857
  if (!pending)
1823
1858
  await this._verifyOrgTransferMaterials(params, owner.aid);
1824
1859
  let keyPair = localStore.loadPendingGroupBind(pendingKey);
@@ -1879,7 +1914,7 @@ export class AUNClient {
1879
1914
  }
1880
1915
  const result = await this.call('org.lifecycle.transferAccept', await this._resumeOrgRequest('org.lifecycle.transferAccept', pending.finalize_params, target));
1881
1916
  await this._uploadOrgAgentMd(store, orgAid, pending.source_params);
1882
- tokenStore.saveMetadata(orgAid, { org_identity_transfer: null });
1917
+ tokenStore.saveMetadata(orgAid, { org_identity_transfer: { transfer_id: transferId, owner_aid: owner.aid, input_hash: inputHash, phase: 'completed', result } });
1883
1918
  localStore.clearPendingGroupBind(pendingKey);
1884
1919
  return result;
1885
1920
  }
@@ -2132,7 +2167,13 @@ export class AUNClient {
2132
2167
  entityIdentityGetter: (aid, type) => this._freshEntityCertificate(aid, type),
2133
2168
  entitySignerResolver: (aid, fingerprint) => this._freshEntitySigner(aid, fingerprint),
2134
2169
  gatewayResolver: (aid) => identityStore._resolveGateway(aid),
2135
- accessTokenResolver: (_aid, gatewayUrl) => identityStore._uploadAgentMdToken(identity, gatewayUrl),
2170
+ accessTokenResolver: async () => {
2171
+ const state = identityStore._tokenStore?.loadInstanceState?.(identity.aid, store.deviceId, store.slotId) ?? {};
2172
+ const token = AuthFlow._getCachedAccessToken(state);
2173
+ if (token && String(state.refresh_token ?? '') && !this._auth._validateCachedCredentials(identityStore._authIdentityFromAid(identity)))
2174
+ return token;
2175
+ return identityStore._uploadAgentMdToken(identity, await identityStore._resolveGateway(identity.aid));
2176
+ },
2136
2177
  aidValidator: (aid) => identityStore._registerFlow.validateAidName(aid),
2137
2178
  });
2138
2179
  return await manager.resumePendingUpload(groupAid);
@@ -2607,6 +2648,11 @@ export class AUNClient {
2607
2648
  const tStart = Date.now();
2608
2649
  this._clientLog.debug(`on enter: event=${event}`);
2609
2650
  const result = this._dispatcher.subscribe(event, handler);
2651
+ if (event === 'message.received' || event === 'message.recalled' || event === 'message.batch') {
2652
+ if (this._p2pPeerPullEnabled)
2653
+ this._delivery.refreshPendingPeers();
2654
+ this._delivery.requestBackgroundPullRecovery();
2655
+ }
2610
2656
  this._clientLog.debug(`on exit: elapsed=${Date.now() - tStart}ms event=${event}`);
2611
2657
  return result;
2612
2658
  }
@@ -3311,6 +3357,8 @@ export class AUNClient {
3311
3357
  this._clientLog.debug(`bootstrap CA chain cache skipped: peer=${aid}, source=${opts.source ?? 'unknown'}, err=${formatCaughtError(exc)}`);
3312
3358
  }
3313
3359
  }
3360
+ const auth = this._auth;
3361
+ const pkiEpoch = auth._strictPkiEpoch;
3314
3362
  try {
3315
3363
  if (opts.signedAt === undefined)
3316
3364
  await this._auth.verifyPeerCertificate(peerGatewayUrl, certPem, aid);
@@ -3328,20 +3376,21 @@ export class AUNClient {
3328
3376
  }
3329
3377
  throw new ValidationError(`peer cert verification failed for ${aid}: ${exc instanceof Error ? exc.message : String(exc)}`);
3330
3378
  }
3379
+ if (auth !== this._auth || pkiEpoch !== auth._strictPkiEpoch)
3380
+ throw new ValidationError('peer certificate validation context changed');
3331
3381
  const nowSec = Date.now() / 1000;
3332
3382
  const entry = {
3333
3383
  certPem,
3334
3384
  validatedAt: nowSec,
3335
- refreshAfter: nowSec + PEER_CERT_CACHE_TTL,
3385
+ refreshAfter: Math.min(nowSec + PEER_CERT_CACHE_TTL, Date.parse(x509Cert.validTo) / 1000, auth._gatewayChainCache.get(`${peerGatewayUrl}:${aid}`)?.expiresAt / 1000 || Infinity),
3386
+ auth,
3387
+ pkiEpoch,
3336
3388
  };
3337
- const cacheKey = AUNClient._certCacheKey(aid, certFingerprint) + (opts.signedAt === undefined ? '' : `:signed:${opts.signedAt}${opts.agentMdRead ? ':agentmd' : ''}`);
3389
+ const scope = opts.signedAt === undefined ? '' : `:historical${opts.agentMdRead ? ':agentmd' : ''}`;
3390
+ const actualFp = `sha256:${x509Cert.fingerprint256.replace(/:/g, '').toLowerCase()}`;
3391
+ const cacheKey = AUNClient._certCacheKey(aid, actualFp) + scope;
3338
3392
  this._certCache.set(cacheKey, entry);
3339
- if (!certFingerprint && opts.signedAt === undefined) {
3340
- const bareKey = AUNClient._certCacheKey(aid);
3341
- this._certCache.set(bareKey, entry);
3342
- const actualFp = `sha256:${x509Cert.fingerprint256.replace(/:/g, '').toLowerCase()}`;
3343
- this._certCache.set(AUNClient._certCacheKey(aid, actualFp), entry);
3344
- }
3393
+ this._certCache.set(AUNClient._certCacheKey(aid, certFingerprint) + scope, entry);
3345
3394
  try {
3346
3395
  // peer 证书只存版本目录,不覆盖 cert.pem
3347
3396
  this._tokenStore.saveCert(aid, certPem, opts.agentMdRead && opts.signedAt !== undefined ? `sha256:${x509Cert.fingerprint256.replace(/:/g, '').toLowerCase()}` : certFingerprint, { makeActive: false });
@@ -3363,14 +3412,46 @@ export class AUNClient {
3363
3412
  const tStart = Date.now();
3364
3413
  this._clientLog.debug(`_fetchPeerCert enter: aid=${aid}, fp=${certFingerprint ?? ''}`);
3365
3414
  try {
3366
- const cacheKey = AUNClient._certCacheKey(aid, certFingerprint) + (signedAt === undefined ? '' : `:signed:${signedAt}${agentMdRead ? ':agentmd' : ''}`);
3415
+ const cacheKey = AUNClient._certCacheKey(aid, certFingerprint) + (signedAt === undefined ? '' : `:historical${agentMdRead ? ':agentmd' : ''}`);
3367
3416
  const cached = this._certCache.get(cacheKey);
3368
3417
  const now = Date.now() / 1000;
3369
- if (cached && now < cached.refreshAfter) {
3418
+ const auth = this._auth, pkiEpoch = auth._strictPkiEpoch;
3419
+ if (cached && cached.auth === auth && cached.pkiEpoch === pkiEpoch && now < cached.refreshAfter) {
3420
+ if (signedAt === undefined) {
3421
+ const statusKey = `${cacheKey}:status:${pkiEpoch}`;
3422
+ let pending = this._certFetchInFlight.get(statusKey);
3423
+ if (!pending) {
3424
+ pending = (async () => {
3425
+ await auth.verifyPeerCertificate(AUNClient._resolvePeerGatewayUrl(this._gatewayUrl, aid), cached.certPem, aid);
3426
+ if (auth !== this._auth || pkiEpoch !== auth._strictPkiEpoch)
3427
+ throw new ValidationError('peer certificate status context changed');
3428
+ return cached.certPem;
3429
+ })();
3430
+ this._certFetchInFlight.set(statusKey, pending);
3431
+ }
3432
+ try {
3433
+ await pending;
3434
+ }
3435
+ finally {
3436
+ if (this._certFetchInFlight.get(statusKey) === pending)
3437
+ this._certFetchInFlight.delete(statusKey);
3438
+ }
3439
+ }
3440
+ else {
3441
+ const peerGatewayUrl = AUNClient._resolvePeerGatewayUrl(this._gatewayUrl, aid);
3442
+ if (agentMdRead) {
3443
+ validateAgentMdCertificate(cached.certPem, aid, signedAt, { requireCurrentValidity: false, readCompatibility: true });
3444
+ await auth._verifyAuthCertChain(peerGatewayUrl, new crypto.X509Certificate(cached.certPem), aid, signedAt);
3445
+ }
3446
+ else
3447
+ await auth.verifyPeerCertificate(peerGatewayUrl, cached.certPem, aid, signedAt);
3448
+ if (auth !== this._auth || pkiEpoch !== auth._strictPkiEpoch)
3449
+ throw new ValidationError('peer certificate validation context changed');
3450
+ }
3370
3451
  this._clientLog.debug(`_fetchPeerCert exit: elapsed=${Date.now() - tStart}ms aid=${aid} (cache_hit)`);
3371
3452
  return cached.certPem;
3372
3453
  }
3373
- const inFlightKey = `${cacheKey}:timeout:${timeoutMs}`;
3454
+ const inFlightKey = `${cacheKey}:signed:${signedAt ?? 'current'}:timeout:${timeoutMs}:pki:${pkiEpoch}`;
3374
3455
  const pending = this._certFetchInFlight.get(inFlightKey);
3375
3456
  if (pending)
3376
3457
  return await pending;
@@ -3381,6 +3462,8 @@ export class AUNClient {
3381
3462
  }
3382
3463
  const peerGatewayUrl = AUNClient._resolvePeerGatewayUrl(gatewayUrl, aid);
3383
3464
  const certPem = await _httpGetText(AUNClient._buildCertUrl(peerGatewayUrl, aid, certFingerprint), this._configModel.verifySsl, timeoutMs);
3465
+ if (auth !== this._auth || pkiEpoch !== auth._strictPkiEpoch)
3466
+ throw new ValidationError('peer certificate fetch context changed');
3384
3467
  const validated = await this._validateAndCachePeerCert({
3385
3468
  aid,
3386
3469
  certPem,
@@ -3801,7 +3884,9 @@ export class AUNClient {
3801
3884
  }
3802
3885
  /** 构建证书下载 URL */
3803
3886
  static _certCacheKey(aid, certFingerprint) {
3804
- const normalized = String(certFingerprint ?? '').trim().toLowerCase();
3887
+ const raw = String(certFingerprint ?? '').trim().toLowerCase();
3888
+ const hex = normalizeFingerprintHex(raw);
3889
+ const normalized = hex ? `sha256:${hex}` : raw;
3805
3890
  return normalized ? `${aid}#${normalized}` : aid;
3806
3891
  }
3807
3892
  static _buildCertUrl(gatewayUrl, aid, certFingerprint) {
@@ -4846,6 +4931,9 @@ export class AUNClient {
4846
4931
  /** 停止所有后台任务 */
4847
4932
  _stopBackgroundTasks() {
4848
4933
  this._stopCredentialRenewal();
4934
+ this._auth.invalidateStrictPkiCache();
4935
+ this._certCache.clear();
4936
+ this._certFetchInFlight.clear();
4849
4937
  if (this._heartbeatTimer !== null) {
4850
4938
  clearInterval(this._heartbeatTimer);
4851
4939
  this._heartbeatTimer = null;