@agentunion/fastaun 0.5.18 → 0.5.20

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 (122) hide show
  1. package/CHANGELOG.md +172 -79
  2. package/_packed_docs/CHANGELOG.md +172 -79
  3. package/_packed_docs/INDEX.md +244 -116
  4. package/_packed_docs/KITE_DOCS_GUIDE.md +98 -69
  5. package/_packed_docs/agent.md/SCHEMA.md +245 -205
  6. 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
  7. 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 +42 -15
  8. package/_packed_docs/aun/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md +4 -4
  9. package/_packed_docs/protocol/01-/350/272/253/344/273/275/344/270/216/345/207/255/350/257/201/345/215/217/350/256/256-auth.md +45 -134
  10. package/_packed_docs/protocol/06-/346/234/215/345/212/241/345/215/217/350/256/256.md +1072 -1072
  11. package/_packed_docs/protocol/07-/351/224/231/350/257/257/347/240/201/344/270/216/347/212/266/346/200/201/346/234/272.md +5 -20
  12. package/_packed_docs/protocol/10-Group-/345/255/220/345/215/217/350/256/256.md +687 -668
  13. package/_packed_docs/protocol/11-Storage-/345/255/220/345/215/217/350/256/256.md +19 -28
  14. package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/211.md +443 -0
  15. package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2112.0.md +161 -0
  16. package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2112.1.md +658 -0
  17. package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2113.0.md +233 -0
  18. package/_packed_docs/protocol/agent.md/SCHEMA.md +4 -0
  19. package/_packed_docs/sdk/03-/346/240/270/345/277/203/346/246/202/345/277/265.md +1 -1
  20. package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +68 -61
  21. package/_packed_docs/sdk/05-E2EE/345/212/240/345/257/206/351/200/232/344/277/241.md +1 -1
  22. package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +279 -285
  23. package/_packed_docs/sdk/08-/346/234/200/344/275/263/345/256/236/350/267/265.md +3 -3
  24. package/_packed_docs/sdk/09-group-rpc-manual.md +271 -235
  25. package/_packed_docs/sdk/09-message-rpc-manual.md +47 -6
  26. package/_packed_docs/sdk/09-storage-rpc-manual.md +70 -68
  27. package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +63 -34
  28. package/_packed_docs/sdk/INDEX.md +105 -34
  29. package/_packed_docs/sdk/Ownership-SDK/345/256/236/347/216/260/346/214/207/345/215/227.md +169 -0
  30. package/_packed_docs/sdk/Ownership-SDK/347/274/226/346/216/222/346/214/207/345/215/227.md +349 -0
  31. package/_packed_docs/sdk/Ownership-SDK/347/274/226/346/216/222/346/214/207/345/215/2272.1.md +922 -0
  32. package/_packed_docs/sdk/Ownership-SDK/351/233/206/346/210/220/346/214/207/345/215/227.md +270 -0
  33. 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 +300 -0
  34. 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 +382 -0
  35. 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
  36. package/_packed_docs/sdk/Ownership2.1/350/257/246/347/273/206/350/256/276/350/256/241.md +476 -0
  37. package/_packed_docs/sdk/Ownership/347/256/241/347/220/206/344/270/223/351/242/230.md +559 -0
  38. package/_packed_docs/sdk/ProtectedHeaders/347/224/250/346/263/225/344/270/223/351/242/230.md +299 -0
  39. package/_packed_docs/sdk//345/207/255/350/257/201/344/270/216/347/273/204/347/273/207/346/234/215/345/212/241SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +173 -0
  40. package/_packed_docs/sdk//345/207/255/350/257/201/344/270/216/347/273/204/347/273/207/346/234/215/345/212/241SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +211 -0
  41. 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
  42. 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
  43. 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
  44. 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
  45. 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
  46. 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
  47. package/dist/agent-md-schema.d.ts +15 -1
  48. package/dist/agent-md-schema.js +87 -24
  49. package/dist/agent-md-schema.js.map +1 -1
  50. package/dist/agent-md.d.ts +54 -2
  51. package/dist/agent-md.js +637 -53
  52. package/dist/agent-md.js.map +1 -1
  53. package/dist/aid-store.d.ts +38 -59
  54. package/dist/aid-store.js +300 -265
  55. package/dist/aid-store.js.map +1 -1
  56. package/dist/aid.js +30 -11
  57. package/dist/aid.js.map +1 -1
  58. package/dist/auth.d.ts +6 -1
  59. package/dist/auth.js +500 -15
  60. package/dist/auth.js.map +1 -1
  61. package/dist/cert-utils.d.ts +44 -2
  62. package/dist/cert-utils.js +149 -49
  63. package/dist/cert-utils.js.map +1 -1
  64. package/dist/client/delivery.d.ts +11 -1
  65. package/dist/client/delivery.js +321 -62
  66. package/dist/client/delivery.js.map +1 -1
  67. package/dist/client/rpc-pipeline.d.ts +1 -0
  68. package/dist/client/rpc-pipeline.js +142 -82
  69. package/dist/client/rpc-pipeline.js.map +1 -1
  70. package/dist/client/v2-e2ee.d.ts +1 -0
  71. package/dist/client/v2-e2ee.js +166 -96
  72. package/dist/client/v2-e2ee.js.map +1 -1
  73. package/dist/client.d.ts +84 -1
  74. package/dist/client.js +780 -30
  75. package/dist/client.js.map +1 -1
  76. package/dist/errors.js +4 -3
  77. package/dist/errors.js.map +1 -1
  78. package/dist/events.d.ts +9 -3
  79. package/dist/events.js +69 -11
  80. package/dist/events.js.map +1 -1
  81. package/dist/facades.d.ts +83 -3
  82. package/dist/facades.js +100 -6
  83. package/dist/facades.js.map +1 -1
  84. package/dist/index.d.ts +9 -4
  85. package/dist/index.js +3 -2
  86. package/dist/index.js.map +1 -1
  87. package/dist/keystore/aid-db.d.ts +5 -0
  88. package/dist/keystore/aid-db.js +57 -0
  89. package/dist/keystore/aid-db.js.map +1 -1
  90. package/dist/keystore/index.d.ts +8 -0
  91. package/dist/keystore/local-identity-store.js +10 -3
  92. package/dist/keystore/local-identity-store.js.map +1 -1
  93. package/dist/keystore/local-token-store.d.ts +6 -1
  94. package/dist/keystore/local-token-store.js +24 -1
  95. package/dist/keystore/local-token-store.js.map +1 -1
  96. package/dist/org-protocol.d.ts +98 -0
  97. package/dist/org-protocol.js +1753 -0
  98. package/dist/org-protocol.js.map +1 -0
  99. package/dist/ownership-proof.d.ts +82 -5
  100. package/dist/ownership-proof.js +327 -31
  101. package/dist/ownership-proof.js.map +1 -1
  102. package/dist/ownership-v21.d.ts +196 -0
  103. package/dist/ownership-v21.js +712 -0
  104. package/dist/ownership-v21.js.map +1 -0
  105. package/dist/ownership-workflow.d.ts +87 -0
  106. package/dist/ownership-workflow.js +1182 -0
  107. package/dist/ownership-workflow.js.map +1 -0
  108. package/dist/seq-tracker.d.ts +1 -0
  109. package/dist/seq-tracker.js +10 -1
  110. package/dist/seq-tracker.js.map +1 -1
  111. package/dist/tools/cross-sdk-agent.d.ts +90 -1
  112. package/dist/tools/cross-sdk-agent.js +441 -7
  113. package/dist/tools/cross-sdk-agent.js.map +1 -1
  114. package/dist/transport.d.ts +8 -0
  115. package/dist/transport.js +118 -4
  116. package/dist/transport.js.map +1 -1
  117. package/dist/v2/e2ee/decrypt.d.ts +1 -0
  118. package/dist/v2/e2ee/decrypt.js +9 -1
  119. package/dist/v2/e2ee/decrypt.js.map +1 -1
  120. package/dist/version.d.ts +1 -1
  121. package/dist/version.js +1 -1
  122. package/package.json +1 -1
package/dist/client.js CHANGED
@@ -26,7 +26,7 @@ import { normalizeGroupId } from './group-id.js';
26
26
  import { validateAIDFormat, validateGroupIDFormat } from './validators.js';
27
27
  import { RPCTransport } from './transport.js';
28
28
  import { AuthFlow } from './auth.js';
29
- import { AgentMdManager, buildDefaultAgentMd } from './agent-md.js';
29
+ import { AgentMdManager, buildDefaultAgentMd, updateOwnershipAgentMd } from './agent-md.js';
30
30
  import { GroupIndexMetaCache } from './group-index.js';
31
31
  import { SeqTracker } from './seq-tracker.js';
32
32
  import { ClientRuntime } from './client/runtime.js';
@@ -37,15 +37,20 @@ import { LifecycleController } from './client/lifecycle.js';
37
37
  import { PeerDirectory } from './client/peers.js';
38
38
  import { RpcPipeline } from './client/rpc-pipeline.js';
39
39
  import { CollabClient } from './collab/client.js';
40
- import { GroupFacade, MailFacade, MessageFacade, StreamFacade } from './facades.js';
40
+ import { CredentialFacade, OrgFacade, GroupFacade, MailFacade, MessageFacade, StreamFacade } from './facades.js';
41
41
  import { StorageVFS } from './storage/vfs.js';
42
42
  import { V2E2EECoordinator } from './client/v2-e2ee.js';
43
43
  import { GroupStateCoordinator } from './client/group-state.js';
44
44
  import { decryptMessage, } from './v2/e2ee/index.js';
45
45
  import { ecdsaVerifyRaw } from './v2/crypto/ecdsa.js';
46
+ import { decryptedOwnershipJsonText } from './v2/e2ee/decrypt.js';
46
47
  import { isJsonObject, ConnectionState, STATE_TO_PUBLIC, } from './types.js';
47
48
  import { AID } from './aid.js';
48
- import { certMatchesFingerprint, normalizeFingerprintHex, publicKeyMatchesFingerprint } from './cert-utils.js';
49
+ import { certFingerprint, certMatchesFingerprint, normalizeFingerprintHex, publicKeyMatchesFingerprint } from './cert-utils.js';
50
+ import { validateAgentMdCertificate, validateAgentMdDocument } from './agent-md-schema.js';
51
+ import { OwnershipFiles, OwnershipStorageAdapter, ownershipFailure, signOwnershipFile } from './ownership-v21.js';
52
+ import { OwnershipWorkflow } from './ownership-workflow.js';
53
+ import { canonicalJson, createCredential, createMemberCredential, signMembershipBinding, verifyServiceEvent } from './org-protocol.js';
49
54
  function isPromiseLike(value) {
50
55
  return Boolean(value && typeof value.then === 'function');
51
56
  }
@@ -342,6 +347,9 @@ function createAgentMdManagerForRuntime(opts) {
342
347
  logger: opts.logger(),
343
348
  ownerAidGetter: opts.ownerAid,
344
349
  currentAidGetter: opts.currentAid,
350
+ entityOwnerResolver: opts.resolveEntityOwner,
351
+ entityIdentityGetter: opts.fetchEntityIdentity,
352
+ entitySignerResolver: opts.fetchEntitySigner,
345
353
  gatewayResolver: async (aid) => {
346
354
  const gatewayUrl = await opts.gateway.resolve(aid);
347
355
  opts.gateway.set(gatewayUrl);
@@ -388,11 +396,51 @@ function createAgentMdManagerForRuntime(opts) {
388
396
  opts.identity.set(identity);
389
397
  return token;
390
398
  },
391
- peerResolver: async (aid, certFingerprint) => {
399
+ uploadAccessTokenResolver: async (_aid, gatewayUrl) => {
400
+ const localAid = String(opts.ownerAid() ?? '').trim();
401
+ let identity = opts.identity.get();
402
+ if (!identity || String(identity.aid ?? '') !== localAid) {
403
+ throw new StateError('no local identity found, register or load an AID first');
404
+ }
405
+ const auth = opts.auth();
406
+ const cachedToken = String(identity.access_token ?? '');
407
+ const expiresAt = auth.getAccessTokenExpiry(identity);
408
+ if (cachedToken && (expiresAt === null || expiresAt > Date.now() / 1000 + 30))
409
+ return cachedToken;
410
+ if (identity.refresh_token) {
411
+ try {
412
+ const refreshed = await auth.refreshCachedTokens(gatewayUrl, identity);
413
+ const refreshedToken = String(refreshed.access_token ?? '');
414
+ const refreshedExpiry = auth.getAccessTokenExpiry(refreshed);
415
+ if (refreshedToken && (refreshedExpiry === null || refreshedExpiry > Date.now() / 1000 + 30)) {
416
+ opts.identity.set(refreshed);
417
+ return refreshedToken;
418
+ }
419
+ }
420
+ catch {
421
+ // refresh 失败时回退到完整 authenticate。
422
+ }
423
+ }
424
+ const result = await auth.authenticate(gatewayUrl, { aid: localAid });
425
+ const token = String(result.access_token ?? '');
426
+ if (!token)
427
+ throw new StateError('authenticate did not return access_token');
428
+ identity = auth.loadIdentityOrNone(localAid) ?? {
429
+ ...identity,
430
+ access_token: token,
431
+ refresh_token: String(result.refresh_token ?? identity.refresh_token ?? ''),
432
+ access_token_expires_at: typeof result.expires_at === 'number' ? result.expires_at : identity.access_token_expires_at,
433
+ token_exp: typeof result.expires_at === 'number' ? result.expires_at : identity.token_exp,
434
+ expires_at: typeof result.expires_at === 'number' ? result.expires_at : identity.expires_at,
435
+ };
436
+ opts.identity.set(identity);
437
+ return token;
438
+ },
439
+ peerResolver: async (aid, certFingerprint, signedAt) => {
392
440
  const target = String(aid ?? '').trim();
393
441
  const expectedFp = String(certFingerprint ?? '').trim().toLowerCase();
394
442
  const current = opts.currentAid();
395
- if (current?.aid === target) {
443
+ if (signedAt === undefined && current?.aid === target) {
396
444
  if (!expectedFp || certMatchesFingerprint(current.certPem, expectedFp))
397
445
  return current;
398
446
  }
@@ -402,7 +450,7 @@ function createAgentMdManagerForRuntime(opts) {
402
450
  }
403
451
  catch { /* best effort before cert fetch */ }
404
452
  }
405
- const certPem = String(await opts.fetchPeerCert(target, expectedFp || undefined) ?? '').trim();
453
+ const certPem = String(await opts.fetchPeerCert(target, expectedFp || undefined, signedAt) ?? '').trim();
406
454
  if (!certPem)
407
455
  throw new NotFoundError(`certificate not found for aid: ${target}`);
408
456
  return AID._create({
@@ -429,6 +477,90 @@ export class AUNClient {
429
477
  _state = 'no_identity';
430
478
  /** 当前 AID 值对象(新 API) */
431
479
  _currentAid = null;
480
+ _serviceEventIdentityVersion = 0;
481
+ /** ownership 关系只在 SDK 内存中编排;服务端不保存状态机。 */
482
+ _ownershipRecords = new Map();
483
+ _ownershipType = 'agent';
484
+ _ownershipStorage = null;
485
+ _ownershipFiles = null;
486
+ _ownershipWorkflow = null;
487
+ _getOwnershipStorage() {
488
+ 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) });
489
+ }
490
+ _getOwnershipFiles() {
491
+ return this._ownershipFiles ??= new OwnershipFiles({
492
+ currentAid: () => { if (!this._currentAid)
493
+ throw ownershipFailure('forbidden', 'ownership requires a loaded identity'); return this._currentAid; },
494
+ currentType: () => this.getType(),
495
+ now: () => Math.trunc(Date.now() / 1000),
496
+ readAgentMdFresh: (subAid) => this._agentMdManager.readOwnershipFresh(subAid),
497
+ submitAgentMdRaw: (subAid, raw, condition) => this._agentMdManager.submitOwnershipRaw(subAid, raw, condition),
498
+ readOwnerMd: (ownerAid, subAid) => this._getOwnershipStorage().readOwnerMd(ownerAid, subAid),
499
+ writeOwnerMd: (ownerAid, subAid, raw) => this._getOwnershipStorage().writeOwnerMd(ownerAid, subAid, raw),
500
+ deleteOwnerMd: (ownerAid, subAid) => this._getOwnershipStorage().deleteOwnerMd(ownerAid, subAid),
501
+ resolveCertificate: async (aid, fingerprint, purpose) => this._auth.resolveOwnershipCertificate(await this._resolveOwnershipCertificateGateway(aid), aid, fingerprint, purpose),
502
+ });
503
+ }
504
+ _getOwnershipWorkflow() {
505
+ return this._ownershipWorkflow ??= new OwnershipWorkflow({
506
+ currentAid: () => this._aid, store: this._tokenStore, files: this._getOwnershipFiles(),
507
+ currentType: () => this.getType(),
508
+ send: (to, payload) => this.call('message.send', { to, encrypt: true, payload: payload }),
509
+ emit: (event, payload) => this._dispatcher.enqueue(event, payload),
510
+ });
511
+ }
512
+ _persistOwnershipRecords() {
513
+ const aid = String(this._aid ?? '').trim();
514
+ if (!aid)
515
+ return;
516
+ const metadata = this._tokenStore.loadMetadata?.(aid) ?? {};
517
+ const existing = metadata.ownership_records;
518
+ const records = existing && typeof existing === 'object' && !Array.isArray(existing)
519
+ ? { ...existing } : {};
520
+ for (const [key, record] of this._ownershipRecords) {
521
+ if (![record.subject_aid, record.owner_aid].some((value) => String(value ?? '').trim().toLowerCase() === aid.toLowerCase()))
522
+ continue;
523
+ records[key] = Object.fromEntries(Object.entries(record).filter(([name]) => !['subject_identity', 'escrow_password', 'escrow_download_url'].includes(name)));
524
+ }
525
+ this._tokenStore.saveMetadata?.(aid, { ownership_records: records });
526
+ }
527
+ _restoreOwnershipRecords() {
528
+ const aid = String(this._aid ?? '').trim();
529
+ if (!aid)
530
+ return;
531
+ const metadata = this._tokenStore.loadMetadata?.(aid);
532
+ const intent = metadata?.registration_intent;
533
+ if (intent?.type)
534
+ this._ownershipType = String(intent.type);
535
+ const records = metadata?.ownership_records;
536
+ if (!records || typeof records !== 'object' || Array.isArray(records))
537
+ return;
538
+ for (const [key, value] of Object.entries(records)) {
539
+ if (!value || typeof value !== 'object' || Array.isArray(value))
540
+ continue;
541
+ const record = value;
542
+ this._ownershipRecords.set(key, record);
543
+ if (String(record.subject_aid ?? '').toLowerCase() === aid.toLowerCase())
544
+ this._ownershipType = String(record.type ?? this._ownershipType);
545
+ }
546
+ }
547
+ async _resumeOwnershipAfterConnect() {
548
+ await this._checkIdentityAdmissionAfterConnect();
549
+ await this._resumeRegistrationIntent();
550
+ await this._resumeOwnership();
551
+ }
552
+ async _resumeRegistrationIntent() {
553
+ const intent = this._tokenStore.loadMetadata?.(this._aid)?.registration_intent;
554
+ if (['group', 'org'].includes(String(intent?.type ?? this.getType())))
555
+ return;
556
+ if (intent?.protocol !== '2.1' || intent.pending !== true)
557
+ return;
558
+ await this._getOwnershipWorkflow().resumeRegistration(String(intent.owner), String(intent.operation_id));
559
+ this._tokenStore.saveMetadata?.(this._aid, { registration_intent: { ...intent, pending: false } });
560
+ }
561
+ async _resumeOwnership() {
562
+ await this._getOwnershipWorkflow().resume();
563
+ }
432
564
  /** 实例级 protected_headers */
433
565
  _instanceProtectedHeaders = null;
434
566
  /** 重连退避时间戳(ms) */
@@ -451,6 +583,7 @@ export class AUNClient {
451
583
  _transport;
452
584
  /** 认证流程 */
453
585
  _auth;
586
+ resolveCredentialService;
454
587
  /** 密钥存储 */
455
588
  _tokenStore;
456
589
  /** 会话参数(重连用) */
@@ -503,6 +636,10 @@ export class AUNClient {
503
636
  _v2SenderIKPending = new Map();
504
637
  /** sender IK 后台补齐任务去重。 */
505
638
  _v2SenderIKFetching = new Set();
639
+ _readStateCache = new Map();
640
+ _readStateQueries = new Map();
641
+ _readStateIdentity = null;
642
+ _readStateGeneration = 0;
506
643
  // ── 后台任务定时器 ──────────────────────────────────────────
507
644
  _heartbeatTimer = null;
508
645
  _tokenRefreshTimer = null;
@@ -551,6 +688,8 @@ export class AUNClient {
551
688
  _mail;
552
689
  _group;
553
690
  _stream;
691
+ _credential;
692
+ _org;
554
693
  constructor(aid) {
555
694
  if (aid !== null && aid !== undefined && !isAIDObject(aid)) {
556
695
  throw new ValidationError('AUNClient only accepts an AID object or no argument');
@@ -586,7 +725,7 @@ export class AUNClient {
586
725
  if (debugFlag) {
587
726
  this._clientLog.info(`AUNClient initialized (debug=true, aunPath=${this._configModel.aunPath})`);
588
727
  }
589
- this._dispatcher = new EventDispatcher(this._logger.for('aun_core.events'));
728
+ this._dispatcher = new EventDispatcher(this._logger.for('aun_core.events'), () => this._identity?.aid ?? '', () => this._ensureReadStateIdentity());
590
729
  const dnsNet = new DnsResilientNet({
591
730
  verifySsl: this._configModel.verifySsl,
592
731
  logger: this._clientLog,
@@ -596,6 +735,9 @@ export class AUNClient {
596
735
  logger: this._logger.for('aun_core.keystore'),
597
736
  });
598
737
  this._tokenStore = tokenStore;
738
+ this._ownershipStorage = null;
739
+ this._ownershipFiles = null;
740
+ this._ownershipWorkflow = null;
599
741
  this._slotId = inputAid?.slotId || 'default';
600
742
  this._connectDeliveryMode = normalizeDeliveryModeConfig({ mode: 'fanout' });
601
743
  this._defaultConnectDeliveryMode = { ...this._connectDeliveryMode };
@@ -611,6 +753,7 @@ export class AUNClient {
611
753
  net: dnsNet,
612
754
  });
613
755
  this._aid = initAid;
756
+ this._restoreOwnershipRecords();
614
757
  this._agentMdManager = createAgentMdManagerForRuntime({
615
758
  config: () => this._configModel,
616
759
  logger: () => this._logger.for('aun_core.agent_md'),
@@ -628,7 +771,10 @@ export class AUNClient {
628
771
  },
629
772
  auth: () => this._auth,
630
773
  tokenStore: () => this._tokenStore,
631
- fetchPeerCert: (target, certFingerprint) => this._fetchPeerCert(target, certFingerprint || undefined),
774
+ fetchPeerCert: (target, certFingerprint, signedAt) => this._fetchPeerCert(target, certFingerprint || undefined, undefined, signedAt, true),
775
+ resolveEntityOwner: (target, type) => this._resolveEntityAgentMdOwner(target, type),
776
+ fetchEntityIdentity: (target, type) => this._freshEntityCertificate(target, type),
777
+ fetchEntitySigner: (target, fingerprint) => this._freshEntitySigner(target, fingerprint),
632
778
  });
633
779
  this._transport = new RPCTransport({
634
780
  eventDispatcher: this._dispatcher,
@@ -638,7 +784,10 @@ export class AUNClient {
638
784
  dnsNet,
639
785
  });
640
786
  this._transport.setProtocolMetaObserver((meta) => this._observeRpcMeta(meta));
787
+ this._transport.setServiceEventVerifier((event, payload) => verifyServiceEvent(event, payload, { targetAid: this._identity?.aid ?? '', resolveEventCertificate: (aid, fingerprint) => this._fetchServiceEventCertificate(aid, fingerprint), resolveCredentialService: this.resolveCredentialService }), () => this._identity?.aid ?? '', () => this._serviceEventIdentityVersion);
641
788
  this._transport.setProtocolDisconnectCallback((err, closeCode) => this._handleTransportDisconnect(err, closeCode));
789
+ this._transport._onForegroundDrained = () => this._delivery.onForegroundPullsDrained();
790
+ this._transport._onRpcResponse = () => this._delivery.requestBackgroundPullRecovery();
642
791
  this._runtime = new ClientRuntime(this);
643
792
  this._identityRuntime = new IdentityRuntimeManager(this._runtime);
644
793
  this._peerDirectory = new PeerDirectory(this._runtime);
@@ -665,6 +814,7 @@ export class AUNClient {
665
814
  this._dispatcher.subscribeProtocol('_raw.message.received', (data) => this._onRawMessageReceived(data));
666
815
  // P2P 消息撤回推送:在线 push 与 pull tombstone 去重后再发布给应用层
667
816
  this._dispatcher.subscribeProtocol('_raw.message.recalled', (data) => this._safeAsync(this._onRawMessageRecalled(data)));
817
+ this._dispatcher.subscribeProtocol('_raw.message.read_changed', (data) => this._onRawReadChanged(data, 'p2p'));
668
818
  // V2 P2P 推送通知:收到通知后自动走 message.v2.pull 拉取并解密
669
819
  this._dispatcher.subscribeProtocol('_raw.peer.v2.message_received', (data) => {
670
820
  const ns = this._aid ? `p2p:${this._aid}` : '';
@@ -692,6 +842,7 @@ export class AUNClient {
692
842
  this._dispatcher.subscribeProtocol('_raw.group.message_created', (data) => this._onRawGroupMessageCreated(data));
693
843
  // 群组消息撤回推送:在线 push 通道,与 pull 双 tombstone 兜底互补(SDK 去重只回调一次)
694
844
  this._dispatcher.subscribeProtocol('_raw.group.message_recalled', (data) => this._safeAsync(this._onRawGroupMessageRecalled(data)));
845
+ this._dispatcher.subscribeProtocol('_raw.group.read_changed', (data) => this._onRawReadChanged(data, 'group'));
695
846
  // 群组变更事件:透传并触发 V2 state/SPK 维护
696
847
  this._dispatcher.subscribeProtocol('_raw.group.changed', (data) => this._onRawGroupChanged(data));
697
848
  // 定向邀请底层事件:按群 + 目标 AID 聚合后向应用层只提示一次。
@@ -724,6 +875,16 @@ export class AUNClient {
724
875
  return sessionToken || null;
725
876
  }
726
877
  /** AUN Storage VFS 入口 */
878
+ get credential() {
879
+ if (!this._credential)
880
+ this._credential = new CredentialFacade(this);
881
+ return this._credential;
882
+ }
883
+ get org() {
884
+ if (!this._org)
885
+ this._org = new OrgFacade(this);
886
+ return this._org;
887
+ }
727
888
  get storage() {
728
889
  if (!this._storage)
729
890
  this._storage = new StorageVFS(this);
@@ -757,6 +918,88 @@ export class AUNClient {
757
918
  get currentAid() {
758
919
  return this._currentAid;
759
920
  }
921
+ _ownershipKey(subjectAid, ownerAid) {
922
+ return `${String(subjectAid ?? '').trim().toLowerCase()}|${String(ownerAid ?? '').trim().toLowerCase()}`;
923
+ }
924
+ getType() {
925
+ return String(this._ownershipType || 'agent').trim() || 'agent';
926
+ }
927
+ get_type() {
928
+ return this.getType();
929
+ }
930
+ getOwner() {
931
+ const aid = String(this._aid ?? '').trim().toLowerCase();
932
+ let best = null;
933
+ let bestActive = false;
934
+ for (const record of this._ownershipRecords.values()) {
935
+ if (String(record.subject_aid ?? '').trim().toLowerCase() === aid) {
936
+ if (String(record.status ?? '').trim().toLowerCase() === 'released')
937
+ continue;
938
+ const owner = String(record.owner_aid ?? '').trim();
939
+ if (!owner)
940
+ continue;
941
+ const active = String(record.status ?? '').trim().toLowerCase() === 'active';
942
+ if (!best || active && !bestActive) {
943
+ best = owner;
944
+ bestActive = active;
945
+ }
946
+ }
947
+ }
948
+ return best;
949
+ }
950
+ get_owner() {
951
+ return this.getOwner();
952
+ }
953
+ getOwnershipStatus() {
954
+ const aid = String(this._aid ?? '').trim().toLowerCase();
955
+ let best = null;
956
+ let bestActive = false;
957
+ for (const record of this._ownershipRecords.values()) {
958
+ const subject = String(record.subject_aid ?? '').trim().toLowerCase();
959
+ const owner = String(record.owner_aid ?? '').trim().toLowerCase();
960
+ if (aid !== subject && aid !== owner)
961
+ continue;
962
+ const active = String(record.status ?? '').trim().toLowerCase() === 'active';
963
+ if (!best || active && !bestActive) {
964
+ best = record;
965
+ bestActive = active;
966
+ }
967
+ }
968
+ if (best)
969
+ return Object.fromEntries(Object.entries(best).filter(([key]) => key !== 'subject_identity' && key !== 'escrow_password' && key !== 'escrow_download_url'));
970
+ return { subject_aid: this._aid ?? '', owner_aid: null, status: 'legacy_unbound', type: this.getType() };
971
+ }
972
+ get_ownership_status() {
973
+ return this.getOwnershipStatus();
974
+ }
975
+ async bindOwneraid(ownerAid) { return this._getOwnershipWorkflow().bindOwneraid(ownerAid); }
976
+ async bindSubaid(subAid) { return this._getOwnershipWorkflow().bindSubaid(subAid); }
977
+ async releaseSubaid(subAid) { return this._getOwnershipWorkflow().releaseSubaid(subAid); }
978
+ async transferSubaid(subAid, newOwnerAid) { return this._getOwnershipWorkflow().transferSubaid(subAid, newOwnerAid); }
979
+ async checkOwnershipConsensus(subAid) { return this._getOwnershipWorkflow().checkOwnershipConsensus(subAid); }
980
+ async bindAid(subjectAid, options = {}) {
981
+ throw ownershipFailure('unsupported_protocol', 'legacy bindAid is unsupported; use bindSubaid');
982
+ }
983
+ async bind_aid(subjectAid, options = {}) { return this.bindAid(subjectAid, options); }
984
+ async bindOwner(ownerAid, options = {}) {
985
+ throw ownershipFailure('unsupported_protocol', 'legacy bindOwner is unsupported; use bindOwneraid');
986
+ }
987
+ async bind_owner(ownerAid, options = {}) { return this.bindOwner(ownerAid, options); }
988
+ async releaseOwnership(subjectAid) { throw ownershipFailure('unsupported_protocol', 'legacy releaseOwnership is unsupported; use releaseSubaid'); }
989
+ async release_ownership(subjectAid) { return this.releaseOwnership(subjectAid); }
990
+ async acceptOwnershipRequest(operationId) {
991
+ return this._getOwnershipWorkflow().acceptOwnershipRequest(operationId);
992
+ }
993
+ async rejectOwnershipRequest(operationId, reason) {
994
+ return this._getOwnershipWorkflow().rejectOwnershipRequest(operationId, reason);
995
+ }
996
+ async _handleOwnershipMessage(message) {
997
+ if (!message || typeof message !== 'object' || Array.isArray(message))
998
+ return false;
999
+ const envelope = message;
1000
+ const content = 'payload' in envelope ? envelope.payload : message;
1001
+ return this._getOwnershipWorkflow().receive(String(envelope.from_aid ?? ''), decryptedOwnershipJsonText(content) ?? content);
1002
+ }
760
1003
  get hasIdentity() {
761
1004
  return this._currentAid !== null && this.state !== ConnectionState.CLOSED;
762
1005
  }
@@ -886,6 +1129,9 @@ export class AUNClient {
886
1129
  logger: this._logger.for('aun_core.keystore'),
887
1130
  });
888
1131
  this._tokenStore = tokenStore;
1132
+ this._ownershipStorage = null;
1133
+ this._ownershipFiles = null;
1134
+ this._ownershipWorkflow = null;
889
1135
  this._auth = new AuthFlow({
890
1136
  tokenStore,
891
1137
  crypto: new CryptoProvider(),
@@ -914,7 +1160,10 @@ export class AUNClient {
914
1160
  },
915
1161
  auth: () => this._auth,
916
1162
  tokenStore: () => this._tokenStore,
917
- fetchPeerCert: (target, certFingerprint) => this._fetchPeerCert(target, certFingerprint || undefined),
1163
+ fetchPeerCert: (target, certFingerprint, signedAt) => this._fetchPeerCert(target, certFingerprint || undefined, undefined, signedAt, true),
1164
+ resolveEntityOwner: (target, type) => this._resolveEntityAgentMdOwner(target, type),
1165
+ fetchEntityIdentity: (target, type) => this._freshEntityCertificate(target, type),
1166
+ fetchEntitySigner: (target, fingerprint) => this._freshEntitySigner(target, fingerprint),
918
1167
  });
919
1168
  this._transport = new RPCTransport({
920
1169
  eventDispatcher: this._dispatcher,
@@ -924,10 +1173,21 @@ export class AUNClient {
924
1173
  dnsNet,
925
1174
  });
926
1175
  this._transport.setProtocolMetaObserver((meta) => this._observeRpcMeta(meta));
1176
+ this._transport.setServiceEventVerifier((event, payload) => verifyServiceEvent(event, payload, { targetAid: this._identity?.aid ?? '', resolveEventCertificate: (aid, fingerprint) => this._fetchServiceEventCertificate(aid, fingerprint), resolveCredentialService: this.resolveCredentialService }), () => this._identity?.aid ?? '', () => this._serviceEventIdentityVersion);
927
1177
  this._transport.setProtocolDisconnectCallback((err, closeCode) => this._handleTransportDisconnect(err, closeCode));
1178
+ this._transport._onForegroundDrained = () => this._delivery.onForegroundPullsDrained();
1179
+ this._transport._onRpcResponse = () => this._delivery.requestBackgroundPullRecovery();
928
1180
  }
929
1181
  loadIdentity(aid) {
1182
+ const previousAid = this._aid;
930
1183
  this._identityRuntime.loadIdentity(aid);
1184
+ this._serviceEventIdentityVersion++;
1185
+ this._ensureReadStateIdentity();
1186
+ if (previousAid !== this._aid) {
1187
+ this._ownershipRecords.clear();
1188
+ this._ownershipType = 'agent';
1189
+ }
1190
+ this._restoreOwnershipRecords();
931
1191
  }
932
1192
  setProtectedHeaders(headers) {
933
1193
  if (!headers) {
@@ -1006,7 +1266,7 @@ export class AUNClient {
1006
1266
  }
1007
1267
  /** 连接到 Gateway;身份来自构造函数或 loadIdentity(aid),认证由 SDK 内部自动完成。 */
1008
1268
  async connect(opts) {
1009
- return this._lifecycle.connect(opts);
1269
+ await this._lifecycle.connect(opts);
1010
1270
  }
1011
1271
  /** 关闭连接 */
1012
1272
  async close() {
@@ -1025,8 +1285,13 @@ export class AUNClient {
1025
1285
  * 自动处理内部方法限制、E2EE 加解密、客户端签名等。
1026
1286
  */
1027
1287
  async call(method, params) {
1288
+ const readIdentity = ['message.read', 'message.read_state', 'group.read', 'group.read_state'].includes(method)
1289
+ ? this._ensureReadStateIdentity() : null;
1028
1290
  try {
1029
- return await this._rpcPipeline.call(method, params);
1291
+ const result = await this._rpcPipeline.call(method, params);
1292
+ if (readIdentity !== null && readIdentity === this._ensureReadStateIdentity())
1293
+ this._observeReadStateResult(method, result, params ?? {});
1294
+ return result;
1030
1295
  }
1031
1296
  catch (err) {
1032
1297
  await this._maybeHandleHalfOpenRpcFailure(err);
@@ -1070,6 +1335,9 @@ export class AUNClient {
1070
1335
  const loaded = store.load(groupAid);
1071
1336
  if (!loaded.ok || !loaded.data?.aid?.isPrivateKeyValid())
1072
1337
  return false;
1338
+ const groupManager = store._agentMdManager ?? this._agentMdManager;
1339
+ if (String((groupManager._loadRecord?.(groupAid, true) ?? groupManager.loadRecord(groupAid))?.local_draft_content ?? ''))
1340
+ return false;
1073
1341
  await this._runGroupIdentityOperation(groupAid, () => this._uploadGroupAgentMd(store, groupAid, {}, {}));
1074
1342
  return true;
1075
1343
  }
@@ -1079,12 +1347,23 @@ export class AUNClient {
1079
1347
  const target = message.slice(agentPrefix.length).trim();
1080
1348
  if (!target || target.toLowerCase() !== String(this._aid ?? '').trim().toLowerCase())
1081
1349
  return false;
1082
- let agentMd = buildDefaultAgentMd(target);
1350
+ return this._ensureInitialAgentMd();
1351
+ }
1352
+ async _ensureInitialAgentMd() {
1353
+ const aid = this._aid;
1354
+ if (!aid || !this._currentAid)
1355
+ return false;
1356
+ const observed = await this._getOwnershipFiles().readAgent(aid);
1357
+ if (observed.exists || String(this._agentMdManager._loadRecord?.(aid, true)?.local_draft_content ?? this._agentMdManager.loadRecord(aid)?.local_draft_content ?? ''))
1358
+ return false;
1359
+ let agentMd = buildDefaultAgentMd(aid, { type: this.getType() });
1083
1360
  try {
1084
- agentMd = this._agentMdManager.readContent(target);
1361
+ agentMd = this._agentMdManager.readContent(aid) || agentMd;
1085
1362
  }
1086
1363
  catch { }
1087
- await this._agentMdManager.upload(agentMd);
1364
+ const payload = updateOwnershipAgentMd(agentMd, {});
1365
+ const raw = signOwnershipFile(this._currentAid, payload.endsWith('\n') || payload.endsWith('\r') ? payload : `${payload}\n`, 'agent');
1366
+ await this._agentMdManager.submitOwnershipRaw(aid, raw, { if_none_match: '*' });
1088
1367
  return true;
1089
1368
  }
1090
1369
  async _checkIdentityAdmissionAfterConnect() {
@@ -1092,10 +1371,12 @@ export class AUNClient {
1092
1371
  if (!aid)
1093
1372
  return;
1094
1373
  try {
1095
- const checked = await this._agentMdManager.check(aid);
1374
+ const checked = await this._agentMdManager.check(aid, 0);
1096
1375
  if (!checked.remote_found) {
1097
- const content = this._agentMdManager.readContent(aid) || buildDefaultAgentMd(aid);
1098
- await this._agentMdManager.upload(content);
1376
+ await this._ensureInitialAgentMd();
1377
+ }
1378
+ else if (checked.in_sync === false) {
1379
+ await this._agentMdManager.download(aid);
1099
1380
  }
1100
1381
  }
1101
1382
  catch (exc) {
@@ -1104,6 +1385,7 @@ export class AUNClient {
1104
1385
  const store = this._aidStore;
1105
1386
  if (!store)
1106
1387
  return;
1388
+ await this._resumeOrgIdentityAfterConnect(store);
1107
1389
  let listed;
1108
1390
  try {
1109
1391
  listed = await this.call('group.list_my', {});
@@ -1135,11 +1417,18 @@ export class AUNClient {
1135
1417
  await this.completeGroupTransfer({ group_id: groupId, group_aid: groupAid }, { aidStore: store });
1136
1418
  return;
1137
1419
  }
1420
+ const groupManager = store._agentMdManager ?? this._agentMdManager;
1421
+ if (String((groupManager._loadRecord?.(groupAid, true) ?? groupManager.loadRecord(groupAid))?.local_draft_content ?? '')) {
1422
+ await this._agentMdManager.check(groupAid);
1423
+ return;
1424
+ }
1138
1425
  const loaded = store.load(groupAid);
1139
1426
  if (!loaded.ok || !loaded.data?.aid?.isPrivateKeyValid()) {
1140
1427
  await this.bindGroupAid({ group_id: groupId, group_aid: groupAid }, { aidStore: store });
1141
1428
  return;
1142
1429
  }
1430
+ if (await this._resumeGroupAgentMdUpload(store, groupAid))
1431
+ return;
1143
1432
  const checked = await this._agentMdManager.check(groupAid);
1144
1433
  if (!checked.remote_found) {
1145
1434
  await this._uploadGroupAgentMd(store, groupAid, {}, {});
@@ -1170,6 +1459,204 @@ export class AUNClient {
1170
1459
  this._groupIdentityOperations.delete(key);
1171
1460
  }
1172
1461
  }
1462
+ async createOrg(params = {}, options = {}) {
1463
+ const store = options.aidStore;
1464
+ const owner = this._currentAid;
1465
+ const orgAid = String(params.org_id ?? '').trim();
1466
+ if (!store || !owner?.isPrivateKeyValid() || !orgAid)
1467
+ throw new ValidationError('createOrg requires aidStore, org_id and current owner identity');
1468
+ this._aidStore = store;
1469
+ let identity = store.load(orgAid);
1470
+ if (!identity.ok) {
1471
+ const registered = await store.register(orgAid, { type: 'org', owner: owner.aid });
1472
+ if (!registered.ok)
1473
+ throw ownershipFailure('unavailable', registered.error.message, true);
1474
+ identity = store.load(orgAid);
1475
+ }
1476
+ if (!identity.ok)
1477
+ throw ownershipFailure('unavailable', identity.error.message, true);
1478
+ const target = options.serviceAid ?? `org.${orgAid.slice(orgAid.indexOf('.') + 1)}`;
1479
+ const tokens = store._tokenStore;
1480
+ const inputHash = crypto.createHash('sha256').update(canonicalJson(params)).digest('hex');
1481
+ let checkpoint = tokens.loadMetadata(orgAid)?.org_identity_creation;
1482
+ if (checkpoint && (checkpoint.owner_aid !== owner.aid || checkpoint.input_hash !== inputHash))
1483
+ throw ownershipFailure('conflict', 'pending Org creation context changed');
1484
+ if (!checkpoint) {
1485
+ const prepared = this.org.prepareRequest('org.lifecycle.create', { ...params, owner_aid: owner.aid }, target, owner, { orgSigner: identity.data.aid });
1486
+ checkpoint = { owner_aid: owner.aid, input_hash: inputHash, source_params: params, prepared, phase: 'create' };
1487
+ tokens.saveMetadata(orgAid, { org_identity_creation: checkpoint });
1488
+ }
1489
+ if (checkpoint.phase === 'create') {
1490
+ const result = await this.call('org.lifecycle.create', checkpoint.prepared);
1491
+ checkpoint = { ...checkpoint, result, phase: 'upload' };
1492
+ tokens.saveMetadata(orgAid, { org_identity_creation: checkpoint });
1493
+ }
1494
+ await this._uploadOrgAgentMd(store, orgAid, params);
1495
+ tokens.saveMetadata(orgAid, { org_identity_creation: null });
1496
+ return checkpoint.result;
1497
+ }
1498
+ async _resumeOrgIdentityAfterConnect(store) {
1499
+ const keyStore = store._keystore;
1500
+ const tokens = store._tokenStore;
1501
+ if (typeof keyStore?.listIdentities !== 'function' || typeof tokens?.loadMetadata !== 'function')
1502
+ return;
1503
+ for (const aid of keyStore.listIdentities()) {
1504
+ const metadata = tokens.loadMetadata(aid);
1505
+ const creation = metadata?.org_identity_creation;
1506
+ const transfer = metadata?.org_identity_transfer;
1507
+ try {
1508
+ if (creation?.owner_aid === this._aid) {
1509
+ await this.createOrg(creation.source_params, { aidStore: store });
1510
+ continue;
1511
+ }
1512
+ if (transfer?.owner_aid === this._aid) {
1513
+ await this.completeOrgTransfer(transfer.source_params, { aidStore: store });
1514
+ continue;
1515
+ }
1516
+ const orgManager = store._agentMdManager;
1517
+ if (String((orgManager?._loadRecord?.(aid, true) ?? orgManager?.loadRecord?.(aid))?.local_draft_content ?? ''))
1518
+ continue;
1519
+ const pending = store._agentMdManager.pendingUpload(aid);
1520
+ if (!pending)
1521
+ continue;
1522
+ const fields = validateAgentMdDocument(pending.content, { expectedAid: aid }).frontmatter;
1523
+ if (fields.type === 'org' && fields.owner === this._aid)
1524
+ await this._resumeGroupAgentMdUpload(store, aid);
1525
+ }
1526
+ catch (error) {
1527
+ this._clientLog.warn(`post-connect Org identity recovery failed: aid=${aid} err=${formatCaughtError(error)}`);
1528
+ }
1529
+ }
1530
+ }
1531
+ async completeOrgTransfer(params = {}, options = {}) {
1532
+ const store = options.aidStore;
1533
+ const owner = this._currentAid;
1534
+ const orgAid = String(params.org_id ?? '').trim();
1535
+ const transferId = String(params.transfer_id ?? '').trim();
1536
+ if (!store || !owner?.isPrivateKeyValid() || !orgAid || !transferId)
1537
+ throw new ValidationError('completeOrgTransfer requires aidStore, org_id, transfer_id and current accepting owner');
1538
+ for (const field of ['previous_owner', 'new_owner']) {
1539
+ const material = params[field];
1540
+ if (!isJsonObject(material) || !isJsonObject(material.membership_binding) || !isJsonObject(material.terms) || field === 'previous_owner' && !isJsonObject(material.credential_template))
1541
+ throw ownershipFailure('invalid', 'Org transfer requires both replacement membership bindings, terms and previous-owner credential');
1542
+ }
1543
+ this._aidStore = store;
1544
+ const localStore = store._keystore;
1545
+ const tokenStore = store._tokenStore;
1546
+ const pendingKey = `org-transfer:${orgAid}:${transferId}`;
1547
+ const inputHash = crypto.createHash('sha256').update(canonicalJson(params)).digest('hex');
1548
+ let pending = tokenStore.loadMetadata(orgAid)?.org_identity_transfer;
1549
+ if (pending && (pending.transfer_id !== transferId || pending.owner_aid !== owner.aid || pending.input_hash !== inputHash))
1550
+ throw ownershipFailure('conflict', 'pending Org transfer context changed');
1551
+ let keyPair = localStore.loadPendingGroupBind(pendingKey);
1552
+ if (!keyPair) {
1553
+ if (pending)
1554
+ throw ownershipFailure('persistence_unavailable', 'Org transfer private key checkpoint is unavailable');
1555
+ keyPair = new CryptoProvider().generateIdentity();
1556
+ localStore.savePendingGroupBind(pendingKey, keyPair);
1557
+ }
1558
+ const target = options.serviceAid ?? `org.${orgAid.slice(orgAid.indexOf('.') + 1)}`;
1559
+ const publicKey = crypto.createPublicKey({ key: Buffer.from(keyPair.public_key_der_b64, 'base64'), format: 'der', type: 'spki' }).export({ format: 'pem', type: 'spki' }).toString();
1560
+ if (pending && pending.public_key !== publicKey)
1561
+ throw ownershipFailure('conflict', 'pending Org transfer context changed');
1562
+ if (!pending) {
1563
+ const rekey = { ...params, stage: 'rekey', public_key: publicKey, curve: 'P-256' };
1564
+ delete rekey.new_owner;
1565
+ delete rekey.request_binding;
1566
+ delete rekey.client_signature;
1567
+ pending = { transfer_id: transferId, owner_aid: owner.aid, public_key: publicKey, input_hash: inputHash, phase: 'rekey', source_params: params, rekey_params: this.org.prepareRequest('org.lifecycle.transferAccept', rekey, target, owner) };
1568
+ tokenStore.saveMetadata(orgAid, { org_identity_transfer: pending });
1569
+ }
1570
+ if (pending.phase === 'rekey') {
1571
+ const result = await this.call('org.lifecycle.transferAccept', pending.rekey_params);
1572
+ pending = { ...pending, phase: 'finalize', rekey_result: result };
1573
+ tokenStore.saveMetadata(orgAid, { org_identity_transfer: pending });
1574
+ }
1575
+ const rekeyResult = pending.rekey_result;
1576
+ const certPem = String(rekeyResult?.cert_pem ?? rekeyResult?.cert ?? '').trim();
1577
+ if (!certPem)
1578
+ throw ownershipFailure('unavailable', 'Org rekey response has no certificate', true);
1579
+ const imported = store.importGroupIdentity(orgAid, { ...keyPair, cert_pem: certPem });
1580
+ if (!imported.ok)
1581
+ throw ownershipFailure('invalid', imported.error.message);
1582
+ const orgIdentity = this._localEntityCertificate(orgAid);
1583
+ if (!pending.finalize_params) {
1584
+ const final = { ...pending.source_params, stage: 'finalize', public_key: publicKey, curve: 'P-256', request_id: crypto.randomUUID().replace(/-/g, ''), nonce: crypto.randomBytes(32).toString('base64url') };
1585
+ delete final.request_binding;
1586
+ delete final.client_signature;
1587
+ for (const field of ['previous_owner', 'new_owner']) {
1588
+ const material = isJsonObject(final[field]) ? final[field] : {};
1589
+ let binding = isJsonObject(material.membership_binding) ? material.membership_binding : undefined;
1590
+ if (!binding || !isJsonObject(material.terms))
1591
+ throw ownershipFailure('invalid', 'Org transfer requires both replacement membership bindings and terms');
1592
+ if (!binding.member_signature && binding.member_aid === owner.aid)
1593
+ binding = signMembershipBinding(binding, owner, 'member');
1594
+ binding = signMembershipBinding(binding, orgIdentity, 'org');
1595
+ const oldCredential = isJsonObject(material.credential_template) ? { ...material.credential_template } : undefined;
1596
+ if (oldCredential) {
1597
+ delete oldCredential.signature;
1598
+ oldCredential.key_version = orgIdentity.certFingerprint;
1599
+ }
1600
+ const credential = oldCredential ? createCredential(oldCredential, orgIdentity) : createMemberCredential(binding, orgIdentity, { key_version: orgIdentity.certFingerprint });
1601
+ final[field] = { ...material, membership_binding: binding, credential_template: credential };
1602
+ delete final[field].registration_authorization;
1603
+ }
1604
+ pending = { ...pending, finalize_params: this.org.prepareRequest('org.lifecycle.transferAccept', final, target, owner, { orgSigner: orgIdentity }) };
1605
+ tokenStore.saveMetadata(orgAid, { org_identity_transfer: pending });
1606
+ }
1607
+ const result = await this.call('org.lifecycle.transferAccept', pending.finalize_params);
1608
+ await this._uploadOrgAgentMd(store, orgAid, pending.source_params);
1609
+ tokenStore.saveMetadata(orgAid, { org_identity_transfer: null });
1610
+ localStore.clearPendingGroupBind(pendingKey);
1611
+ return result;
1612
+ }
1613
+ async dissolveOrg(params = {}, options = {}) {
1614
+ const store = options.aidStore ?? this._aidStore;
1615
+ if (store)
1616
+ this._aidStore = store;
1617
+ const orgAid = String(params.org_id ?? '').trim();
1618
+ const owner = this._currentAid;
1619
+ const target = options.serviceAid ?? `org.${orgAid.slice(orgAid.indexOf('.') + 1)}`;
1620
+ const payload = owner ? this.org.prepareRequest('org.lifecycle.dissolve', params, target, owner) : params;
1621
+ const result = await this.call('org.lifecycle.dissolve', payload);
1622
+ const organization = isJsonObject(result?.organization) ? result.organization : {};
1623
+ const status = String(organization.status ?? result?.status ?? '');
1624
+ if (store && orgAid && status === 'dissolved') {
1625
+ const tokenStore = store._tokenStore;
1626
+ const keyStore = store._keystore;
1627
+ const certPem = keyStore?.loadCert?.(orgAid);
1628
+ const metadata = tokenStore?.loadMetadata?.(orgAid) ?? {};
1629
+ const transfer = metadata.org_identity_transfer;
1630
+ if (transfer?.transfer_id && typeof keyStore?.clearPendingGroupBind === 'function') {
1631
+ keyStore.clearPendingGroupBind(`org-transfer:${orgAid}:${String(transfer.transfer_id)}`);
1632
+ }
1633
+ tokenStore?.saveMetadata?.(orgAid, {
1634
+ ...(certPem ? { invalidated_entity_certificate: certFingerprint(certPem) } : {}),
1635
+ org_identity_creation: undefined,
1636
+ org_identity_transfer: undefined,
1637
+ });
1638
+ tokenStore?.saveInstanceState?.(orgAid, store.deviceId, store.slotId, {});
1639
+ store._agentMdManager?.saveRecord?.(orgAid, {
1640
+ pending_operation: '', pending_content: '', pending_content_etag: '', pending_error: '',
1641
+ pending_condition: {}, pending_relation: {}, pending_authorization: '',
1642
+ });
1643
+ }
1644
+ return result;
1645
+ }
1646
+ async _uploadOrgAgentMd(store, orgAid, params) {
1647
+ const owner = this._currentAid;
1648
+ if (!owner)
1649
+ throw ownershipFailure('forbidden', 'Org agent.md requires current owner');
1650
+ if (await this._resumeGroupAgentMdUpload(store, orgAid))
1651
+ return;
1652
+ const identity = this._localEntityCertificate(orgAid);
1653
+ const content = String(params.org_agent_md ?? buildDefaultAgentMd(orgAid, { type: 'org', owner: owner.aid, entityCertFingerprint: identity.certFingerprint, name: String(params.name ?? orgAid) }));
1654
+ const uploaded = await store.uploadAgentMd(orgAid, content, { uploaderAid: owner.aid, entityOwnerResolver: (aid, type) => this._resolveEntityAgentMdOwner(aid, type), entityIdentityGetter: (aid) => this._freshEntityCertificate(aid, 'org') });
1655
+ if (!uploaded.ok) {
1656
+ const code = ['invalid', 'unavailable', 'forbidden', 'conflict', 'persistence_unavailable'].includes(uploaded.error.code) ? uploaded.error.code : uploaded.error.code === 'NETWORK_ERROR' ? 'unavailable' : 'invalid';
1657
+ throw ownershipFailure(code, uploaded.error.message, code === 'unavailable');
1658
+ }
1659
+ }
1173
1660
  async createGroup(params = {}, options = {}) {
1174
1661
  if (!isJsonObject(params)) {
1175
1662
  throw new ValidationError('createGroup params must be an object');
@@ -1338,6 +1825,8 @@ export class AUNClient {
1338
1825
  `name: ${JSON.stringify(name)}`,
1339
1826
  'type: "group"',
1340
1827
  'version: "1.0.0"',
1828
+ `owner: ${JSON.stringify(String(group.owner_aid ?? group.owner ?? this._aid ?? ''))}`,
1829
+ `entity_cert_fingerprint: ${JSON.stringify(this._localEntityCertificate(groupAid).certFingerprint)}`,
1341
1830
  `description: ${JSON.stringify(description)}`,
1342
1831
  ...(avatar ? [`avatar: ${JSON.stringify(avatar)}`] : []),
1343
1832
  '---',
@@ -1346,16 +1835,71 @@ export class AUNClient {
1346
1835
  '',
1347
1836
  ].join('\n');
1348
1837
  }
1838
+ async _resumeGroupAgentMdUpload(store, groupAid) {
1839
+ const identityStore = store;
1840
+ const pendingManager = identityStore._agentMdManager ?? this._agentMdManager;
1841
+ const pending = pendingManager.pendingUpload(groupAid);
1842
+ if (!pending)
1843
+ return false;
1844
+ const fields = validateAgentMdDocument(pending.content, { expectedAid: groupAid, validateOwnership: false }).frontmatter;
1845
+ if (['ownership', 'ownership_proof', 'ownership_release'].some((field) => Object.hasOwn(fields, field))) {
1846
+ throw new StateError('ownership pending agent.md requires its persisted 2.1 task', { stringCode: 'unsupported_protocol' });
1847
+ }
1848
+ const identity = this._currentAid;
1849
+ if (!identity || fields.owner !== identity.aid)
1850
+ throw ownershipFailure('forbidden', 'group agent.md recovery requires its original current owner');
1851
+ const manager = new AgentMdManager({
1852
+ aunPath: store.aunPath,
1853
+ verifySsl: identityStore._verifySsl,
1854
+ logger: identityStore._log.for('aun_core.agent_md'),
1855
+ uploadViaGatewayHost: true,
1856
+ ownerAidGetter: () => identity.aid,
1857
+ currentAidGetter: () => identity,
1858
+ entityOwnerResolver: (aid, type) => this._resolveEntityAgentMdOwner(aid, type),
1859
+ entityIdentityGetter: (aid, type) => this._freshEntityCertificate(aid, type),
1860
+ entitySignerResolver: (aid, fingerprint) => this._freshEntitySigner(aid, fingerprint),
1861
+ gatewayResolver: (aid) => identityStore._resolveGateway(aid),
1862
+ accessTokenResolver: (_aid, gatewayUrl) => identityStore._uploadAgentMdToken(identity, gatewayUrl),
1863
+ aidValidator: (aid) => identityStore._registerFlow.validateAidName(aid),
1864
+ });
1865
+ return await manager.resumePendingUpload(groupAid);
1866
+ }
1349
1867
  async _uploadGroupAgentMd(store, groupAid, params, group) {
1350
1868
  const uploaderAid = String(this._aid ?? '').trim();
1351
1869
  if (!uploaderAid) {
1352
1870
  throw new ValidationError('group agent.md upload requires current owner AID');
1353
1871
  }
1354
- const uploaded = await store.uploadAgentMd(groupAid, this._groupAgentMdContent(groupAid, params, group), { uploaderAid });
1872
+ this._aidStore = store;
1873
+ if (await this._resumeGroupAgentMdUpload(store, groupAid))
1874
+ return;
1875
+ const uploaded = await store.uploadAgentMd(groupAid, this._groupAgentMdContent(groupAid, params, group), { uploaderAid, entityOwnerResolver: (aid, type) => this._resolveEntityAgentMdOwner(aid, type), entityIdentityGetter: (aid) => this._freshEntityCertificate(aid) });
1355
1876
  if (!uploaded.ok) {
1356
- throw new ValidationError(`group agent.md upload failed: ${uploaded.error.message}`);
1877
+ const code = ['invalid', 'unavailable', 'forbidden', 'conflict', 'persistence_unavailable'].includes(uploaded.error.code) ? uploaded.error.code : uploaded.error.code === 'NETWORK_ERROR' ? 'unavailable' : 'invalid';
1878
+ throw ownershipFailure(code, `group agent.md upload failed: ${uploaded.error.message}`, code === 'unavailable');
1357
1879
  }
1358
1880
  }
1881
+ _localEntityCertificate(aid) {
1882
+ const loaded = this._aidStore?.load(aid);
1883
+ if (!loaded?.ok)
1884
+ throw ownershipFailure('unavailable', 'entity local certificate is unavailable', true);
1885
+ return loaded.data.aid;
1886
+ }
1887
+ async _freshEntityCertificate(aid, type = 'group') {
1888
+ const material = await this._auth.resolveCurrentEntityCertificate(await this._resolveOwnershipCertificateGateway(aid), aid, type);
1889
+ const certPem = material.cert_pem;
1890
+ return AID._create({ aid, aunPath: this._configModel.aunPath, certPem, certValid: true, privateKeyPem: null, privateKeyValid: false });
1891
+ }
1892
+ async _freshEntitySigner(aid, fingerprint) {
1893
+ const material = await this._auth.resolveOwnershipCertificate(await this._resolveOwnershipCertificateGateway(aid), aid, fingerprint, 'signing');
1894
+ return AID._create({ aid, aunPath: this._configModel.aunPath, certPem: material.cert_pem, certValid: true, privateKeyPem: null, privateKeyValid: false });
1895
+ }
1896
+ async _resolveEntityAgentMdOwner(aid, type) {
1897
+ const authority = await this._agentMdManager.readEntityAuthorityFresh(aid);
1898
+ const identity = await this._freshEntityCertificate(aid, type);
1899
+ if (authority.entity_cert_fingerprint !== identity.certFingerprint)
1900
+ throw ownershipFailure('conflict', 'entity authority changed during current-certificate verification');
1901
+ return authority.owner_aid;
1902
+ }
1359
1903
  async bindGroupAid(params = {}, options = {}) {
1360
1904
  if (!isJsonObject(params)) {
1361
1905
  throw new ValidationError('bindGroupAid params must be an object');
@@ -1793,6 +2337,169 @@ export class AUNClient {
1793
2337
  this._clientLog.debug(`on exit: elapsed=${Date.now() - tStart}ms event=${event}`);
1794
2338
  return result;
1795
2339
  }
2340
+ _readStateIsNewer(previous, current) {
2341
+ if (!previous)
2342
+ return true;
2343
+ const oldOrder = BigInt(String(previous.last_read_order ?? 0));
2344
+ const newOrder = BigInt(String(current.last_read_order ?? 0));
2345
+ if (oldOrder !== newOrder)
2346
+ return newOrder > oldOrder;
2347
+ const oldId = String(previous.last_read_message_id ?? '');
2348
+ const newId = String(current.last_read_message_id ?? '');
2349
+ if (oldOrder === 0n)
2350
+ return newId === oldId;
2351
+ const oldPoints = Array.from(oldId);
2352
+ const newPoints = Array.from(newId);
2353
+ for (let index = 0; index < Math.min(oldPoints.length, newPoints.length); index++) {
2354
+ const oldPoint = oldPoints[index].codePointAt(0);
2355
+ const newPoint = newPoints[index].codePointAt(0);
2356
+ if (oldPoint !== newPoint)
2357
+ return newPoint > oldPoint;
2358
+ }
2359
+ return newPoints.length >= oldPoints.length;
2360
+ }
2361
+ _ensureReadStateIdentity() {
2362
+ const identity = `${this._aid ?? ''}\u0000${this._deviceId ?? ''}`;
2363
+ if (identity !== this._readStateIdentity) {
2364
+ this._readStateCache.clear();
2365
+ this._readStateQueries.clear();
2366
+ this._readStateIdentity = identity;
2367
+ this._readStateGeneration += 1;
2368
+ }
2369
+ return `${identity}\u0000${this._readStateGeneration}`;
2370
+ }
2371
+ _registerReadStateQuery(method, params) {
2372
+ const key = method + '\u0000' + JSON.stringify(Object.keys(params).sort().map((field) => [field, params[field]]));
2373
+ this._readStateQueries.set(key, { method, params: { ...params } });
2374
+ }
2375
+ _readStateItems(result) {
2376
+ const resultObject = isJsonObject(result) ? result : null;
2377
+ return Array.isArray(resultObject?.states)
2378
+ ? resultObject.states.filter((item) => isJsonObject(item))
2379
+ : [];
2380
+ }
2381
+ _observeReadStateResult(method, result, params) {
2382
+ if (!['message.read', 'message.read_state', 'group.read', 'group.read_state'].includes(method))
2383
+ return;
2384
+ this._ensureReadStateIdentity();
2385
+ const isGroup = method.startsWith('group.');
2386
+ if (method.endsWith('.read')) {
2387
+ for (const state of this._readStateItems(params)) {
2388
+ const query = { topic_key: state.topic_key ?? '' };
2389
+ if (isGroup)
2390
+ query.group_id = state.group_id ?? params.group_id ?? '';
2391
+ else
2392
+ query.scope = state.scope || params.scope || 'p2p';
2393
+ this._registerReadStateQuery(`${isGroup ? 'group' : 'message'}.read_state`, query);
2394
+ }
2395
+ if (!Array.isArray(params.states) && params.topic_key) {
2396
+ const query = {};
2397
+ for (const field of ['scope', 'group_id', 'topic_key']) {
2398
+ if (field in params)
2399
+ query[field] = params[field];
2400
+ }
2401
+ this._registerReadStateQuery(`${isGroup ? 'group' : 'message'}.read_state`, query);
2402
+ }
2403
+ return;
2404
+ }
2405
+ this._registerReadStateQuery(method, params);
2406
+ for (const state of this._readStateItems(result)) {
2407
+ const scope = String(state.scope ?? (isGroup ? 'group' : 'p2p')).trim();
2408
+ const topicKey = String(state.topic_key ?? params.topic_key ?? '');
2409
+ const groupId = isGroup ? String(state.group_id ?? params.group_id ?? '').trim() : '';
2410
+ const lastRead = String(state.last_read_message_id ?? '').trim();
2411
+ if (!scope || !topicKey || !lastRead || (isGroup && !groupId))
2412
+ continue;
2413
+ const key = `${scope}\u0000${groupId}\u0000${topicKey}`;
2414
+ const query = { topic_key: topicKey };
2415
+ if (isGroup)
2416
+ query.group_id = groupId;
2417
+ else
2418
+ query.scope = scope;
2419
+ const previous = this._readStateCache.get(key)?.state;
2420
+ if (previous && !this._readStateIsNewer(previous, state))
2421
+ continue;
2422
+ const samePosition = previous?.last_read_message_id === state.last_read_message_id
2423
+ && BigInt(String(previous?.last_read_order ?? 0)) === BigInt(String(state.last_read_order ?? 0));
2424
+ this._readStateCache.set(key, {
2425
+ method: `${isGroup ? 'group' : 'message'}.read_state`,
2426
+ params: query,
2427
+ state: samePosition ? { ...previous, ...state } : { ...state },
2428
+ });
2429
+ }
2430
+ }
2431
+ _onRawReadChanged(data, expectedScope) {
2432
+ if (!isJsonObject(data))
2433
+ return;
2434
+ const eventData = data;
2435
+ if (eventData.scope !== expectedScope || eventData.owner_aid !== this._aid)
2436
+ return;
2437
+ const fields = ['owner_aid', 'topic_key', 'last_read_message_id', ...(expectedScope === 'group' ? ['group_id'] : [])];
2438
+ if (fields.some((field) => {
2439
+ const value = eventData[field];
2440
+ return typeof value !== 'string' || !value || Array.from(value).length > 128;
2441
+ }))
2442
+ return;
2443
+ const order = eventData.last_read_order;
2444
+ if (!(typeof order === 'number' && Number.isSafeInteger(order) && order > 0)
2445
+ && !(typeof order === 'string' && /^[1-9]\d*$/.test(order)))
2446
+ return;
2447
+ this._ensureReadStateIdentity();
2448
+ const scope = expectedScope;
2449
+ const topicKey = String(eventData.topic_key ?? '');
2450
+ const groupId = scope === 'group' ? String(eventData.group_id) : '';
2451
+ const lastRead = String(eventData.last_read_message_id);
2452
+ if (scope && topicKey && lastRead && (scope !== 'group' || groupId)) {
2453
+ const key = `${scope}\u0000${groupId}\u0000${topicKey}`;
2454
+ const query = { topic_key: topicKey };
2455
+ if (scope === 'group')
2456
+ query.group_id = groupId;
2457
+ else
2458
+ query.scope = scope;
2459
+ const method = `${scope === 'group' ? 'group' : 'message'}.read_state`;
2460
+ this._registerReadStateQuery(method, query);
2461
+ const previous = this._readStateCache.get(key)?.state;
2462
+ if (previous && (!this._readStateIsNewer(previous, eventData)
2463
+ || (previous.last_read_message_id === eventData.last_read_message_id
2464
+ && BigInt(String(previous.last_read_order ?? 0)) === BigInt(String(eventData.last_read_order ?? 0)))))
2465
+ return;
2466
+ this._readStateCache.set(key, { method, params: query, state: { ...eventData } });
2467
+ }
2468
+ const identity = this._ensureReadStateIdentity();
2469
+ this._dispatcher.enqueue(scope === 'group' ? 'group.read_changed' : 'message.read_changed', eventData, () => identity === this._ensureReadStateIdentity());
2470
+ }
2471
+ async _reconcileReadStates() {
2472
+ const identity = this._ensureReadStateIdentity();
2473
+ const keys = new Set();
2474
+ const errors = [];
2475
+ for (const entry of [...this._readStateQueries.values()]) {
2476
+ try {
2477
+ const result = await this.call(entry.method, { ...entry.params });
2478
+ if (identity !== this._ensureReadStateIdentity())
2479
+ return;
2480
+ this._observeReadStateResult(entry.method, result, entry.params);
2481
+ for (const state of this._readStateItems(result)) {
2482
+ const scope = String(state.scope ?? (entry.method.startsWith('group.') ? 'group' : 'p2p'));
2483
+ const groupId = scope === 'group' ? String(state.group_id ?? entry.params.group_id ?? '') : '';
2484
+ const topicKey = String(state.topic_key ?? entry.params.topic_key ?? '');
2485
+ const key = `${scope}\u0000${groupId}\u0000${topicKey}`;
2486
+ if (this._readStateCache.has(key))
2487
+ keys.add(key);
2488
+ }
2489
+ }
2490
+ catch (error) {
2491
+ errors.push({ method: entry.method, params: { ...entry.params }, error: error instanceof Error ? error.message : String(error) });
2492
+ this._clientLog.debug(`read state reconnect reconciliation failed: ${error instanceof Error ? error.message : String(error)}`);
2493
+ }
2494
+ if (identity !== this._ensureReadStateIdentity())
2495
+ return;
2496
+ }
2497
+ this._dispatcher.enqueue('read_state_sync', {
2498
+ owner_aid: this._aid ?? '',
2499
+ states: [...keys].map((key) => ({ ...this._readStateCache.get(key).state })),
2500
+ errors,
2501
+ }, () => identity === this._ensureReadStateIdentity());
2502
+ }
1796
2503
  async _callRawV2Rpc(method, params, pullGateKey = '') {
1797
2504
  const p = { ...(params ?? {}) };
1798
2505
  const forceForeground = Boolean(p._rpc_foreground);
@@ -1952,8 +2659,15 @@ export class AUNClient {
1952
2659
  _markPublishedSeq(ns, seq) {
1953
2660
  this._delivery.markPublishedSeq(ns, seq);
1954
2661
  }
1955
- _publishAppEvent(event, payload, source = 'direct') {
1956
- return this._delivery.publishAppEvent(event, payload, source);
2662
+ _publishAppEvent(event, payload, source = 'direct', deliveryContext) {
2663
+ if (event === 'message.received') {
2664
+ return this._handleOwnershipMessage(payload).then((handled) => {
2665
+ if (!handled)
2666
+ return this._delivery.publishAppEvent(event, payload, source, deliveryContext);
2667
+ return undefined;
2668
+ });
2669
+ }
2670
+ return this._delivery.publishAppEvent(event, payload, source, deliveryContext);
1957
2671
  }
1958
2672
  _echoTimestamp() {
1959
2673
  const now = new Date();
@@ -2132,8 +2846,8 @@ export class AUNClient {
2132
2846
  }
2133
2847
  return true;
2134
2848
  }
2135
- async _drainOrderedMessages(ns, beforeSeq, pullResponse = false, persist = true, source = 'push') {
2136
- return this._delivery.drainOrderedMessages(ns, beforeSeq, pullResponse, persist, source);
2849
+ async _drainOrderedMessages(ns, beforeSeq, pullResponse = false, persist = true, source = 'push', throughSeq) {
2850
+ return this._delivery.drainOrderedMessages(ns, beforeSeq, pullResponse, persist, source, undefined, throughSeq);
2137
2851
  }
2138
2852
  async _publishOrderedMessage(event, ns, seq, payload, source = 'push') {
2139
2853
  return this._delivery.publishOrderedMessage(event, ns, seq, payload, source);
@@ -2322,7 +3036,15 @@ export class AUNClient {
2322
3036
  }
2323
3037
  }
2324
3038
  try {
2325
- await this._auth.verifyPeerCertificate(peerGatewayUrl, certPem, aid);
3039
+ if (opts.signedAt === undefined)
3040
+ await this._auth.verifyPeerCertificate(peerGatewayUrl, certPem, aid);
3041
+ else if (opts.agentMdRead) {
3042
+ validateAgentMdCertificate(certPem, aid, opts.signedAt, { requireCurrentValidity: false, readCompatibility: true });
3043
+ await this._auth._verifyAuthCertChain(peerGatewayUrl, new crypto.X509Certificate(certPem), aid, opts.signedAt);
3044
+ }
3045
+ else {
3046
+ await this._auth.verifyPeerCertificate(peerGatewayUrl, certPem, aid, opts.signedAt);
3047
+ }
2326
3048
  }
2327
3049
  catch (exc) {
2328
3050
  if (cachedBootstrapChain) {
@@ -2336,9 +3058,9 @@ export class AUNClient {
2336
3058
  validatedAt: nowSec,
2337
3059
  refreshAfter: nowSec + PEER_CERT_CACHE_TTL,
2338
3060
  };
2339
- const cacheKey = AUNClient._certCacheKey(aid, certFingerprint);
3061
+ const cacheKey = AUNClient._certCacheKey(aid, certFingerprint) + (opts.signedAt === undefined ? '' : `:signed:${opts.signedAt}${opts.agentMdRead ? ':agentmd' : ''}`);
2340
3062
  this._certCache.set(cacheKey, entry);
2341
- if (!certFingerprint) {
3063
+ if (!certFingerprint && opts.signedAt === undefined) {
2342
3064
  const bareKey = AUNClient._certCacheKey(aid);
2343
3065
  this._certCache.set(bareKey, entry);
2344
3066
  const actualFp = `sha256:${x509Cert.fingerprint256.replace(/:/g, '').toLowerCase()}`;
@@ -2346,19 +3068,26 @@ export class AUNClient {
2346
3068
  }
2347
3069
  try {
2348
3070
  // peer 证书只存版本目录,不覆盖 cert.pem
2349
- this._tokenStore.saveCert(aid, certPem, certFingerprint, { makeActive: false });
3071
+ this._tokenStore.saveCert(aid, certPem, opts.agentMdRead && opts.signedAt !== undefined ? `sha256:${x509Cert.fingerprint256.replace(/:/g, '').toLowerCase()}` : certFingerprint, { makeActive: false });
2350
3072
  }
2351
3073
  catch (exc) {
2352
3074
  this._clientLog.error(`failed to write cert to keystore (aid=${aid}, fp=${certFingerprint ?? ''}): ${formatCaughtError(exc)}`, exc instanceof Error ? exc : undefined);
2353
3075
  }
2354
3076
  return certPem;
2355
3077
  }
3078
+ async _fetchServiceEventCertificate(aid, fingerprint) {
3079
+ const certificate = await this._fetchPeerCert(aid, fingerprint);
3080
+ if (!this._gatewayUrl)
3081
+ throw new ValidationError('gateway url unavailable for service event certificate validation');
3082
+ await this._auth.verifyPeerCertificate(AUNClient._resolvePeerGatewayUrl(this._gatewayUrl, aid), certificate, aid, undefined, true);
3083
+ return certificate;
3084
+ }
2356
3085
  /** 获取对方证书(带缓存 + 完整 PKI 验证),跨域时自动路由到 peer 所在域。 */
2357
- async _fetchPeerCert(aid, certFingerprint, timeoutMs = 30_000) {
3086
+ async _fetchPeerCert(aid, certFingerprint, timeoutMs = 30_000, signedAt, agentMdRead = false) {
2358
3087
  const tStart = Date.now();
2359
3088
  this._clientLog.debug(`_fetchPeerCert enter: aid=${aid}, fp=${certFingerprint ?? ''}`);
2360
3089
  try {
2361
- const cacheKey = AUNClient._certCacheKey(aid, certFingerprint);
3090
+ const cacheKey = AUNClient._certCacheKey(aid, certFingerprint) + (signedAt === undefined ? '' : `:signed:${signedAt}${agentMdRead ? ':agentmd' : ''}`);
2362
3091
  const cached = this._certCache.get(cacheKey);
2363
3092
  const now = Date.now() / 1000;
2364
3093
  if (cached && now < cached.refreshAfter) {
@@ -2376,6 +3105,8 @@ export class AUNClient {
2376
3105
  certPem,
2377
3106
  certFingerprint,
2378
3107
  source: 'fetch',
3108
+ signedAt,
3109
+ agentMdRead,
2379
3110
  });
2380
3111
  this._clientLog.debug(`_fetchPeerCert exit: elapsed=${Date.now() - tStart}ms aid=${aid} (fetched)`);
2381
3112
  return validated;
@@ -2908,6 +3639,7 @@ export class AUNClient {
2908
3639
  }
2909
3640
  this._lifecycle.assertConnectionAttemptOwner(connectionOwner);
2910
3641
  this._startBackgroundTasks();
3642
+ this._safeAsync(this._reconcileReadStates());
2911
3643
  const connectionKind = String(params.connection_kind ?? 'long');
2912
3644
  const isShortConnection = connectionKind === 'short';
2913
3645
  const hasExplicitBackgroundSync = Object.prototype.hasOwnProperty.call(params, 'background_sync');
@@ -2934,7 +3666,7 @@ export class AUNClient {
2934
3666
  this._clientLog.warn(`schedule post-connect P2P gap fill failed: ${formatCaughtError(exc)}`);
2935
3667
  });
2936
3668
  }
2937
- this._safeAsync(this._checkIdentityAdmissionAfterConnect());
3669
+ this._safeAsync(this._resumeOwnershipAfterConnect());
2938
3670
  this._clientLog.debug(`_connectOnce exit: elapsed=${Date.now() - tStart}ms gateway=${gatewayUrl}, aid=${this._aid ?? ''}`);
2939
3671
  }
2940
3672
  catch (err) {
@@ -3333,6 +4065,8 @@ export class AUNClient {
3333
4065
  encrypted: true,
3334
4066
  e2ee,
3335
4067
  };
4068
+ if (typeof plaintext.type === 'string' && plaintext.type.startsWith('aun.ownership.'))
4069
+ result.from_aid = fromAid;
3336
4070
  if (senderDeviceId)
3337
4071
  result.sender_device_id = senderDeviceId;
3338
4072
  const explicitDirection = String(msg.direction ?? '').trim();
@@ -3546,6 +4280,22 @@ export class AUNClient {
3546
4280
  const gateways = await this._resolveGatewayCandidatesForAid(aid);
3547
4281
  return gateways[0];
3548
4282
  }
4283
+ async _resolveOwnershipCertificateGateway(aid) {
4284
+ const issuer = aid.slice(aid.indexOf('.') + 1);
4285
+ if (issuer === this._aid?.slice(this._aid.indexOf('.') + 1))
4286
+ return this._resolveGatewayForAid(aid);
4287
+ const port = this._configModel.discoveryPort ? `:${this._configModel.discoveryPort}` : '';
4288
+ let lastError;
4289
+ for (const host of [aid, `gateway.${issuer}`]) {
4290
+ try {
4291
+ return await this._discovery.discover(`https://${host}${port}/.well-known/aun-gateway`);
4292
+ }
4293
+ catch (error) {
4294
+ lastError = error;
4295
+ }
4296
+ }
4297
+ throw ownershipFailure('unavailable', lastError instanceof Error ? lastError.message : 'signer Gateway discovery failed', true);
4298
+ }
3549
4299
  /** 发现并缓存全部 Gateway 候选,供首连和自动重连故障转移使用。 */
3550
4300
  async _resolveGatewayCandidatesForAid(aid) {
3551
4301
  const resolvedAid = String(aid ?? this._aid ?? '').trim();