@agentunion/fastaun 0.5.22 → 0.5.24

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 (45) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/_packed_docs/CHANGELOG.md +33 -0
  3. package/_packed_docs/INDEX.md +32 -16
  4. package/_packed_docs/KITE_DOCS_GUIDE.md +22 -18
  5. package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2112.1.md +2 -2
  6. package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +13 -7
  7. package/_packed_docs/sdk/INDEX.md +21 -6
  8. package/_packed_docs/sdk/Ownership-SDK/347/274/226/346/216/222/346/214/207/345/215/2272.1.md +49 -2
  9. 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 +123 -72
  10. 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 +173 -96
  11. package/dist/agent-md.d.ts +16 -2
  12. package/dist/agent-md.js +60 -29
  13. package/dist/agent-md.js.map +1 -1
  14. package/dist/aid-store.d.ts +2 -0
  15. package/dist/aid-store.js +76 -19
  16. package/dist/aid-store.js.map +1 -1
  17. package/dist/auth.d.ts +6 -0
  18. package/dist/auth.js +205 -69
  19. package/dist/auth.js.map +1 -1
  20. package/dist/client.d.ts +12 -10
  21. package/dist/client.js +193 -112
  22. package/dist/client.js.map +1 -1
  23. package/dist/index.d.ts +1 -1
  24. package/dist/keystore/aid-db.d.ts +1 -8
  25. package/dist/keystore/aid-db.js +46 -204
  26. package/dist/keystore/aid-db.js.map +1 -1
  27. package/dist/keystore/index.d.ts +0 -15
  28. package/dist/keystore/local-token-store.d.ts +1 -6
  29. package/dist/keystore/local-token-store.js +0 -15
  30. package/dist/keystore/local-token-store.js.map +1 -1
  31. package/dist/org-protocol.d.ts +10 -0
  32. package/dist/org-protocol.js +100 -12
  33. package/dist/org-protocol.js.map +1 -1
  34. package/dist/ownership-v21.d.ts +2 -2
  35. package/dist/ownership-v21.js +25 -12
  36. package/dist/ownership-v21.js.map +1 -1
  37. package/dist/ownership-workflow.d.ts +30 -59
  38. package/dist/ownership-workflow.js +260 -1667
  39. package/dist/ownership-workflow.js.map +1 -1
  40. package/dist/tools/cross-sdk-agent.d.ts +1 -2
  41. package/dist/tools/cross-sdk-agent.js +7 -14
  42. package/dist/tools/cross-sdk-agent.js.map +1 -1
  43. package/dist/version.d.ts +1 -1
  44. package/dist/version.js +1 -1
  45. package/package.json +1 -1
package/dist/client.js CHANGED
@@ -48,8 +48,8 @@ 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';
52
- import { OwnershipWorkflow } from './ownership-workflow.js';
51
+ import { OwnershipFiles, OwnershipStorageAdapter, ownershipAid, ownershipFailure } from './ownership-v21.js';
52
+ import { OwnershipWorkflow, OWNERSHIP_SDK_MESSAGE_TYPE, parseOwnershipMessage } 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) {
55
55
  return Boolean(value && typeof value.then === 'function');
@@ -479,8 +479,6 @@ export class AUNClient {
479
479
  _currentAid = null;
480
480
  _serviceEventIdentityVersion = 0;
481
481
  _serviceEventIdentityAbort = new AbortController();
482
- /** ownership 关系只在 SDK 内存中编排;服务端不保存状态机。 */
483
- _ownershipRecords = new Map();
484
482
  _ownershipType = 'agent';
485
483
  _ownershipStorage = null;
486
484
  _ownershipFiles = null;
@@ -489,12 +487,15 @@ export class AUNClient {
489
487
  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
488
  }
491
489
  _getOwnershipFiles() {
492
- return this._ownershipFiles ??= new OwnershipFiles({
490
+ return this._ownershipFiles ??= this._createOwnershipFiles((subAid, stage) => this._agentMdManager.readOwnershipFresh(subAid, stage));
491
+ }
492
+ _createOwnershipFiles(readAgentMdFresh) {
493
+ return new OwnershipFiles({
493
494
  currentAid: () => { if (!this._currentAid)
494
495
  throw ownershipFailure('forbidden', 'ownership requires a loaded identity'); return this._currentAid; },
495
496
  currentType: () => this.getType(),
496
497
  now: () => Math.trunc(Date.now() / 1000),
497
- readAgentMdFresh: (subAid) => this._agentMdManager.readOwnershipFresh(subAid),
498
+ readAgentMdFresh,
498
499
  submitAgentMdRaw: (subAid, raw, condition, assertActive, stage) => this._agentMdManager.submitOwnershipRaw(subAid, raw, condition, assertActive, stage),
499
500
  readOwnerMd: (ownerAid, subAid) => this._getOwnershipStorage().readOwnerMd(ownerAid, subAid),
500
501
  writeOwnerMd: (ownerAid, subAid, raw, original, assertActive) => this._getOwnershipStorage().writeOwnerMd(ownerAid, subAid, raw, original, assertActive),
@@ -504,59 +505,65 @@ export class AUNClient {
504
505
  }
505
506
  _getOwnershipWorkflow() {
506
507
  return this._ownershipWorkflow ??= new OwnershipWorkflow({
507
- currentAid: () => this._aid, store: this._tokenStore, files: this._getOwnershipFiles(),
508
- currentType: () => this.getType(),
508
+ currentAid: () => this._aid, files: this._getOwnershipFiles(),
509
+ currentType: () => this.getType(), assertCurrent: () => this._ownershipOperationGuard?.(),
510
+ recoverRequest: (operationId) => this._recoverOwnershipRequest(operationId),
509
511
  send: (to, payload, messageId) => this.call('message.send', { to, encrypt: true, message_id: messageId, payload: payload }),
510
512
  emit: (event, payload) => this._dispatcher.enqueue(event, payload),
511
513
  });
512
514
  }
513
- _persistOwnershipRecords() {
514
- const aid = String(this._aid ?? '').trim();
515
- if (!aid)
516
- return;
517
- const metadata = this._tokenStore.loadMetadata?.(aid) ?? {};
518
- const existing = metadata.ownership_records;
519
- const records = existing && typeof existing === 'object' && !Array.isArray(existing)
520
- ? { ...existing } : {};
521
- for (const [key, record] of this._ownershipRecords) {
522
- if (![record.subject_aid, record.owner_aid].some((value) => String(value ?? '').trim().toLowerCase() === aid.toLowerCase()))
523
- continue;
524
- records[key] = Object.fromEntries(Object.entries(record).filter(([name]) => !['subject_identity', 'escrow_password', 'escrow_download_url'].includes(name)));
515
+ async _recoverOwnershipRequest(operationId) {
516
+ const identity = this._aid, version = this._serviceEventIdentityVersion;
517
+ const current = () => { if (this._aid !== identity || this._serviceEventIdentityVersion !== version)
518
+ throw ownershipFailure('conflict', 'ownership request identity changed'); };
519
+ const catalog = await this.call('message.peers', { filter: 'all', limit: 200 });
520
+ current();
521
+ for (const peer of Array.isArray(catalog.peers) ? catalog.peers.slice(0, 200) : []) {
522
+ const sender = String(peer.sender_aid ?? '');
523
+ const history = await this.call('message.history', { sender_aid: sender, before_seq: Number.MAX_SAFE_INTEGER, limit: 200 });
524
+ current();
525
+ for (const row of Array.isArray(history.messages) ? history.messages : []) {
526
+ const envelope = row, content = envelope.payload ?? envelope;
527
+ const raw = decryptedOwnershipJsonText(content) ?? content;
528
+ let value = raw;
529
+ if (typeof raw === 'string') {
530
+ try {
531
+ value = JSON.parse(raw);
532
+ }
533
+ catch {
534
+ continue;
535
+ }
536
+ }
537
+ if (!isJsonObject(value) || value.type !== OWNERSHIP_SDK_MESSAGE_TYPE || value.operation_id !== operationId || !['bind.request', 'transfer.request'].includes(String(value.action)))
538
+ continue;
539
+ const authenticated = String(envelope.from_aid ?? envelope.from ?? envelope.sender_aid ?? '');
540
+ if (authenticated !== sender)
541
+ throw ownershipFailure('forbidden', 'ownership history sender changed');
542
+ return { from: authenticated, payload: parseOwnershipMessage(raw, { authenticated_from_aid: authenticated, local_aid: identity }) };
543
+ }
525
544
  }
526
- this._tokenStore.saveMetadata?.(aid, { ownership_records: records });
545
+ return null;
527
546
  }
528
- _restoreOwnershipRecords() {
547
+ _restoreIdentityType() {
529
548
  const aid = String(this._aid ?? '').trim();
530
549
  if (!aid)
531
550
  return;
532
551
  const metadata = this._tokenStore.loadMetadata?.(aid);
533
- const intent = metadata?.registration_intent;
534
- if (intent?.type)
535
- this._ownershipType = String(intent.type);
536
- const records = metadata?.ownership_records;
537
- if (!records || typeof records !== 'object' || Array.isArray(records))
538
- return;
539
- for (const [key, value] of Object.entries(records)) {
540
- if (!value || typeof value !== 'object' || Array.isArray(value))
541
- continue;
542
- const record = value;
543
- this._ownershipRecords.set(key, record);
544
- if (String(record.subject_aid ?? '').toLowerCase() === aid.toLowerCase())
545
- this._ownershipType = String(record.type ?? this._ownershipType);
546
- }
547
- }
548
- async _resumeOwnershipAfterConnect() {
549
- await this._checkIdentityAdmissionAfterConnect();
550
- await this._resumeRegistrationIntent();
551
- await this._resumeOwnership();
552
+ const legacy = metadata?.registration_intent;
553
+ if (metadata?.identity_type ?? legacy?.type)
554
+ this._ownershipType = String(metadata?.identity_type ?? legacy?.type);
552
555
  }
556
+ _ownershipOperation = false;
557
+ _ownershipOperationGuard;
553
558
  _credentialRenewal;
554
559
  _credentialRenewalTimer = null;
555
560
  _credentialRenewalAbort = null;
556
561
  _credentialRenewalScan = Promise.resolve();
562
+ _nextCredentialRevocationSyncAt = 0;
557
563
  _getCredentialRenewal() {
558
564
  return this._credentialRenewal ??= new CredentialRenewal({
559
565
  currentAid: () => this._aid ?? '',
566
+ queryContext: () => JSON.stringify([this._serviceEventIdentityVersion, this._gatewayUrl, this._transport?._connectionGeneration, this._transport?._workerGeneration]),
560
567
  atomicCheckpointSave: () => this._tokenStore instanceof LocalTokenStore,
561
568
  load: (aid) => (this._tokenStore._loadMetadataStrict?.(aid) ?? this._tokenStore.loadMetadata?.(aid))?.credential_renewals_v1 ?? {},
562
569
  ...(typeof this._tokenStore._loadMetadataStrict === 'function' ? {
@@ -630,6 +637,7 @@ export class AUNClient {
630
637
  if (aid === this._aid && this._identity) {
631
638
  this._identity.cert = identity.certPem;
632
639
  this._currentAid = identity;
640
+ this._auth.setIdentity(this._identity);
633
641
  }
634
642
  }
635
643
  return identity;
@@ -665,14 +673,14 @@ export class AUNClient {
665
673
  this._credentialRenewalAbort = abort;
666
674
  const current = () => this._credentialRenewalAbort === abort && !abort.signal.aborted && aid === this._aid && version === this._serviceEventIdentityVersion && ['ready', 'connected'].includes(this._state) && !this._closing && !this._disconnecting;
667
675
  const guard = { current, signal: abort.signal };
668
- let nextRevocationSync = Date.now() + 3_600_000;
676
+ this._nextCredentialRevocationSyncAt ||= Date.now() + 3_600_000;
669
677
  const scan = async () => {
670
678
  const task = this._credentialRenewalScan.then(async () => {
671
679
  if (!current())
672
680
  return;
673
- const syncRevocations = Date.now() >= nextRevocationSync;
681
+ const syncRevocations = Date.now() >= this._nextCredentialRevocationSyncAt;
674
682
  if (syncRevocations)
675
- nextRevocationSync = Date.now() + 3_600_000;
683
+ this._nextCredentialRevocationSyncAt = Date.now() + 3_600_000;
676
684
  await this._resumeCredentialRenewalAfterConnect(guard, syncRevocations);
677
685
  });
678
686
  this._credentialRenewalScan = task.catch(() => { });
@@ -710,6 +718,10 @@ export class AUNClient {
710
718
  const verified = await verifyServiceEvent(event, payload, { targetAid, resolveEventCertificate: (aid, fingerprint) => this._fetchServiceEventCertificate(aid, fingerprint), resolveCredentialService: this.resolveCredentialService });
711
719
  if (!current())
712
720
  throw new ValidationError('service event receiver identity changed');
721
+ const eventName = event.replace(/^event\//, '');
722
+ if (eventName.startsWith('org.') || ['credential.revoked', 'credential.revocationList'].includes(eventName)) {
723
+ this._getCredentialRenewal().invalidateQueries?.();
724
+ }
713
725
  if (['credential.revoked', 'credential.revocationList'].includes(event.replace(/^event\//, ''))) {
714
726
  await this._getCredentialRenewal()._receiveRevocations(payload, { current, signal });
715
727
  }
@@ -740,18 +752,6 @@ export class AUNClient {
740
752
  await this._safeAsync(this._getCredentialRenewal().receive(String(envelope.from ?? envelope.from_aid ?? envelope.sender_aid ?? ''), parsed));
741
753
  return true;
742
754
  }
743
- async _resumeRegistrationIntent() {
744
- const intent = this._tokenStore.loadMetadata?.(this._aid)?.registration_intent;
745
- if (['group', 'org'].includes(String(intent?.type ?? this.getType())))
746
- return;
747
- if (intent?.protocol !== '2.1' || intent.pending !== true)
748
- return;
749
- await this._getOwnershipWorkflow().resumeRegistration(String(intent.owner), String(intent.operation_id));
750
- this._tokenStore.saveMetadata?.(this._aid, { registration_intent: { ...intent, pending: false } });
751
- }
752
- async _resumeOwnership() {
753
- await this._getOwnershipWorkflow().resume();
754
- }
755
755
  /** 实例级 protected_headers */
756
756
  _instanceProtectedHeaders = null;
757
757
  /** 重连退避时间戳(ms) */
@@ -947,7 +947,7 @@ export class AUNClient {
947
947
  net: dnsNet,
948
948
  });
949
949
  this._aid = initAid;
950
- this._restoreOwnershipRecords();
950
+ this._restoreIdentityType();
951
951
  this._agentMdManager = createAgentMdManagerForRuntime({
952
952
  config: () => this._configModel,
953
953
  logger: () => this._logger.for('aun_core.agent_md'),
@@ -1128,9 +1128,6 @@ export class AUNClient {
1128
1128
  get currentAid() {
1129
1129
  return this._currentAid;
1130
1130
  }
1131
- _ownershipKey(subjectAid, ownerAid) {
1132
- return `${String(subjectAid ?? '').trim().toLowerCase()}|${String(ownerAid ?? '').trim().toLowerCase()}`;
1133
- }
1134
1131
  getType() {
1135
1132
  return String(this._ownershipType || 'agent').trim() || 'agent';
1136
1133
  }
@@ -1138,45 +1135,12 @@ export class AUNClient {
1138
1135
  return this.getType();
1139
1136
  }
1140
1137
  getOwner() {
1141
- const aid = String(this._aid ?? '').trim().toLowerCase();
1142
- let best = null;
1143
- let bestActive = false;
1144
- for (const record of this._ownershipRecords.values()) {
1145
- if (String(record.subject_aid ?? '').trim().toLowerCase() === aid) {
1146
- if (String(record.status ?? '').trim().toLowerCase() === 'released')
1147
- continue;
1148
- const owner = String(record.owner_aid ?? '').trim();
1149
- if (!owner)
1150
- continue;
1151
- const active = String(record.status ?? '').trim().toLowerCase() === 'active';
1152
- if (!best || active && !bestActive) {
1153
- best = owner;
1154
- bestActive = active;
1155
- }
1156
- }
1157
- }
1158
- return best;
1138
+ return null;
1159
1139
  }
1160
1140
  get_owner() {
1161
1141
  return this.getOwner();
1162
1142
  }
1163
1143
  getOwnershipStatus() {
1164
- const aid = String(this._aid ?? '').trim().toLowerCase();
1165
- let best = null;
1166
- let bestActive = false;
1167
- for (const record of this._ownershipRecords.values()) {
1168
- const subject = String(record.subject_aid ?? '').trim().toLowerCase();
1169
- const owner = String(record.owner_aid ?? '').trim().toLowerCase();
1170
- if (aid !== subject && aid !== owner)
1171
- continue;
1172
- const active = String(record.status ?? '').trim().toLowerCase() === 'active';
1173
- if (!best || active && !bestActive) {
1174
- best = record;
1175
- bestActive = active;
1176
- }
1177
- }
1178
- if (best)
1179
- return Object.fromEntries(Object.entries(best).filter(([key]) => key !== 'subject_identity' && key !== 'escrow_password' && key !== 'escrow_download_url'));
1180
1144
  return { subject_aid: this._aid ?? '', owner_aid: null, status: 'legacy_unbound', type: this.getType() };
1181
1145
  }
1182
1146
  get_ownership_status() {
@@ -1184,10 +1148,61 @@ export class AUNClient {
1184
1148
  }
1185
1149
  async bindOwneraid(ownerAid) { return this._getOwnershipWorkflow().bindOwneraid(ownerAid); }
1186
1150
  async bindSubaid(subAid) { return this._getOwnershipWorkflow().bindSubaid(subAid); }
1187
- async resetOwnershipTask() { return this._getOwnershipWorkflow().resetOwnershipTask(); }
1151
+ async bindLocalSubaid(subClient) { return this._localOwnershipOperation(subClient); }
1152
+ async transferLocalSubaid(subClient, newOwnerClient) { return this._localOwnershipOperation(subClient, newOwnerClient); }
1153
+ _createOwnershipOperationClient(slotId) {
1154
+ const aid = this._currentAid;
1155
+ const client = new AUNClient(AID._create({ aid: aid.aid, aunPath: aid.aunPath, certPem: aid.certPem, privateKeyPem: aid.privateKeyPem,
1156
+ certValid: aid.isCertValid(), privateKeyValid: aid.isPrivateKeyValid(), deviceId: this._deviceId, slotId,
1157
+ verifySsl: aid.verifySsl, rootCaPath: aid.rootCaPath, debug: aid.debug, aidStore: aid._aidStore }));
1158
+ client._ownershipOperation = true;
1159
+ client._ownershipType = this.getType();
1160
+ return client;
1161
+ }
1162
+ async _localOwnershipOperation(sub, next) {
1163
+ const sources = [this, sub, ...(next ? [next] : [])];
1164
+ if (sources.some((source) => !(source instanceof AUNClient) || !source._currentAid?.isPrivateKeyValid() || !source._currentAid.isCertValid() || !source._currentAid.privateKeyPem || source._currentAid.aid !== source._aid || source.getType() !== 'agent') || new Set(sources.map((source) => source._aid)).size !== sources.length)
1165
+ throw ownershipFailure('forbidden', 'local ownership requires distinct agent clients with private identities');
1166
+ const identities = sources.map((source) => [source._aid, source._currentAid, source._serviceEventIdentityVersion]);
1167
+ const guard = () => { if (sources.some((source, index) => source._aid !== identities[index][0] || source._currentAid !== identities[index][1] || source._serviceEventIdentityVersion !== identities[index][2]))
1168
+ throw ownershipFailure('conflict', 'local ownership source identity changed'); };
1169
+ for (const source of sources) {
1170
+ guard();
1171
+ const document = await source._agentMdManager.readAgentMdFresh(source._aid);
1172
+ guard();
1173
+ if (!document.exists)
1174
+ throw ownershipFailure('forbidden', 'local ownership requires a published agent document');
1175
+ const verified = await source._getOwnershipFiles().verifyFile(document.content, 'agent', source._aid);
1176
+ guard();
1177
+ if (verified.frontmatter.type !== 'agent')
1178
+ throw ownershipFailure('forbidden', 'local ownership requires verified agent identities');
1179
+ }
1180
+ const slot = `ownership-${crypto.randomUUID()}`, clients = [];
1181
+ try {
1182
+ for (const source of sources) {
1183
+ const client = source._createOwnershipOperationClient(slot);
1184
+ client._ownershipOperationGuard = guard;
1185
+ clients.push(client);
1186
+ }
1187
+ for (const client of clients) {
1188
+ guard();
1189
+ await client.connect({ connection_kind: 'short', background_sync: false, auto_reconnect: false });
1190
+ guard();
1191
+ }
1192
+ return next ? await clients[0]._getOwnershipWorkflow().transferLocalSubaid(clients[1]._getOwnershipWorkflow(), clients[2]._getOwnershipWorkflow()) : await clients[0]._getOwnershipWorkflow().bindLocalSubaid(clients[1]._getOwnershipWorkflow());
1193
+ }
1194
+ finally {
1195
+ await Promise.all(clients.map((client) => client.close()));
1196
+ }
1197
+ }
1188
1198
  async releaseSubaid(subAid) { return this._getOwnershipWorkflow().releaseSubaid(subAid); }
1189
1199
  async transferSubaid(subAid, newOwnerAid) { return this._getOwnershipWorkflow().transferSubaid(subAid, newOwnerAid); }
1190
- 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
+ }
1191
1206
  async bindAid(subjectAid, options = {}) {
1192
1207
  throw ownershipFailure('unsupported_protocol', 'legacy bindAid is unsupported; use bindSubaid');
1193
1208
  }
@@ -1209,7 +1224,28 @@ export class AUNClient {
1209
1224
  return false;
1210
1225
  const envelope = message;
1211
1226
  const content = 'payload' in envelope ? envelope.payload : message;
1212
- return this._getOwnershipWorkflow().receive(String(envelope.from_aid ?? ''), decryptedOwnershipJsonText(content) ?? content);
1227
+ const raw = decryptedOwnershipJsonText(content) ?? content, sender = String(envelope.from_aid ?? '');
1228
+ let value = raw;
1229
+ if (typeof value === 'string') {
1230
+ try {
1231
+ value = JSON.parse(value);
1232
+ }
1233
+ catch {
1234
+ return false;
1235
+ }
1236
+ }
1237
+ if (value && typeof value === 'object' && !Array.isArray(value) && value.type === OWNERSHIP_SDK_MESSAGE_TYPE) {
1238
+ const control = value, request = control.action === 'bind.request' || control.action === 'transfer.request';
1239
+ try {
1240
+ parseOwnershipMessage(raw, { authenticated_from_aid: sender, local_aid: this._aid, ...(request ? {} : { task: { ...control, initiator_aid: this._aid } }) });
1241
+ }
1242
+ catch (error) {
1243
+ if (error instanceof ValidationError && !error.retryable || error instanceof StateError && !error.retryable && ['invalid', 'forbidden'].includes(error.stringCode))
1244
+ return true;
1245
+ throw error;
1246
+ }
1247
+ }
1248
+ return this._getOwnershipWorkflow().receive(sender, raw);
1213
1249
  }
1214
1250
  get hasIdentity() {
1215
1251
  return this._currentAid !== null && this.state !== ConnectionState.CLOSED;
@@ -1394,14 +1430,16 @@ export class AUNClient {
1394
1430
  this._identityRuntime.loadIdentity(aid);
1395
1431
  this._stopCredentialRenewal();
1396
1432
  this._serviceEventIdentityVersion++;
1433
+ this._auth.invalidateStrictPkiCache();
1434
+ this._certCache.clear();
1435
+ this._certFetchInFlight.clear();
1397
1436
  this._serviceEventIdentityAbort.abort();
1398
1437
  this._serviceEventIdentityAbort = new AbortController();
1399
1438
  this._ensureReadStateIdentity();
1400
1439
  if (previousAid !== this._aid) {
1401
- this._ownershipRecords.clear();
1402
1440
  this._ownershipType = 'agent';
1403
1441
  }
1404
- this._restoreOwnershipRecords();
1442
+ this._restoreIdentityType();
1405
1443
  }
1406
1444
  setProtectedHeaders(headers) {
1407
1445
  if (!headers) {
@@ -3339,6 +3377,8 @@ export class AUNClient {
3339
3377
  this._clientLog.debug(`bootstrap CA chain cache skipped: peer=${aid}, source=${opts.source ?? 'unknown'}, err=${formatCaughtError(exc)}`);
3340
3378
  }
3341
3379
  }
3380
+ const auth = this._auth;
3381
+ const pkiEpoch = auth._strictPkiEpoch;
3342
3382
  try {
3343
3383
  if (opts.signedAt === undefined)
3344
3384
  await this._auth.verifyPeerCertificate(peerGatewayUrl, certPem, aid);
@@ -3356,20 +3396,21 @@ export class AUNClient {
3356
3396
  }
3357
3397
  throw new ValidationError(`peer cert verification failed for ${aid}: ${exc instanceof Error ? exc.message : String(exc)}`);
3358
3398
  }
3399
+ if (auth !== this._auth || pkiEpoch !== auth._strictPkiEpoch)
3400
+ throw new ValidationError('peer certificate validation context changed');
3359
3401
  const nowSec = Date.now() / 1000;
3360
3402
  const entry = {
3361
3403
  certPem,
3362
3404
  validatedAt: nowSec,
3363
- refreshAfter: nowSec + PEER_CERT_CACHE_TTL,
3405
+ refreshAfter: Math.min(nowSec + PEER_CERT_CACHE_TTL, Date.parse(x509Cert.validTo) / 1000, auth._gatewayChainCache.get(`${peerGatewayUrl}:${aid}`)?.expiresAt / 1000 || Infinity),
3406
+ auth,
3407
+ pkiEpoch,
3364
3408
  };
3365
- const cacheKey = AUNClient._certCacheKey(aid, certFingerprint) + (opts.signedAt === undefined ? '' : `:signed:${opts.signedAt}${opts.agentMdRead ? ':agentmd' : ''}`);
3409
+ const scope = opts.signedAt === undefined ? '' : `:historical${opts.agentMdRead ? ':agentmd' : ''}`;
3410
+ const actualFp = `sha256:${x509Cert.fingerprint256.replace(/:/g, '').toLowerCase()}`;
3411
+ const cacheKey = AUNClient._certCacheKey(aid, actualFp) + scope;
3366
3412
  this._certCache.set(cacheKey, entry);
3367
- if (!certFingerprint && opts.signedAt === undefined) {
3368
- const bareKey = AUNClient._certCacheKey(aid);
3369
- this._certCache.set(bareKey, entry);
3370
- const actualFp = `sha256:${x509Cert.fingerprint256.replace(/:/g, '').toLowerCase()}`;
3371
- this._certCache.set(AUNClient._certCacheKey(aid, actualFp), entry);
3372
- }
3413
+ this._certCache.set(AUNClient._certCacheKey(aid, certFingerprint) + scope, entry);
3373
3414
  try {
3374
3415
  // peer 证书只存版本目录,不覆盖 cert.pem
3375
3416
  this._tokenStore.saveCert(aid, certPem, opts.agentMdRead && opts.signedAt !== undefined ? `sha256:${x509Cert.fingerprint256.replace(/:/g, '').toLowerCase()}` : certFingerprint, { makeActive: false });
@@ -3391,14 +3432,46 @@ export class AUNClient {
3391
3432
  const tStart = Date.now();
3392
3433
  this._clientLog.debug(`_fetchPeerCert enter: aid=${aid}, fp=${certFingerprint ?? ''}`);
3393
3434
  try {
3394
- const cacheKey = AUNClient._certCacheKey(aid, certFingerprint) + (signedAt === undefined ? '' : `:signed:${signedAt}${agentMdRead ? ':agentmd' : ''}`);
3435
+ const cacheKey = AUNClient._certCacheKey(aid, certFingerprint) + (signedAt === undefined ? '' : `:historical${agentMdRead ? ':agentmd' : ''}`);
3395
3436
  const cached = this._certCache.get(cacheKey);
3396
3437
  const now = Date.now() / 1000;
3397
- if (cached && now < cached.refreshAfter) {
3438
+ const auth = this._auth, pkiEpoch = auth._strictPkiEpoch;
3439
+ if (cached && cached.auth === auth && cached.pkiEpoch === pkiEpoch && now < cached.refreshAfter) {
3440
+ if (signedAt === undefined) {
3441
+ const statusKey = `${cacheKey}:status:${pkiEpoch}`;
3442
+ let pending = this._certFetchInFlight.get(statusKey);
3443
+ if (!pending) {
3444
+ pending = (async () => {
3445
+ await auth.verifyPeerCertificate(AUNClient._resolvePeerGatewayUrl(this._gatewayUrl, aid), cached.certPem, aid);
3446
+ if (auth !== this._auth || pkiEpoch !== auth._strictPkiEpoch)
3447
+ throw new ValidationError('peer certificate status context changed');
3448
+ return cached.certPem;
3449
+ })();
3450
+ this._certFetchInFlight.set(statusKey, pending);
3451
+ }
3452
+ try {
3453
+ await pending;
3454
+ }
3455
+ finally {
3456
+ if (this._certFetchInFlight.get(statusKey) === pending)
3457
+ this._certFetchInFlight.delete(statusKey);
3458
+ }
3459
+ }
3460
+ else {
3461
+ const peerGatewayUrl = AUNClient._resolvePeerGatewayUrl(this._gatewayUrl, aid);
3462
+ if (agentMdRead) {
3463
+ validateAgentMdCertificate(cached.certPem, aid, signedAt, { requireCurrentValidity: false, readCompatibility: true });
3464
+ await auth._verifyAuthCertChain(peerGatewayUrl, new crypto.X509Certificate(cached.certPem), aid, signedAt);
3465
+ }
3466
+ else
3467
+ await auth.verifyPeerCertificate(peerGatewayUrl, cached.certPem, aid, signedAt);
3468
+ if (auth !== this._auth || pkiEpoch !== auth._strictPkiEpoch)
3469
+ throw new ValidationError('peer certificate validation context changed');
3470
+ }
3398
3471
  this._clientLog.debug(`_fetchPeerCert exit: elapsed=${Date.now() - tStart}ms aid=${aid} (cache_hit)`);
3399
3472
  return cached.certPem;
3400
3473
  }
3401
- const inFlightKey = `${cacheKey}:timeout:${timeoutMs}`;
3474
+ const inFlightKey = `${cacheKey}:signed:${signedAt ?? 'current'}:timeout:${timeoutMs}:pki:${pkiEpoch}`;
3402
3475
  const pending = this._certFetchInFlight.get(inFlightKey);
3403
3476
  if (pending)
3404
3477
  return await pending;
@@ -3409,6 +3482,8 @@ export class AUNClient {
3409
3482
  }
3410
3483
  const peerGatewayUrl = AUNClient._resolvePeerGatewayUrl(gatewayUrl, aid);
3411
3484
  const certPem = await _httpGetText(AUNClient._buildCertUrl(peerGatewayUrl, aid, certFingerprint), this._configModel.verifySsl, timeoutMs);
3485
+ if (auth !== this._auth || pkiEpoch !== auth._strictPkiEpoch)
3486
+ throw new ValidationError('peer certificate fetch context changed');
3412
3487
  const validated = await this._validateAndCachePeerCert({
3413
3488
  aid,
3414
3489
  certPem,
@@ -3829,7 +3904,9 @@ export class AUNClient {
3829
3904
  }
3830
3905
  /** 构建证书下载 URL */
3831
3906
  static _certCacheKey(aid, certFingerprint) {
3832
- const normalized = String(certFingerprint ?? '').trim().toLowerCase();
3907
+ const raw = String(certFingerprint ?? '').trim().toLowerCase();
3908
+ const hex = normalizeFingerprintHex(raw);
3909
+ const normalized = hex ? `sha256:${hex}` : raw;
3833
3910
  return normalized ? `${aid}#${normalized}` : aid;
3834
3911
  }
3835
3912
  static _buildCertUrl(gatewayUrl, aid, certFingerprint) {
@@ -3984,7 +4061,8 @@ export class AUNClient {
3984
4061
  this._clientLog.warn(`schedule post-connect P2P gap fill failed: ${formatCaughtError(exc)}`);
3985
4062
  });
3986
4063
  }
3987
- this._safeAsync(this._resumeOwnershipAfterConnect());
4064
+ if (!this._ownershipOperation)
4065
+ this._safeAsync(this._checkIdentityAdmissionAfterConnect());
3988
4066
  this._clientLog.debug(`_connectOnce exit: elapsed=${Date.now() - tStart}ms gateway=${gatewayUrl}, aid=${this._aid ?? ''}`);
3989
4067
  }
3990
4068
  catch (err) {
@@ -4874,6 +4952,9 @@ export class AUNClient {
4874
4952
  /** 停止所有后台任务 */
4875
4953
  _stopBackgroundTasks() {
4876
4954
  this._stopCredentialRenewal();
4955
+ this._auth.invalidateStrictPkiCache();
4956
+ this._certCache.clear();
4957
+ this._certFetchInFlight.clear();
4877
4958
  if (this._heartbeatTimer !== null) {
4878
4959
  clearInterval(this._heartbeatTimer);
4879
4960
  this._heartbeatTimer = null;