@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.
- package/CHANGELOG.md +33 -0
- package/_packed_docs/CHANGELOG.md +33 -0
- package/_packed_docs/INDEX.md +32 -16
- package/_packed_docs/KITE_DOCS_GUIDE.md +22 -18
- package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2112.1.md +2 -2
- package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +13 -7
- package/_packed_docs/sdk/INDEX.md +21 -6
- package/_packed_docs/sdk/Ownership-SDK/347/274/226/346/216/222/346/214/207/345/215/2272.1.md +49 -2
- 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
- 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
- package/dist/agent-md.d.ts +16 -2
- package/dist/agent-md.js +60 -29
- package/dist/agent-md.js.map +1 -1
- package/dist/aid-store.d.ts +2 -0
- package/dist/aid-store.js +76 -19
- package/dist/aid-store.js.map +1 -1
- package/dist/auth.d.ts +6 -0
- package/dist/auth.js +205 -69
- package/dist/auth.js.map +1 -1
- package/dist/client.d.ts +12 -10
- package/dist/client.js +193 -112
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/keystore/aid-db.d.ts +1 -8
- package/dist/keystore/aid-db.js +46 -204
- package/dist/keystore/aid-db.js.map +1 -1
- package/dist/keystore/index.d.ts +0 -15
- package/dist/keystore/local-token-store.d.ts +1 -6
- package/dist/keystore/local-token-store.js +0 -15
- package/dist/keystore/local-token-store.js.map +1 -1
- package/dist/org-protocol.d.ts +10 -0
- package/dist/org-protocol.js +100 -12
- package/dist/org-protocol.js.map +1 -1
- package/dist/ownership-v21.d.ts +2 -2
- package/dist/ownership-v21.js +25 -12
- package/dist/ownership-v21.js.map +1 -1
- package/dist/ownership-workflow.d.ts +30 -59
- package/dist/ownership-workflow.js +260 -1667
- package/dist/ownership-workflow.js.map +1 -1
- package/dist/tools/cross-sdk-agent.d.ts +1 -2
- package/dist/tools/cross-sdk-agent.js +7 -14
- package/dist/tools/cross-sdk-agent.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- 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 ??=
|
|
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
|
|
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,
|
|
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
|
-
|
|
514
|
-
const
|
|
515
|
-
if (
|
|
516
|
-
|
|
517
|
-
const
|
|
518
|
-
|
|
519
|
-
const
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
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
|
-
|
|
545
|
+
return null;
|
|
527
546
|
}
|
|
528
|
-
|
|
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
|
|
534
|
-
if (
|
|
535
|
-
this._ownershipType = String(
|
|
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
|
-
|
|
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() >=
|
|
681
|
+
const syncRevocations = Date.now() >= this._nextCredentialRevocationSyncAt;
|
|
674
682
|
if (syncRevocations)
|
|
675
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
|
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) {
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
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 ? '' : `:
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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;
|