@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/aid-store.js CHANGED
@@ -4,12 +4,14 @@
4
4
  */
5
5
  import * as http from 'node:http';
6
6
  import * as https from 'node:https';
7
- import { X509Certificate, createPrivateKey, createPublicKey } from 'node:crypto';
7
+ import { X509Certificate, createPrivateKey, createPublicKey, randomUUID } from 'node:crypto';
8
8
  import { join } from 'node:path';
9
9
  import { homedir } from 'node:os';
10
+ import { readFileSync, writeFileSync } from 'node:fs';
10
11
  import { AID } from './aid.js';
11
12
  import * as codes from './error-codes.js';
12
- import { certCommonName, certMatchesFingerprint, certTimeError, signBytes, verifySignatureWithCert } from './cert-utils.js';
13
+ import { certCommonName, certMatchesFingerprint, certTimeError, parseAgentMdTailSignature, publicKeyMatchesFingerprint, signBytes, signDocument, verifyDocument, verifySignatureWithCert } from './cert-utils.js';
14
+ import { certFingerprint as _certFingerprint } from './cert-utils.js';
13
15
  import { resultErr, resultOk } from './result.js';
14
16
  import { LocalIdentityStore } from './keystore/local-identity-store.js';
15
17
  import { CryptoProvider } from './crypto.js';
@@ -18,17 +20,21 @@ import { GatewayDiscovery } from './discovery.js';
18
20
  import { DnsResilientNet } from './net.js';
19
21
  import { AUNLogger } from './logger.js';
20
22
  import { getDeviceId, normalizeSlotId } from './config.js';
21
- import { ClientSignatureError, IdentityConflictError, NotFoundError, ValidationError } from './errors.js';
23
+ import { AUNError, ClientSignatureError, IdentityConflictError, NotFoundError, ValidationError } from './errors.js';
22
24
  import { AgentMdManager, buildDefaultAgentMd } from './agent-md.js';
25
+ import { parseFileFrontmatter, validateAgentMdCertificate, validateAgentMdDocument } from './agent-md-schema.js';
23
26
  import { LocalTokenStore } from './keystore/local-token-store.js';
24
27
  import { AuthFlow } from './auth.js';
25
- import { OwnershipStatement, signOwnershipStatement } from './ownership-proof.js';
28
+ import { OWNERSHIP_UUID_V4_RE, ownershipFailure, signOwnershipFile } from './ownership-v21.js';
29
+ import { validateAIDFormat } from './validators.js';
26
30
  function resultError(r) {
27
31
  if (r.ok)
28
32
  return null;
29
33
  return r.error;
30
34
  }
31
35
  function agentMdFailure(exc) {
36
+ if (exc instanceof AUNError && exc.stringCode)
37
+ return resultErr(exc.stringCode, exc.message, exc);
32
38
  if (exc instanceof ClientSignatureError) {
33
39
  return resultErr(codes.SIGNATURE_INVALID, exc.message, exc);
34
40
  }
@@ -40,19 +46,6 @@ function agentMdFailure(exc) {
40
46
  }
41
47
  return resultErr(codes.NETWORK_ERROR, exc instanceof Error ? exc.message : String(exc), exc);
42
48
  }
43
- function ownerAgentMd(content, ownerAid, proof) {
44
- const marker = content.indexOf('<!-- AUN-SIGNATURE');
45
- const payload = marker >= 0 ? content.slice(0, marker) : content;
46
- const lines = payload.replace(/\r\n/g, '\n').replace(/\r/g, '\n').split('\n');
47
- const end = lines.findIndex((line, index) => index > 0 && line.trim() === '---');
48
- if (end < 0)
49
- throw new ValidationError('agent.md frontmatter is required for ownership operation');
50
- const filtered = lines.filter((line, index) => !(index > 0 && index < end && (/^\s*owner\s*:/.test(line) || /^\s*ownership_proof\s*:/.test(line))));
51
- const closed = filtered.findIndex((line, index) => index > 0 && line.trim() === '---');
52
- filtered.splice(closed, 0, `owner: ${JSON.stringify(ownerAid)}`, `ownership_proof: ${JSON.stringify(proof)}`);
53
- const result = filtered.join('\n');
54
- return result.endsWith('\n') ? result : `${result}\n`;
55
- }
56
49
  /** 发起 HTTP HEAD 请求,返回 [status, headers] */
57
50
  function httpHead(url, verifySsl, timeoutMs = 5000) {
58
51
  return new Promise((resolve, reject) => {
@@ -104,6 +97,114 @@ export class AIDStore {
104
97
  _gatewayCache = new Map();
105
98
  _agentMdManager;
106
99
  _peerAuth;
100
+ async sign(aid, input, options = {}) {
101
+ try {
102
+ if (!['text', 'file'].includes(options.inputType ?? 'text') || !['raw', 'document'].includes(options.format ?? 'raw'))
103
+ throw new Error('invalid signature inputType or format');
104
+ if (options.append && options.format !== 'document')
105
+ throw new Error('append requires document format');
106
+ if (options.inPlace && (options.inputType !== 'file' || options.format !== 'document'))
107
+ throw new Error('inPlace requires file input and document format');
108
+ const raw = options.inputType === 'file' ? readFileSync(input) : input;
109
+ if (typeof raw === 'string' && options.format !== 'document' && new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(new TextEncoder().encode(raw)) !== raw)
110
+ throw new Error('signature text must be valid UTF-8');
111
+ const loaded = options.identity ? resultOk({ aid: options.identity }) : this.load(aid);
112
+ if (!loaded.ok)
113
+ return loaded;
114
+ const identity = loaded.data.aid;
115
+ if (identity.aid !== aid || identity.certSubject.toLowerCase() !== aid.toLowerCase() || !identity.isPrivateKeyValid() || !identity.isCertValid())
116
+ return resultErr(codes.PRIVATE_KEY_NOT_VALID, 'sign requires a valid local signing identity');
117
+ if (options.format !== 'document')
118
+ return identity.sign(raw);
119
+ const content = typeof raw === 'string' ? raw : new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(raw);
120
+ const signed = signDocument(identity, content, { append: options.append, tag: options.tag, timestamp: options.timestamp, signerAid: options.signerAid });
121
+ if (options.inPlace)
122
+ writeFileSync(input, signed.signed, 'utf8');
123
+ return resultOk(signed);
124
+ }
125
+ catch (error) {
126
+ return resultErr(codes.SIGNATURE_OPERATION_ERROR, error instanceof Error ? error.message : String(error), error);
127
+ }
128
+ }
129
+ async verify(aid, input, signatureOrOptions, options = {}) {
130
+ const signature = typeof signatureOrOptions === 'string' ? signatureOrOptions : undefined;
131
+ if (signatureOrOptions && typeof signatureOrOptions === 'object')
132
+ options = signatureOrOptions;
133
+ try {
134
+ if (!['text', 'file'].includes(options.inputType ?? 'text') || !['raw', 'document'].includes(options.format ?? 'raw'))
135
+ throw new Error('invalid signature inputType or format');
136
+ if (options.inPlace)
137
+ throw new Error('verify does not modify files');
138
+ if (options.identity && (options.identity.aid !== aid || options.identity.certSubject.toLowerCase() !== aid.toLowerCase()))
139
+ throw new Error('verification identity does not match AID');
140
+ const raw = options.inputType === 'file' ? readFileSync(input) : input;
141
+ if (typeof raw === 'string' && options.format !== 'document' && new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(new TextEncoder().encode(raw)) !== raw)
142
+ throw new Error('signature text must be valid UTF-8');
143
+ if (options.format === 'document') {
144
+ const text = typeof raw === 'string' ? raw : new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(raw);
145
+ const parsed = parseAgentMdTailSignature(text, options.readCompatibility);
146
+ if (parsed.parseError || !parsed.fields)
147
+ return resultOk(verifyDocument(text, { expectedAid: aid, readCompatibility: options.readCompatibility }));
148
+ }
149
+ let loaded = options.identity ? resultOk({ aid: options.identity }) : this.load(aid);
150
+ if (!loaded.ok && options.format === 'document' && options.certificateResolver) {
151
+ const text = typeof raw === 'string' ? raw : new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(raw);
152
+ const primary = parseAgentMdTailSignature(text, options.readCompatibility).fields;
153
+ if (primary) {
154
+ const certificate = await options.certificateResolver(aid, primary.cert_fingerprint, Number(primary.timestamp));
155
+ loaded = resultOk({ aid: typeof certificate === 'string' ? AID._create({ aid, aunPath: this.aunPath, certPem: certificate, certValid: true, privateKeyPem: null, privateKeyValid: false }) : certificate });
156
+ }
157
+ }
158
+ if (!loaded.ok)
159
+ return loaded;
160
+ const identity = loaded.data.aid;
161
+ if (identity.aid !== aid || identity.certSubject.toLowerCase() !== aid.toLowerCase())
162
+ throw new Error('verification identity does not match AID');
163
+ if (options.format !== 'document')
164
+ return identity.verify(raw, signature ?? '');
165
+ const content = typeof raw === 'string' ? raw : new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(raw);
166
+ const certificates = await this._documentCertificates(content, options, identity);
167
+ const primary = parseAgentMdTailSignature(content, options.readCompatibility).fields;
168
+ const primaryCertPem = primary ? certificates.get(`${aid};${primary.cert_fingerprint}`) ?? identity.certPem : identity.certPem;
169
+ return resultOk(verifyDocument(content, { expectedAid: aid, primaryCertPem, readCompatibility: options.readCompatibility, requireCurrentValidity: options.requireCurrentValidity, certificateResolver: (signer, fingerprint) => certificates.get(`${signer};${fingerprint}`) }));
170
+ }
171
+ catch (error) {
172
+ return resultErr(codes.VERIFICATION_OPERATION_ERROR, error instanceof Error ? error.message : String(error), error);
173
+ }
174
+ }
175
+ async _documentCertificates(content, options, primary) {
176
+ const certificates = new Map();
177
+ let inferredAid = primary?.aid;
178
+ if (!inferredAid) {
179
+ try {
180
+ const meta = parseFileFrontmatter(parseAgentMdTailSignature(content).payload);
181
+ inferredAid = String(meta.aid ?? meta.owner_aid ?? '') || undefined;
182
+ }
183
+ catch { }
184
+ }
185
+ for (const item of parseAgentMdTailSignature(content, options.readCompatibility).signatures) {
186
+ const fields = item.fields, signer = fields.signer_aid ?? inferredAid;
187
+ if (!signer)
188
+ continue;
189
+ let cert;
190
+ if (options.certificateResolver)
191
+ cert = await options.certificateResolver(signer, fields.cert_fingerprint, Number(fields.timestamp));
192
+ else if (primary?.aid === signer && certMatchesFingerprint(primary.certPem, fields.cert_fingerprint))
193
+ cert = primary;
194
+ else {
195
+ const loaded = this.load(signer);
196
+ if (loaded.ok && certMatchesFingerprint(loaded.data.aid.certPem, fields.cert_fingerprint))
197
+ cert = loaded.data.aid;
198
+ else
199
+ throw new Error('signing certificate is unavailable locally; an explicit certificateResolver is required');
200
+ }
201
+ if (cert && typeof cert !== 'string' && cert.aid !== signer)
202
+ throw new Error('resolved certificate identity does not match signer AID');
203
+ if (cert)
204
+ certificates.set(`${signer};${fields.cert_fingerprint}`, typeof cert === 'string' ? cert : cert.certPem);
205
+ }
206
+ return certificates;
207
+ }
107
208
  constructor(opts) {
108
209
  this.aunPath = String(opts.aunPath ?? join(homedir(), '.aun'));
109
210
  this._encryptionSeed = String(opts.encryptionSeed ?? '');
@@ -148,28 +249,15 @@ export class AIDStore {
148
249
  logger: this._log.for('aun_core.agent_md'),
149
250
  aidValidator: (target) => this._registerFlow.validateAidName(target),
150
251
  gatewayResolver: (target) => this._resolveGateway(target),
151
- peerResolver: async (target, certFingerprint) => {
152
- const fp = String(certFingerprint ?? '').trim().toLowerCase();
153
- const cachedCert = this._keystore.loadCert(target, fp || undefined);
154
- if (cachedCert) {
155
- return AID._create({ aid: target, aunPath: this.aunPath, certPem: cachedCert, privateKeyPem: null, certValid: true, privateKeyValid: false, deviceId: this.deviceId, slotId: this.slotId, verifySsl: this._verifySsl, rootCaPath: this._rootCaPath, debug: this._debug });
156
- }
157
- if (fp) {
158
- const gatewayUrl = await this._resolveGateway(target);
159
- const certPem = await this._net.httpGetText(pkiCertUrl(gatewayUrl, target, fp), 10_000);
160
- await this._verifyDownloadedPeerCertificate(gatewayUrl, target, certPem, fp);
161
- this._keystore.saveCert(target, certPem, fp, { makeActive: false });
162
- return AID._create({ aid: target, aunPath: this.aunPath, certPem, privateKeyPem: null, certValid: true, privateKeyValid: false });
163
- }
164
- const gatewayUrl = await this._resolveGateway(target);
165
- const certPem = await this._registerFlow.fetchPeerCert(gatewayUrl, target);
166
- if (!certPem) {
167
- throw new Error(`certificate not found for aid: ${target}`);
168
- }
169
- await this._verifyDownloadedPeerCertificate(gatewayUrl, target, certPem);
170
- this._keystore.saveCert(target, certPem);
171
- return AID._create({ aid: target, aunPath: this.aunPath, certPem, privateKeyPem: null, certValid: true, privateKeyValid: false });
252
+ entitySignerResolver: async (target, fingerprint) => {
253
+ const material = await this._peerAuth.resolveOwnershipCertificate(await this._resolveGateway(target), target, fingerprint, 'signing');
254
+ return AID._create({ aid: target, aunPath: this.aunPath, certPem: material.cert_pem, privateKeyPem: null, certValid: true, privateKeyValid: false });
255
+ },
256
+ entityIdentityGetter: async (target, type) => {
257
+ const material = await this._peerAuth.resolveCurrentEntityCertificate(await this._resolveGateway(target), target, type ?? 'org');
258
+ return AID._create({ aid: target, aunPath: this.aunPath, certPem: material.cert_pem, privateKeyPem: null, certValid: true, privateKeyValid: false });
172
259
  },
260
+ peerResolver: (target, certFingerprint, signedAt) => this._resolveAgentMdPeerCertificate(target, certFingerprint, signedAt),
173
261
  });
174
262
  }
175
263
  close() {
@@ -179,6 +267,29 @@ export class AIDStore {
179
267
  if (typeof net.close === 'function')
180
268
  net.close();
181
269
  }
270
+ async _resolveAgentMdPeerCertificate(target, certFingerprint, signedAt) {
271
+ const fp = String(certFingerprint ?? '').trim().toLowerCase();
272
+ const cachedCert = this._keystore.loadCert(target, fp || undefined);
273
+ if (cachedCert && (!fp || certMatchesFingerprint(cachedCert, fp) || publicKeyMatchesFingerprint(cachedCert, fp))) {
274
+ if (signedAt !== undefined)
275
+ await this._verifyDownloadedPeerCertificate(await this._resolveGateway(target), target, cachedCert, fp, signedAt);
276
+ return AID._create({ aid: target, aunPath: this.aunPath, certPem: cachedCert, privateKeyPem: null, certValid: true, privateKeyValid: false, deviceId: this.deviceId, slotId: this.slotId, verifySsl: this._verifySsl, rootCaPath: this._rootCaPath, debug: this._debug });
277
+ }
278
+ if (fp) {
279
+ const gatewayUrl = await this._resolveGateway(target);
280
+ const certPem = await this._net.httpGetText(pkiCertUrl(gatewayUrl, target, fp), 10_000);
281
+ await this._verifyDownloadedPeerCertificate(gatewayUrl, target, certPem, fp, signedAt);
282
+ this._keystore.saveCert(target, certPem, _certFingerprint(certPem), { makeActive: false });
283
+ return AID._create({ aid: target, aunPath: this.aunPath, certPem, privateKeyPem: null, certValid: true, privateKeyValid: false });
284
+ }
285
+ const gatewayUrl = await this._resolveGateway(target);
286
+ const certPem = await this._registerFlow.fetchPeerCert(gatewayUrl, target);
287
+ if (!certPem)
288
+ throw new Error(`certificate not found for aid: ${target}`);
289
+ await this._verifyDownloadedPeerCertificate(gatewayUrl, target, certPem, undefined, signedAt);
290
+ this._keystore.saveCert(target, certPem);
291
+ return AID._create({ aid: target, aunPath: this.aunPath, certPem, privateKeyPem: null, certValid: true, privateKeyValid: false });
292
+ }
182
293
  // ── 离线方法 ──────────────────────────────────────────────────
183
294
  load(aid) {
184
295
  const target = String(aid ?? '').trim();
@@ -193,6 +304,9 @@ export class AIDStore {
193
304
  catch (exc) {
194
305
  return resultErr(codes.CERT_PARSE_ERROR, `certificate parse failed for aid: ${target}`, exc);
195
306
  }
307
+ if (this._tokenStore.loadMetadata?.(target)?.invalidated_entity_certificate === _certFingerprint(certPem)) {
308
+ return resultErr(codes.CERT_NOT_VALID, `group/org identity has been invalidated: ${target}`);
309
+ }
196
310
  const timeErr = certTimeError(certPem);
197
311
  if (timeErr === 'expired')
198
312
  return resultErr(codes.CERT_EXPIRED, `certificate expired for aid: ${target}`);
@@ -295,11 +409,28 @@ export class AIDStore {
295
409
  return Promise.resolve(this.changeSeed(oldSeed, newSeed));
296
410
  }
297
411
  // ── 联网方法 ──────────────────────────────────────────────────
298
- async registerIdentity(aid, uploadDefault = true) {
412
+ async register(aid, options) {
299
413
  const target = String(aid ?? '').trim();
300
414
  this._log.for('aun_core.aid_store').debug(`register enter: aid=${target || '-'}`);
301
415
  try {
302
416
  this._registerFlow.validateAidName(target);
417
+ const type = String(options?.type ?? '').trim(), owner = String(options?.owner ?? '').trim().toLowerCase();
418
+ const entity = ['group', 'org'].includes(type);
419
+ if (entity && !owner)
420
+ throw new ValidationError('group/org registration requires owner');
421
+ if (!type || type === 'human' && owner)
422
+ throw new ValidationError('register requires type; human forbids owner');
423
+ if (owner)
424
+ validateAIDFormat(owner, 'owner');
425
+ const previous = this._tokenStore.loadMetadata(target)?.registration_intent;
426
+ const exttype = String(options?.exttype ?? '').trim();
427
+ if (previous && (entity ? previous.protocol !== 'entity' || previous.pending !== false : previous.protocol !== '2.1' || !OWNERSHIP_UUID_V4_RE.test(String(previous.operation_id)) || typeof previous.pending !== 'boolean'))
428
+ throw ownershipFailure('persistence_unavailable', 'registration ownership intent is corrupt');
429
+ if (previous && (previous.type !== type || previous.owner !== owner || previous.exttype !== exttype))
430
+ throw new IdentityConflictError('registration ownership intent already exists with a different context');
431
+ const intent = previous ?? (entity ? { protocol: 'entity', type, owner, exttype, pending: false } : { protocol: '2.1', type, owner, exttype, operation_id: randomUUID(), pending: Boolean(owner) });
432
+ if (type !== 'agent' || owner)
433
+ this._tokenStore.saveMetadata(target, { registration_intent: intent });
303
434
  const gatewayUrl = await this._resolveGateway(target);
304
435
  const result = await this._registerFlow.registerAid(gatewayUrl, target);
305
436
  if (result.cert) {
@@ -310,8 +441,8 @@ export class AIDStore {
310
441
  public_key_der_b64: result.public_key_der_b64,
311
442
  curve: result.curve,
312
443
  });
313
- if (uploadDefault)
314
- await this.uploadAgentMd(target, buildDefaultAgentMd(target));
444
+ if (!entity)
445
+ await this._createInitialRegistrationAgentMd(target, type, exttype);
315
446
  return resultOk({ registered: true });
316
447
  }
317
448
  catch (exc) {
@@ -328,219 +459,32 @@ export class AIDStore {
328
459
  return resultErr(codes.SERVER_ERROR, msg, exc);
329
460
  }
330
461
  }
331
- async register(aidOrParams) {
332
- if (typeof aidOrParams === 'string')
333
- return await this.registerIdentity(aidOrParams);
334
- const params = aidOrParams;
335
- const target = String(params?.aid ?? '').trim();
336
- const aidType = String(params?.type ?? '').trim().toLowerCase();
337
- const owner = String(params?.owner ?? params?.owner_aid ?? '').trim();
338
- if (!target || !aidType)
339
- return resultErr(codes.INVALID_AID_FORMAT, 'register requires aid and type');
340
- try {
341
- const identity = await this.registerIdentity(target, false);
342
- if (!identity.ok)
343
- return identity;
344
- const gatewayUrl = await this._resolveGateway(target);
345
- const prepareParams = { aid: target, type: aidType };
346
- if (owner)
347
- prepareParams.owner = owner;
348
- const idempotencyKey = params.idempotencyKey ?? params.idempotency_key;
349
- if (idempotencyKey)
350
- prepareParams.idempotency_key = idempotencyKey;
351
- const prepared = await this._registerFlow.shortRpc(gatewayUrl, 'auth.aid_register_prepare', prepareParams);
352
- const operationId = String(prepared.operation_id ?? '').trim();
353
- if (!operationId)
354
- throw new ValidationError('aid_register_prepare response missing operation_id');
355
- let subjectSignature = params.subjectSignature ?? params.subject_signature;
356
- const ownerSignature = params.ownerSignature ?? params.owner_signature;
357
- if (!subjectSignature && owner) {
358
- const loaded = this.load(target);
359
- if (loaded.ok) {
360
- const statement = OwnershipStatement.create(String(prepared.proof_id ?? ''), target, owner);
361
- const signed = signOwnershipStatement({ certFingerprint: loaded.data.aid.certFingerprint, sign: (payload) => loaded.data.aid.sign(Buffer.from(payload)) }, statement);
362
- subjectSignature = { cert_fingerprint: signed.cert_fingerprint, signature: signed.signature };
363
- }
364
- }
365
- const signatures = {};
366
- if (subjectSignature)
367
- signatures.subject_signature = subjectSignature;
368
- if (ownerSignature)
369
- signatures.owner_signature = ownerSignature;
370
- let response = prepared;
371
- if (Object.keys(signatures).length > 0)
372
- response = await this._registerFlow.shortRpc(gatewayUrl, 'auth.aid_register_submit', { operation_id: operationId, ...signatures });
373
- const state = String(response.state ?? response.operation_state ?? '').toLowerCase();
374
- const complete = !owner || owner === target || (!!ownerSignature && !!subjectSignature);
375
- if (complete && state === 'agent_md_pending') {
376
- let content = params.agentMd ?? params.agent_md ?? buildDefaultAgentMd(target, { type: aidType });
377
- if (owner) {
378
- const proof = { proof_id: prepared.proof_id, subject_aid: target, owner_aid: owner, fact_hash: prepared.fact_hash, statement_hash: prepared.statement_hash, subject_signature: subjectSignature };
379
- if (owner !== target)
380
- proof.owner_signature = ownerSignature;
381
- content = ownerAgentMd(content, owner, proof);
382
- }
383
- const uploaded = await this.uploadAgentMd(target, content, { ownerOperationId: operationId });
384
- if (!uploaded.ok)
385
- return uploaded;
386
- response = await this._registerFlow.shortRpc(gatewayUrl, 'auth.aid_register_activate', { operation_id: operationId });
387
- }
388
- return resultOk(response);
389
- }
390
- catch (exc) {
391
- if (exc instanceof ValidationError)
392
- return resultErr(codes.INVALID_AID_FORMAT, exc.message, exc);
393
- return resultErr(codes.NETWORK_ERROR, exc instanceof Error ? exc.message : String(exc), exc);
394
- }
395
- }
396
- async create(params) { return await this.register(params); }
397
- async bindAid(params) {
398
- const target = String(params?.aid ?? '').trim();
399
- const aidType = String(params?.type ?? '').trim().toLowerCase();
400
- const owner = String(params?.owner ?? params?.owner_aid ?? '').trim();
401
- if (!target || !aidType)
402
- return resultErr(codes.INVALID_AID_FORMAT, 'bindAid requires aid and type');
462
+ async _createInitialRegistrationAgentMd(target, type, exttype) {
403
463
  try {
404
- this._registerFlow.validateAidName(target);
405
- const gatewayUrl = await this._resolveGateway(target);
406
- const prepareParams = { aid: target, type: aidType };
407
- if (owner)
408
- prepareParams.owner = owner;
409
- const idempotencyKey = params.idempotencyKey ?? params.idempotency_key;
410
- if (idempotencyKey)
411
- prepareParams.idempotency_key = idempotencyKey;
412
- const prepared = await this._registerFlow.shortRpc(gatewayUrl, 'auth.aid_bind_prepare', prepareParams);
413
- const operationId = String(prepared.operation_id ?? '').trim();
414
- if (!operationId)
415
- throw new ValidationError('aid_bind_prepare response missing operation_id');
416
- const signatures = {};
417
- let subjectSignature = params.subjectSignature ?? params.subject_signature;
418
- if (!subjectSignature && owner) {
419
- const loaded = this.load(target);
420
- if (loaded.ok) {
421
- const statement = OwnershipStatement.create(String(prepared.proof_id ?? ''), target, owner);
422
- const signed = signOwnershipStatement({
423
- certFingerprint: loaded.data.aid.certFingerprint,
424
- sign: (payload) => loaded.data.aid.sign(Buffer.from(payload)),
425
- }, statement);
426
- subjectSignature = { cert_fingerprint: signed.cert_fingerprint, signature: signed.signature };
427
- }
428
- }
429
- if (subjectSignature)
430
- signatures.subject_signature = subjectSignature;
431
- const ownerSignature = params.ownerSignature ?? params.owner_signature;
432
- if (ownerSignature)
433
- signatures.owner_signature = ownerSignature;
434
- let response = prepared;
435
- if (Object.keys(signatures).length > 0) {
436
- response = await this._registerFlow.shortRpc(gatewayUrl, 'auth.aid_bind_submit', { operation_id: operationId, ...signatures });
437
- }
438
- const agentMd = params.agentMd ?? params.agent_md;
439
- if (agentMd !== undefined) {
440
- const uploaded = await this.uploadAgentMd(target, agentMd, {
441
- ownerOperationId: operationId,
442
- });
443
- if (!uploaded.ok)
444
- return uploaded;
445
- }
446
- const state = String(response.state ?? response.operation_state ?? '').toLowerCase();
447
- if (state === 'agent_md_pending' || (!Object.keys(signatures).length && !owner && String(prepared.state ?? '') === 'agent_md_pending')) {
448
- response = await this._registerFlow.shortRpc(gatewayUrl, 'auth.aid_bind_activate', { operation_id: operationId });
449
- }
450
- return resultOk(response);
451
- }
452
- catch (exc) {
453
- if (exc instanceof ValidationError)
454
- return resultErr(codes.INVALID_AID_FORMAT, exc.message, exc);
455
- return resultErr(codes.NETWORK_ERROR, exc instanceof Error ? exc.message : String(exc), exc);
456
- }
457
- }
458
- async getAidBindingStatus(aid) {
459
- const target = String(aid ?? '').trim();
460
- if (!target)
461
- return resultErr(codes.INVALID_AID_FORMAT, 'getAidBindingStatus requires aid');
462
- try {
463
- this._registerFlow.validateAidName(target);
464
- const gatewayUrl = await this._resolveGateway(target);
465
- return resultOk(await this._registerFlow.shortRpc(gatewayUrl, 'auth.aid_bind_status', { aid: target }));
466
- }
467
- catch (exc) {
468
- return resultErr(codes.NETWORK_ERROR, exc instanceof Error ? exc.message : String(exc), exc);
469
- }
470
- }
471
- async resumeAidBinding(operationId) {
472
- const target = String(operationId ?? '').trim();
473
- if (!target)
474
- return resultErr(codes.INVALID_AID_FORMAT, 'resumeAidBinding requires operationId');
475
- try {
476
- const gatewayUrl = await this._resolveGateway(target);
477
- let response = await this._registerFlow.shortRpc(gatewayUrl, 'auth.aid_operation_get', { operation_id: target });
478
- if (String(response.state ?? response.operation_state ?? '').toLowerCase() === 'agent_md_pending') {
479
- const method = String(response.kind ?? '').toLowerCase() === 'owner_change' ? 'auth.owner_change_activate' : 'auth.aid_bind_activate';
480
- response = await this._registerFlow.shortRpc(gatewayUrl, method, { operation_id: target });
481
- }
482
- return resultOk(response);
483
- }
484
- catch (exc) {
485
- return resultErr(codes.NETWORK_ERROR, exc instanceof Error ? exc.message : String(exc), exc);
486
- }
487
- }
488
- async bind_aid(params) { return this.bindAid(params); }
489
- async get_aid_binding_status(aid) { return this.getAidBindingStatus(aid); }
490
- async resume_aid_binding(operationId) { return this.resumeAidBinding(operationId); }
491
- async changeOwner(params) {
492
- const subject = String(params?.subject_aid ?? params?.aid ?? '').trim();
493
- const newOwner = String(params?.new_owner_aid ?? params?.new_owner ?? '').trim();
494
- if (!subject || !newOwner)
495
- return resultErr(codes.INVALID_AID_FORMAT, 'changeOwner requires subject_aid and new_owner_aid');
496
- try {
497
- this._registerFlow.validateAidName(subject);
498
- const gatewayUrl = await this._resolveGateway(subject);
499
- const prepare = { subject_aid: subject, new_owner_aid: newOwner };
500
- if (params.current_owner_aid ?? params.owner_aid)
501
- prepare.current_owner_aid = params.current_owner_aid ?? params.owner_aid;
502
- if (params.expected_relation_version !== undefined)
503
- prepare.expected_relation_version = params.expected_relation_version;
504
- if (params.idempotencyKey ?? params.idempotency_key)
505
- prepare.idempotency_key = params.idempotencyKey ?? params.idempotency_key;
506
- const prepared = await this._registerFlow.shortRpc(gatewayUrl, 'auth.owner_change_prepare', prepare);
507
- const operationId = String(prepared.operation_id ?? '').trim();
508
- if (!operationId)
509
- throw new ValidationError('owner_change_prepare response missing operation_id');
510
- const signatures = {};
511
- for (const key of ['old_owner_signature', 'subject_signature', 'new_owner_signature'])
512
- if (params[key])
513
- signatures[key] = params[key];
514
- let response = prepared;
515
- if (Object.keys(signatures).length > 0)
516
- response = await this._registerFlow.shortRpc(gatewayUrl, 'auth.owner_change_submit', { operation_id: operationId, ...signatures });
517
- if (String(response.state ?? response.operation_state ?? '').toLowerCase() === 'agent_md_pending' && (params.agentMd ?? params.agent_md) !== undefined) {
518
- const proof = { proof_id: prepared.proof_id, subject_aid: subject, owner_aid: newOwner, fact_hash: prepared.fact_hash, statement_hash: prepared.statement_hash, subject_signature: params.subject_signature, owner_signature: params.new_owner_signature };
519
- const content = ownerAgentMd(String(params.agentMd ?? params.agent_md), newOwner, proof);
520
- const uploaded = await this.uploadAgentMd(subject, content, { ownerOperationId: operationId });
521
- if (!uploaded.ok)
522
- return uploaded;
523
- response = await this._registerFlow.shortRpc(gatewayUrl, 'auth.owner_change_activate', { operation_id: operationId });
524
- }
525
- return resultOk(response);
526
- }
527
- catch (exc) {
528
- return resultErr(codes.NETWORK_ERROR, exc instanceof Error ? exc.message : String(exc), exc);
529
- }
530
- }
531
- async resumeOwnerChange(operationId, aid) {
532
- const id = String(operationId ?? '').trim();
533
- if (!id)
534
- return resultErr(codes.INVALID_AID_FORMAT, 'resumeOwnerChange requires operationId');
535
- try {
536
- const gatewayUrl = await this._resolveGateway(String(aid ?? id));
537
- let response = await this._registerFlow.shortRpc(gatewayUrl, 'auth.aid_operation_get', { operation_id: id });
538
- if (String(response.state ?? response.operation_state ?? '').toLowerCase() === 'agent_md_pending')
539
- response = await this._registerFlow.shortRpc(gatewayUrl, 'auth.owner_change_activate', { operation_id: id });
540
- return resultOk(response);
464
+ const loaded = this.load(target);
465
+ if (!loaded.ok)
466
+ return loaded;
467
+ const current = loaded.data.aid;
468
+ const manager = new AgentMdManager({
469
+ aunPath: this.aunPath, verifySsl: this._verifySsl, logger: this._log.for('aun_core.agent_md'), uploadViaGatewayHost: true,
470
+ ownerAidGetter: () => target, currentAidGetter: () => current,
471
+ gatewayResolver: (value) => this._resolveGateway(value),
472
+ accessTokenResolver: async () => this._uploadAgentMdToken(current, await this._resolveGateway(target)),
473
+ aidValidator: (value) => this._registerFlow.validateAidName(value),
474
+ });
475
+ const fresh = await manager.readOwnershipFresh(target), now = Math.trunc(Date.now() / 1000);
476
+ if (fresh.protocol !== '2.1' || fresh.sub_aid !== target || !Number.isSafeInteger(fresh.relation_version) || fresh.relation_version < 0 || !['locked', 'unlocked'].includes(fresh.lock) || !Number.isSafeInteger(fresh.observed_at) || fresh.observed_at < now - 30 || fresh.observed_at > now + 5)
477
+ throw ownershipFailure('unavailable', 'initial NS authority is unavailable', true);
478
+ if (fresh.exists)
479
+ return resultOk({ initialized: false });
480
+ if (fresh.relation_version !== 0 || fresh.lock !== 'unlocked')
481
+ throw ownershipFailure('conflict', 'initial NS relation is not unbound');
482
+ const raw = signOwnershipFile(current, buildDefaultAgentMd(target, { type, exttype }), 'agent', { now });
483
+ await manager.submitOwnershipRaw(target, raw, { if_none_match: '*' });
484
+ return resultOk({ initialized: true });
541
485
  }
542
- catch (exc) {
543
- return resultErr(codes.NETWORK_ERROR, exc instanceof Error ? exc.message : String(exc), exc);
486
+ catch (error) {
487
+ return agentMdFailure(error);
544
488
  }
545
489
  }
546
490
  async exists(aid) {
@@ -569,6 +513,19 @@ export class AIDStore {
569
513
  let peer;
570
514
  let certFromCache = false;
571
515
  const loaded = this.load(target);
516
+ const local = !forceRefresh && !skipAgentMd && ((loaded.ok && loaded.data?.aid.isPrivateKeyValid()) || this._keystore.loadKeyPair(target)?.private_key_pem) ? (this._agentMdManager._loadRecord?.(target, true) ?? this._agentMdManager.loadRecord(target)) : null;
517
+ const draft = String(local?.local_draft_content ?? '');
518
+ const content = draft || String(local?.content ?? '');
519
+ if (content) {
520
+ peer = loaded.ok && loaded.data ? loaded.data.aid : AID._create({ aid: target, aunPath: this.aunPath, certPem: this._keystore.loadCert(target) ?? '', privateKeyPem: null, certValid: false, privateKeyValid: false, deviceId: this.deviceId, slotId: this.slotId, verifySsl: this._verifySsl, rootCaPath: this._rootCaPath, debug: this._debug, aidStore: this });
521
+ const localEtag = AgentMdManager.contentEtag(content), remoteEtag = String(local?.remote_etag ?? '');
522
+ return resultOk({ aid: peer, source: { cert_from_cache: true, agent_md_fetched: false }, agent_md: {
523
+ aid: target, content, verification: { status: 'unverified' }, signature: {}, cert_pem: '',
524
+ etag: localEtag, last_modified: String(local?.last_modified ?? ''), status: 200,
525
+ in_sync: !draft && !!remoteEtag && localEtag === remoteEtag,
526
+ saved_to: draft ? this._agentMdManager.metaPath(target) : this._agentMdManager.filePath(target), save_error: null,
527
+ } });
528
+ }
572
529
  if (loaded.ok && loaded.data && !forceRefresh) {
573
530
  peer = loaded.data.aid;
574
531
  certFromCache = true;
@@ -595,6 +552,8 @@ export class AIDStore {
595
552
  return resultOk({ aid: peer, agent_md: agentMd.data, source });
596
553
  }
597
554
  catch (exc) {
555
+ if (exc instanceof AUNError && exc.stringCode)
556
+ return resultErr(exc.stringCode, exc.message, exc);
598
557
  return resultErr(codes.NETWORK_ERROR, String(exc), exc);
599
558
  }
600
559
  }
@@ -614,12 +573,17 @@ export class AIDStore {
614
573
  return agentMdFailure(exc);
615
574
  }
616
575
  }
617
- async _verifyDownloadedPeerCertificate(gatewayUrl, aid, certPem, certFingerprint = '') {
576
+ async _verifyDownloadedPeerCertificate(gatewayUrl, aid, certPem, certFingerprint = '', signedAt) {
618
577
  const expectedFp = String(certFingerprint ?? '').trim().toLowerCase();
619
- if (expectedFp && !certMatchesFingerprint(certPem, expectedFp)) {
578
+ if (expectedFp && !certMatchesFingerprint(certPem, expectedFp) && !(signedAt !== undefined && publicKeyMatchesFingerprint(certPem, expectedFp))) {
620
579
  throw new ValidationError(`certificate fingerprint mismatch for aid: ${aid}`);
621
580
  }
622
- await this._peerAuth.verifyPeerCertificate(gatewayUrl, certPem, aid);
581
+ if (signedAt === undefined)
582
+ await this._peerAuth.verifyPeerCertificate(gatewayUrl, certPem, aid);
583
+ else {
584
+ validateAgentMdCertificate(certPem, aid, signedAt, { requireCurrentValidity: false, readCompatibility: true });
585
+ await this._peerAuth._verifyAuthCertChain(gatewayUrl, new X509Certificate(certPem), aid, signedAt);
586
+ }
623
587
  }
624
588
  _authIdentityFromAid(aid) {
625
589
  return {
@@ -658,10 +622,11 @@ export class AIDStore {
658
622
  return resultErr(err?.code ?? codes.CERT_NOT_FOUND, err?.message ?? `certificate not found for aid: ${target}`);
659
623
  }
660
624
  const current = loaded.data.aid;
661
- if (!current.isPrivateKeyValid() || !current.privateKeyPem) {
662
- return resultErr(codes.PRIVATE_KEY_NOT_VALID, `uploadAgentMd requires local AID with a valid private key: ${target}`);
663
- }
664
- const uploaderTarget = String(options.uploaderAid ?? options.uploader_aid ?? target).trim();
625
+ const raw = content ?? (String((this._agentMdManager._loadRecord?.(target, true) ?? this._agentMdManager.loadRecord(target))?.local_draft_content ?? '') || this._agentMdManager.readContent(target));
626
+ const parsed = validateAgentMdDocument(raw, { expectedAid: target, validateOwnership: false });
627
+ const meta = parsed.frontmatter;
628
+ const entity = ['group', 'org'].includes(String(meta.type));
629
+ const uploaderTarget = String(options.uploaderAid ?? options.uploader_aid ?? (entity ? meta.owner : target)).trim();
665
630
  this._registerFlow.validateAidName(uploaderTarget);
666
631
  let uploader = current;
667
632
  if (uploaderTarget.toLowerCase() !== target.toLowerCase()) {
@@ -675,19 +640,48 @@ export class AIDStore {
675
640
  return resultErr(codes.PRIVATE_KEY_NOT_VALID, `uploadAgentMd requires uploader AID with a valid private key: ${uploaderTarget}`);
676
641
  }
677
642
  }
643
+ if (!entity && (!current.isPrivateKeyValid() || !current.privateKeyPem)) {
644
+ return resultErr(codes.PRIVATE_KEY_NOT_VALID, `uploadAgentMd requires local AID with a valid private key: ${target}`);
645
+ }
678
646
  const manager = new AgentMdManager({
679
647
  aunPath: this.aunPath,
680
648
  verifySsl: this._verifySsl,
681
649
  logger: this._log.for('aun_core.agent_md'),
682
650
  uploadViaGatewayHost: true,
683
- ownerAidGetter: () => target,
684
- currentAidGetter: () => current,
651
+ ownerAidGetter: () => entity ? uploaderTarget : target,
652
+ currentAidGetter: () => entity ? uploader : current,
653
+ entityIdentityGetter: options.entityIdentityGetter ?? (async (value, type) => {
654
+ const gateway = await this._resolveGateway(value);
655
+ const material = await this._peerAuth.resolveCurrentEntityCertificate(gateway, value, type ?? 'org');
656
+ const certPem = material.cert_pem;
657
+ return AID._create({ aid: value, aunPath: this.aunPath, certPem, certValid: true, privateKeyPem: null, privateKeyValid: false });
658
+ }),
659
+ entitySignerResolver: async (value, fingerprint) => {
660
+ const material = await this._peerAuth.resolveOwnershipCertificate(await this._resolveGateway(value), value, fingerprint, 'signing');
661
+ return AID._create({ aid: value, aunPath: this.aunPath, certPem: material.cert_pem, certValid: true, privateKeyPem: null, privateKeyValid: false });
662
+ },
663
+ entityOwnerResolver: options.entityOwnerResolver,
664
+ peerResolver: (value, fingerprint, signedAt) => this._resolveAgentMdPeerCertificate(value, fingerprint, signedAt),
685
665
  gatewayResolver: (value) => this._resolveGateway(value),
686
666
  accessTokenResolver: async () => this._uploadAgentMdToken(uploader, await this._resolveGateway(uploaderTarget)),
687
667
  aidValidator: (value) => this._registerFlow.validateAidName(value),
688
668
  });
689
- const ownerOperationId = options.ownerOperationId ?? options.owner_operation_id;
690
- return resultOk(await manager.upload(content, ownerOperationId));
669
+ let prepared = raw;
670
+ if (entity && !parsed.signature) {
671
+ const signed = await this.sign(target, raw, { format: 'document', identity: current, signerAid: target });
672
+ if (!signed.ok)
673
+ return signed;
674
+ prepared = signed.data.signed;
675
+ }
676
+ if (entity && options.coSignerAid) {
677
+ if (options.coSignerAid !== uploaderTarget)
678
+ throw new ValidationError('entity additional signer must be its uploading owner');
679
+ const signed = await this.sign(options.coSignerAid, prepared, { format: 'document', append: true, identity: uploader });
680
+ if (!signed.ok)
681
+ return signed;
682
+ prepared = signed.data.signed;
683
+ }
684
+ return resultOk(entity ? await manager.uploadAs(target, prepared, current) : await manager.upload(raw));
691
685
  }
692
686
  catch (exc) {
693
687
  return agentMdFailure(exc);
@@ -782,8 +776,44 @@ export class AIDStore {
782
776
  try {
783
777
  this._registerFlow.validateAidName(target);
784
778
  const oldAid = loaded.data.aid;
785
- const newIdentity = this._registerFlow.generateIdentity();
786
779
  const gatewayUrl = await this._resolveGateway(target);
780
+ const pendingStore = this._keystore;
781
+ let pendingDir = null;
782
+ let newIdentity = null;
783
+ for (const handle of pendingStore.listPendingIdentityDirs(target)) {
784
+ const candidate = pendingStore.loadPendingKeyPair(handle, target);
785
+ if (candidate?.rekey_pending === true) {
786
+ pendingDir = handle;
787
+ newIdentity = candidate;
788
+ break;
789
+ }
790
+ }
791
+ if (newIdentity) {
792
+ const serverCert = await this._registerFlow.fetchPeerCert(gatewayUrl, target);
793
+ if (serverCert) {
794
+ const cert = new X509Certificate(serverCert);
795
+ const serverPublicKey = cert.publicKey.export({ type: 'spki', format: 'der' }).toString('base64');
796
+ if (serverPublicKey === newIdentity.public_key_der_b64 && certCommonName(serverCert) === target && !certTimeError(serverCert)) {
797
+ pendingStore.savePendingCert(pendingDir, serverCert);
798
+ this._keystore.saveIdentity(target, {
799
+ aid: target, private_key_pem: newIdentity.private_key_pem,
800
+ public_key_der_b64: newIdentity.public_key_der_b64, curve: newIdentity.curve, cert: serverCert,
801
+ });
802
+ pendingStore.discardPendingIdentity(pendingDir);
803
+ const refreshed = this.load(target);
804
+ if (!refreshed.ok || !refreshed.data)
805
+ return resultErr(codes.REKEY_FAILED, 'rekeyed identity reload failed');
806
+ return resultOk({ rekeyed: true, new_cert_not_after: refreshed.data.aid.certNotAfter, new_fingerprint: refreshed.data.aid.certFingerprint });
807
+ }
808
+ }
809
+ }
810
+ else {
811
+ newIdentity = this._registerFlow.generateIdentity();
812
+ pendingDir = pendingStore.pendingIdentityDir(target);
813
+ pendingStore.savePendingKeyPair(pendingDir, target, { ...newIdentity, aid: target, rekey_pending: true });
814
+ }
815
+ if (!pendingDir || !newIdentity)
816
+ return resultErr(codes.REKEY_FAILED, 'pending rekey identity is unavailable');
787
817
  const clientNonce = this._registerFlow.newClientNonce();
788
818
  const phase1 = await this._registerFlow.shortRpc(gatewayUrl, 'auth.aid_login1', {
789
819
  aid: target, cert: oldAid.certPem, client_nonce: clientNonce,
@@ -801,7 +831,12 @@ export class AIDStore {
801
831
  const certPem = String(response.cert ?? response.cert_pem ?? '').trim();
802
832
  if (!certPem)
803
833
  return resultErr(codes.REKEY_FAILED, 'server response missing certificate');
804
- this._keystore.saveIdentity(target, { aid: target, ...newIdentity, cert: certPem });
834
+ pendingStore.savePendingCert(pendingDir, certPem);
835
+ this._keystore.saveIdentity(target, {
836
+ aid: target, private_key_pem: newIdentity.private_key_pem,
837
+ public_key_der_b64: newIdentity.public_key_der_b64, curve: newIdentity.curve, cert: certPem,
838
+ });
839
+ pendingStore.discardPendingIdentity(pendingDir);
805
840
  const refreshed = this.load(target);
806
841
  if (!refreshed.ok || !refreshed.data)
807
842
  return resultErr(codes.REKEY_FAILED, 'rekeyed identity reload failed');