@agentunion/fastaun 0.5.19 → 0.5.21
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 +176 -78
- package/_packed_docs/CHANGELOG.md +176 -78
- package/_packed_docs/INDEX.md +288 -269
- package/_packed_docs/KITE_DOCS_GUIDE.md +101 -95
- package/_packed_docs/agent.md/SCHEMA.md +7 -7
- package/_packed_docs/agent.md/working/AID/347/261/273/345/236/213-owner/345/205/263/347/263/273/344/270/216/346/215/242owner/347/274/226/346/216/222/346/226/271/346/241/210.md +530 -0
- package/_packed_docs/agent.md/working/AID/347/261/273/345/236/213-owner/345/205/263/347/263/273/345/256/214/346/225/264/350/256/276/350/256/241/344/270/216/345/256/236/346/226/275/350/256/241/345/210/222.md +685 -0
- package/_packed_docs/agent.md/working/agent.md-schema/344/270/216/347/255/276/345/217/221/351/252/214/347/255/276/346/265/201/347/250/213/346/276/204/346/270/205.md +349 -0
- package/_packed_docs/agent.md/working//346/234/254/345/234/260Docker/346/265/213/350/257/225Bug/346/270/205/345/215/225.md +40 -0
- 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
- package/_packed_docs/protocol/06-/346/234/215/345/212/241/345/215/217/350/256/256.md +372 -331
- package/_packed_docs/protocol/08-AUN-E2EE.md +17 -12
- package/_packed_docs/protocol/10-Group-/345/255/220/345/215/217/350/256/256.md +676 -676
- package/_packed_docs/protocol/11-Storage-/345/255/220/345/215/217/350/256/256.md +8 -12
- package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/211.md +445 -0
- package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2112.0.md +161 -0
- package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2112.1.md +666 -0
- package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2113.0.md +233 -0
- package/_packed_docs/protocol/agent.md/SCHEMA.md +1 -1
- package/_packed_docs/sdk/0.5.21-SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +368 -0
- package/_packed_docs/sdk/0.5.21-SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +350 -0
- package/_packed_docs/sdk/01-/345/277/253/351/200/237/345/274/200/345/247/213.md +1 -1
- package/_packed_docs/sdk/02-WebSocket/345/215/217/350/256/256.md +5 -1
- package/_packed_docs/sdk/03-/346/240/270/345/277/203/346/246/202/345/277/265.md +64 -64
- package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +70 -63
- package/_packed_docs/sdk/05-E2EE/345/212/240/345/257/206/351/200/232/344/277/241.md +43 -40
- package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +179 -19
- package/_packed_docs/sdk/08-/346/234/200/344/275/263/345/256/236/350/267/265.md +50 -49
- package/_packed_docs/sdk/09-group-rpc-manual.md +261 -254
- package/_packed_docs/sdk/09-message-rpc-manual.md +253 -160
- package/_packed_docs/sdk/09-storage-rpc-manual.md +18 -26
- package/_packed_docs/sdk/09-/345/207/255/350/257/201/346/234/215/345/212/241RPC/346/211/213/345/206/214.md +71 -0
- package/_packed_docs/sdk/09-/347/273/204/347/273/207/346/234/215/345/212/241RPC/346/211/213/345/206/214.md +88 -0
- package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +105 -92
- package/_packed_docs/sdk/INDEX.md +197 -167
- package/_packed_docs/sdk/Ownership-SDK/345/256/236/347/216/260/346/214/207/345/215/227.md +169 -0
- package/_packed_docs/sdk/Ownership-SDK/347/274/226/346/216/222/346/214/207/345/215/227.md +349 -0
- package/_packed_docs/sdk/Ownership-SDK/347/274/226/346/216/222/346/214/207/345/215/2272.1.md +930 -0
- package/_packed_docs/sdk/Ownership-SDK/351/233/206/346/210/220/346/214/207/345/215/227.md +270 -0
- 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 +363 -0
- 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 +553 -0
- 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
- package/_packed_docs/sdk/Ownership2.1/350/257/246/347/273/206/350/256/276/350/256/241.md +480 -0
- package/_packed_docs/sdk/Ownership/347/256/241/347/220/206/344/270/223/351/242/230.md +559 -0
- package/_packed_docs/sdk/ProtectedHeaders/347/224/250/346/263/225/344/270/223/351/242/230.md +299 -0
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- package/dist/agent-md-schema.d.ts +13 -1
- package/dist/agent-md-schema.js +63 -33
- package/dist/agent-md-schema.js.map +1 -1
- package/dist/agent-md.d.ts +30 -3
- package/dist/agent-md.js +293 -152
- package/dist/agent-md.js.map +1 -1
- package/dist/aid-store.d.ts +42 -0
- package/dist/aid-store.js +224 -44
- package/dist/aid-store.js.map +1 -1
- package/dist/aid.js +18 -13
- package/dist/aid.js.map +1 -1
- package/dist/auth.js +78 -2
- package/dist/auth.js.map +1 -1
- package/dist/cert-utils.d.ts +44 -1
- package/dist/cert-utils.js +157 -56
- package/dist/cert-utils.js.map +1 -1
- package/dist/client/delivery.d.ts +68 -1
- package/dist/client/delivery.js +1196 -135
- package/dist/client/delivery.js.map +1 -1
- package/dist/client/rpc-pipeline.d.ts +5 -1
- package/dist/client/rpc-pipeline.js +240 -111
- package/dist/client/rpc-pipeline.js.map +1 -1
- package/dist/client/v2-e2ee.d.ts +12 -2
- package/dist/client/v2-e2ee.js +842 -379
- package/dist/client/v2-e2ee.js.map +1 -1
- package/dist/client.d.ts +26 -2
- package/dist/client.js +420 -75
- package/dist/client.js.map +1 -1
- package/dist/config.d.ts +4 -0
- package/dist/config.js +1 -0
- package/dist/config.js.map +1 -1
- package/dist/events.d.ts +23 -4
- package/dist/events.js +171 -15
- package/dist/events.js.map +1 -1
- package/dist/facades.d.ts +43 -1
- package/dist/facades.js +368 -12
- package/dist/facades.js.map +1 -1
- package/dist/index.d.ts +7 -4
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/keystore/aid-db.d.ts +7 -2
- package/dist/keystore/aid-db.js +194 -43
- package/dist/keystore/aid-db.js.map +1 -1
- package/dist/keystore/index.d.ts +15 -1
- package/dist/keystore/local-token-store.d.ts +10 -2
- package/dist/keystore/local-token-store.js +81 -16
- package/dist/keystore/local-token-store.js.map +1 -1
- package/dist/org-protocol.d.ts +45 -0
- package/dist/org-protocol.js +829 -22
- package/dist/org-protocol.js.map +1 -1
- package/dist/ownership-v21.d.ts +40 -9
- package/dist/ownership-v21.js +174 -79
- package/dist/ownership-v21.js.map +1 -1
- package/dist/ownership-workflow.d.ts +22 -0
- package/dist/ownership-workflow.js +861 -66
- package/dist/ownership-workflow.js.map +1 -1
- package/dist/secret-store/file-store.js +84 -0
- package/dist/secret-store/file-store.js.map +1 -1
- package/dist/seq-tracker.d.ts +1 -0
- package/dist/seq-tracker.js +2 -2
- package/dist/seq-tracker.js.map +1 -1
- package/dist/tools/cross-sdk-agent.d.ts +67 -3
- package/dist/tools/cross-sdk-agent.js +575 -42
- package/dist/tools/cross-sdk-agent.js.map +1 -1
- package/dist/transport.d.ts +4 -0
- package/dist/transport.js +108 -44
- package/dist/transport.js.map +1 -1
- package/dist/types.d.ts +1 -0
- package/dist/v2/e2ee/encrypt-group.js +1 -0
- package/dist/v2/e2ee/encrypt-group.js.map +1 -1
- package/dist/v2/e2ee/encrypt-p2p.js +1 -0
- package/dist/v2/e2ee/encrypt-p2p.js.map +1 -1
- package/dist/v2/e2ee/types.d.ts +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/agent-md.js
CHANGED
|
@@ -3,15 +3,26 @@ import * as fs from 'node:fs';
|
|
|
3
3
|
import * as http from 'node:http';
|
|
4
4
|
import * as https from 'node:https';
|
|
5
5
|
import * as path from 'node:path';
|
|
6
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
6
7
|
import { parseDocument } from 'yaml';
|
|
7
8
|
import { AID } from './aid.js';
|
|
8
|
-
import { certMatchesFingerprint, parseAgentMdTailSignature } from './cert-utils.js';
|
|
9
|
-
import { parseFileFrontmatter, validateAgentMdDocument, verifyAgentMdUpload } from './agent-md-schema.js';
|
|
9
|
+
import { certMatchesFingerprint, normalizeFingerprintHex, parseAgentMdTailSignature, publicKeyMatchesFingerprint, verifyDocument } from './cert-utils.js';
|
|
10
|
+
import { parseFileFrontmatter, validateAgentMdDocument, validateAgentMdReadDocument, verifyAgentMdBodyUpdate, verifyAgentMdUpload } from './agent-md-schema.js';
|
|
10
11
|
import { validateOwnershipFrontmatter, verifyOwnershipFrontmatterStatus } from './ownership-proof.js';
|
|
11
12
|
import { AUNError, ClientSignatureError, NotFoundError, StateError, ValidationError, } from './errors.js';
|
|
12
13
|
import { isJsonObject } from './types.js';
|
|
13
|
-
import { ownershipFailure, parseOwnershipFile } from './ownership-v21.js';
|
|
14
|
+
import { ownershipAid, ownershipFailure, parseOwnershipFile } from './ownership-v21.js';
|
|
15
|
+
import { resultOk } from './result.js';
|
|
14
16
|
const DEFAULT_HTTP_TIMEOUT_MS = 30_000;
|
|
17
|
+
function relationshipFields(meta) {
|
|
18
|
+
return Object.fromEntries(['owner', 'ownership', 'ownership_proof', 'ownership_release'].filter((key) => Object.hasOwn(meta, key)).map((key) => [key, meta[key]]));
|
|
19
|
+
}
|
|
20
|
+
function legacyRelationship(meta) {
|
|
21
|
+
if (['group', 'org'].includes(String(meta.type)))
|
|
22
|
+
return undefined;
|
|
23
|
+
const fields = relationshipFields(meta);
|
|
24
|
+
return Object.keys(fields).length ? fields : undefined;
|
|
25
|
+
}
|
|
15
26
|
const AGENT_MD_NEGATIVE_CACHE_TTL_MS = 60_000;
|
|
16
27
|
export function buildOwnershipReleaseAgentMd(content, release) {
|
|
17
28
|
return updateOwnershipAgentMd(content, { ownership_release: release });
|
|
@@ -21,7 +32,7 @@ export function buildOwnershipAgentMd(content, owner, proof) {
|
|
|
21
32
|
}
|
|
22
33
|
export function updateOwnershipAgentMd(content, fields) {
|
|
23
34
|
const payload = parseAgentMdTailSignature(String(content ?? '')).payload;
|
|
24
|
-
const match =
|
|
35
|
+
const match = /^\ufeff?---(?:\r\n|\n|\r)([\s\S]*?)(?:\r\n|\n|\r)---([\s\S]*)$/.exec(payload);
|
|
25
36
|
if (!match)
|
|
26
37
|
throw new ValidationError('agent.md must start with YAML frontmatter');
|
|
27
38
|
const document = parseDocument(match[1], { uniqueKeys: true });
|
|
@@ -37,7 +48,7 @@ export function updateOwnershipAgentMd(content, fields) {
|
|
|
37
48
|
for (const [name, value] of Object.entries(fields))
|
|
38
49
|
document.set(name, value);
|
|
39
50
|
const yamlText = document.toString().replace(/\r\n/g, '\n');
|
|
40
|
-
const updated =
|
|
51
|
+
const updated = `${payload.startsWith('\ufeff') ? '\ufeff' : ''}---\n${yamlText.endsWith('\n') ? yamlText : `${yamlText}\n`}---${match[2]}`;
|
|
41
52
|
validateAgentMdDocument(updated, { expectedAid: String(frontmatter.aid ?? '') });
|
|
42
53
|
return updated;
|
|
43
54
|
}
|
|
@@ -251,12 +262,12 @@ export class AgentMdManager {
|
|
|
251
262
|
this._atomicWriteText(filePath, String(content ?? ''));
|
|
252
263
|
return filePath;
|
|
253
264
|
}
|
|
254
|
-
|
|
265
|
+
_loadRecord(aid, strict = false) {
|
|
255
266
|
const target = String(aid ?? '').trim();
|
|
256
267
|
if (!target)
|
|
257
268
|
return null;
|
|
258
269
|
try {
|
|
259
|
-
const record = this._withRecordLock(target, () => this._readRecordUnlocked(target));
|
|
270
|
+
const record = this._withRecordLock(target, () => this._readRecordUnlocked(target, strict));
|
|
260
271
|
const loaded = Object.keys(record).length > 0 ? { ...record, aid: target } : { aid: target };
|
|
261
272
|
try {
|
|
262
273
|
const content = this.readContent(target);
|
|
@@ -270,7 +281,7 @@ export class AgentMdManager {
|
|
|
270
281
|
}
|
|
271
282
|
if (Object.keys(loaded).length <= 1)
|
|
272
283
|
return null;
|
|
273
|
-
if (Object.keys(record).length === 0 && typeof loaded.content === 'string') {
|
|
284
|
+
if (Object.keys(record).length === 0 && typeof loaded.content === 'string' && !fs.existsSync(this.metaPath(target))) {
|
|
274
285
|
this._withRecordLock(target, () => this._writeRecordUnlocked(target, {
|
|
275
286
|
aid: target,
|
|
276
287
|
local_etag: loaded.local_etag,
|
|
@@ -281,11 +292,16 @@ export class AgentMdManager {
|
|
|
281
292
|
return { ...loaded };
|
|
282
293
|
}
|
|
283
294
|
catch (err) {
|
|
295
|
+
if (strict)
|
|
296
|
+
throw err;
|
|
284
297
|
this._log.debug(`agent.md cache load skipped: aid=${target} err=${err instanceof Error ? err.message : String(err)}`);
|
|
285
298
|
return null;
|
|
286
299
|
}
|
|
287
300
|
}
|
|
288
|
-
|
|
301
|
+
loadRecord(aid) {
|
|
302
|
+
return this._loadRecord(aid);
|
|
303
|
+
}
|
|
304
|
+
_saveRecord(aid, fields, strict = false) {
|
|
289
305
|
const target = String(aid ?? '').trim();
|
|
290
306
|
if (!target)
|
|
291
307
|
return {};
|
|
@@ -294,6 +310,7 @@ export class AgentMdManager {
|
|
|
294
310
|
const hasContent = Object.prototype.hasOwnProperty.call(inputFields, 'content') && inputFields.content !== undefined && inputFields.content !== null;
|
|
295
311
|
let savedTo = '';
|
|
296
312
|
const record = this._withRecordLock(target, () => {
|
|
313
|
+
const next = { ...this._readRecordUnlocked(target, true), aid: target };
|
|
297
314
|
if (hasContent) {
|
|
298
315
|
const content = String(inputFields.content ?? '');
|
|
299
316
|
savedTo = this.writeContent(target, content);
|
|
@@ -303,7 +320,6 @@ export class AgentMdManager {
|
|
|
303
320
|
inputFields.fetched_at = Date.now();
|
|
304
321
|
}
|
|
305
322
|
delete inputFields.content;
|
|
306
|
-
const next = { ...this._readRecordUnlocked(target), aid: target };
|
|
307
323
|
for (const [key, value] of Object.entries(inputFields)) {
|
|
308
324
|
if (value !== undefined && value !== null)
|
|
309
325
|
next[key] = value;
|
|
@@ -319,7 +335,7 @@ export class AgentMdManager {
|
|
|
319
335
|
loaded.saved_to = savedTo;
|
|
320
336
|
}
|
|
321
337
|
else {
|
|
322
|
-
const current = this.
|
|
338
|
+
const current = this._loadRecord(target, strict);
|
|
323
339
|
if (current && typeof current.content === 'string')
|
|
324
340
|
loaded.content = current.content;
|
|
325
341
|
}
|
|
@@ -327,11 +343,27 @@ export class AgentMdManager {
|
|
|
327
343
|
return { ...loaded };
|
|
328
344
|
}
|
|
329
345
|
catch (err) {
|
|
346
|
+
if (strict)
|
|
347
|
+
throw err;
|
|
330
348
|
this._log.debug(`agent.md cache save skipped: aid=${target} err=${err instanceof Error ? err.message : String(err)}`);
|
|
331
349
|
return {};
|
|
332
350
|
}
|
|
333
351
|
}
|
|
334
|
-
|
|
352
|
+
saveRecord(aid, fields) {
|
|
353
|
+
return this._saveRecord(aid, fields, Object.prototype.hasOwnProperty.call(fields, 'local_draft_content'));
|
|
354
|
+
}
|
|
355
|
+
_clearConfirmedDraft(aid, content) {
|
|
356
|
+
this._withRecordLock(aid, () => {
|
|
357
|
+
const record = this._readRecordUnlocked(aid, true);
|
|
358
|
+
if (record.local_draft_content !== content)
|
|
359
|
+
return;
|
|
360
|
+
record.local_draft_content = '';
|
|
361
|
+
record.updated_at = Date.now();
|
|
362
|
+
this._writeRecordUnlocked(aid, record);
|
|
363
|
+
});
|
|
364
|
+
this._cache.delete(aid);
|
|
365
|
+
}
|
|
366
|
+
async upload(content, originalCondition) {
|
|
335
367
|
const target = this._ownerAid();
|
|
336
368
|
if (!target)
|
|
337
369
|
throw new ValidationError('uploadAgentMd requires local AID');
|
|
@@ -339,28 +371,57 @@ export class AgentMdManager {
|
|
|
339
371
|
if (!current?.isPrivateKeyValid()) {
|
|
340
372
|
throw new StateError('uploadAgentMd requires loaded AID with a valid private key');
|
|
341
373
|
}
|
|
342
|
-
const rawContent = content === undefined || content === null ? this.readContent(target) : String(content);
|
|
374
|
+
const rawContent = content === undefined || content === null ? String(this._loadRecord(target, true)?.local_draft_content ?? '') || this.readContent(target) : String(content);
|
|
343
375
|
if (!rawContent.trim())
|
|
344
376
|
throw new ValidationError('uploadAgentMd requires non-empty content');
|
|
345
|
-
const parsed = validateAgentMdDocument(rawContent, { expectedAid: target });
|
|
377
|
+
const parsed = validateAgentMdDocument(rawContent, { expectedAid: target, validateOwnership: false });
|
|
346
378
|
if (['group', 'org'].includes(String(parsed.frontmatter.type)))
|
|
347
379
|
return await this.uploadAs(target, rawContent, current);
|
|
380
|
+
if (parseAgentMdTailSignature(rawContent).signatures.length > 1) {
|
|
381
|
+
await this._verifyDocument(rawContent, target, current, true, true);
|
|
382
|
+
return await this._uploadPrepared(target, rawContent, current, originalCondition);
|
|
383
|
+
}
|
|
348
384
|
const signed = current.signAgentMd(rawContent);
|
|
349
385
|
if (!signed.ok || !signed.data) {
|
|
350
386
|
const message = signed.error?.message ?? 'agent.md signing failed';
|
|
351
387
|
throw new ClientSignatureError(message);
|
|
352
388
|
}
|
|
353
389
|
const signedContent = signed.data.signed;
|
|
354
|
-
|
|
355
|
-
return await this._uploadPrepared(target, signedContent, current);
|
|
390
|
+
verifyAgentMdBodyUpdate(signedContent, current.certPem, target);
|
|
391
|
+
return await this._uploadPrepared(target, signedContent, current, originalCondition);
|
|
392
|
+
}
|
|
393
|
+
async readAgentMdFresh(aid) {
|
|
394
|
+
const target = this.safeAid(aid), gateway = await this._resolveGateway(target);
|
|
395
|
+
const response = await requestText(this._url(target, gateway, this._uploadViaGatewayHost), {
|
|
396
|
+
method: 'GET', verifySsl: this._verifySsl,
|
|
397
|
+
headers: { 'X-AUN-Signature-Format': 'multi-v1', Accept: 'text/markdown', 'Cache-Control': 'no-cache', ...(this._uploadViaGatewayHost ? { Host: target } : {}) },
|
|
398
|
+
timeoutMs: DEFAULT_HTTP_TIMEOUT_MS, redirectsLeft: 0, strictUtf8: true,
|
|
399
|
+
});
|
|
400
|
+
if (response.status === 404)
|
|
401
|
+
return { exists: false, content: '', etag: '' };
|
|
402
|
+
if (response.status !== 200)
|
|
403
|
+
throw ownershipFailure('unavailable', `agent.md fresh read failed: HTTP ${response.status}`, true);
|
|
404
|
+
const etag = headerValue(response.headers, 'ETag');
|
|
405
|
+
if (!etag)
|
|
406
|
+
throw ownershipFailure('unavailable', 'agent.md fresh read has no ETag', true);
|
|
407
|
+
return { exists: true, content: response.text, etag };
|
|
356
408
|
}
|
|
357
409
|
async readOwnershipFresh(aid) {
|
|
358
410
|
const target = this.safeAid(aid);
|
|
359
411
|
const response = await this._ownershipRequest(target, 'GET');
|
|
412
|
+
if ([200, 400, 404].includes(response.status) && headerValue(response.headers, 'X-AUN-Ownership-Status') === 'not_applicable')
|
|
413
|
+
throw ownershipFailure('not_applicable', 'group/org uses its lifecycle owner relationship');
|
|
414
|
+
if (response.status === 400) {
|
|
415
|
+
let value;
|
|
416
|
+
try {
|
|
417
|
+
value = JSON.parse(response.text);
|
|
418
|
+
}
|
|
419
|
+
catch { }
|
|
420
|
+
if (isJsonObject(value) && value.error === 'invalid')
|
|
421
|
+
throw ownershipFailure('invalid', 'NS rejected the current agent.md: HTTP 400');
|
|
422
|
+
}
|
|
360
423
|
if (response.status !== 200 && response.status !== 404)
|
|
361
424
|
throw ownershipFailure('unavailable', `NS fresh read failed: HTTP ${response.status}`, true);
|
|
362
|
-
if (headerValue(response.headers, 'X-AUN-Ownership-Status') === 'not_applicable')
|
|
363
|
-
throw ownershipFailure('not_applicable', 'group/org uses its lifecycle owner relationship');
|
|
364
425
|
let type;
|
|
365
426
|
if (response.status === 200) {
|
|
366
427
|
try {
|
|
@@ -380,7 +441,7 @@ export class AgentMdManager {
|
|
|
380
441
|
throw ownershipFailure('unavailable', 'NS fresh metadata is incomplete', true);
|
|
381
442
|
return { sub_aid: target, exists: response.status === 200, content: response.status === 200 ? response.text : '', etag, protocol, relation_version: Number(version), lock: lock, observed_at: Math.floor(Date.now() / 1000) };
|
|
382
443
|
}
|
|
383
|
-
async submitOwnershipRaw(aid, raw, condition) {
|
|
444
|
+
async submitOwnershipRaw(aid, raw, condition, assertActive) {
|
|
384
445
|
const target = this.safeAid(aid), current = this._currentAid();
|
|
385
446
|
if (!current?.isPrivateKeyValid() || current.aid !== target || this._ownerAid() !== target)
|
|
386
447
|
throw ownershipFailure('forbidden', 'raw agent.md submission requires its own authenticated identity');
|
|
@@ -388,24 +449,25 @@ export class AgentMdManager {
|
|
|
388
449
|
throw new ValidationError('raw agent.md requires one original condition');
|
|
389
450
|
parseOwnershipFile(raw, 'agent', { expectedSubAid: target });
|
|
390
451
|
verifyAgentMdUpload(raw, current.certPem, target);
|
|
391
|
-
const response = await this._ownershipRequest(target, 'PUT', raw, condition);
|
|
452
|
+
const response = await this._ownershipRequest(target, 'PUT', raw, condition, false, assertActive);
|
|
392
453
|
if (response.status === 409 || response.status === 412)
|
|
393
454
|
throw ownershipFailure('conflict', `NS original condition failed: HTTP ${response.status}`);
|
|
394
455
|
if (response.status === 428)
|
|
395
456
|
throw ownershipFailure('unsupported_protocol', 'NS does not support the required original document condition');
|
|
396
457
|
if (response.status === 401 || response.status === 403)
|
|
397
458
|
throw ownershipFailure('forbidden', `NS self submission denied: HTTP ${response.status}`);
|
|
459
|
+
if (response.status === 400)
|
|
460
|
+
throw ownershipFailure('invalid', 'NS rejected the submitted agent.md: HTTP 400');
|
|
398
461
|
if (response.status < 200 || response.status >= 300)
|
|
399
462
|
throw ownershipFailure('unavailable', `NS raw submission failed: HTTP ${response.status}`, true);
|
|
400
463
|
const fresh = await this.readOwnershipFresh(target);
|
|
401
464
|
if (fresh.content !== raw)
|
|
402
465
|
throw ownershipFailure('unavailable', 'NS raw submission cannot be confirmed from the current document', true);
|
|
403
|
-
this.saveRecord(target, { content: raw, local_etag: AgentMdManager.contentEtag(raw), remote_etag: fresh.etag, fetched_at: Date.now(), checked_at: Date.now(), remote_status: 'found' });
|
|
404
466
|
return fresh;
|
|
405
467
|
}
|
|
406
|
-
async _ownershipRequest(target, method, raw, condition) {
|
|
468
|
+
async _ownershipRequest(target, method, raw, condition, legacyRead = false, assertActive) {
|
|
407
469
|
const gatewayUrl = await this._resolveGateway(target);
|
|
408
|
-
const headers = { Accept: 'text/markdown', 'Cache-Control': 'no-cache', 'X-AUN-Ownership-Check': '1' };
|
|
470
|
+
const headers = { 'X-AUN-Signature-Format': 'multi-v1', Accept: 'text/markdown', 'Cache-Control': 'no-cache', ...(!legacyRead ? { 'X-AUN-Ownership-Check': '1' } : {}) };
|
|
409
471
|
if (method === 'PUT') {
|
|
410
472
|
headers['Content-Type'] = 'text/markdown; charset=utf-8';
|
|
411
473
|
headers.Authorization = `Bearer ${await this._uploadAccessToken(target, gatewayUrl)}`;
|
|
@@ -417,11 +479,13 @@ export class AgentMdManager {
|
|
|
417
479
|
const viaGateway = this._uploadViaGatewayHost;
|
|
418
480
|
if (viaGateway)
|
|
419
481
|
headers.Host = target;
|
|
420
|
-
const request = (url) =>
|
|
421
|
-
|
|
482
|
+
const request = (url) => { if (method === 'PUT')
|
|
483
|
+
assertActive?.(); return requestText(url, { method, verifySsl: this._verifySsl, headers, body: raw, timeoutMs: DEFAULT_HTTP_TIMEOUT_MS, redirectsLeft: 0, strictUtf8: true }); };
|
|
484
|
+
const primary = this._url(target, gatewayUrl, viaGateway);
|
|
485
|
+
let response = await request(primary);
|
|
422
486
|
if (response.status === 404 && !headerValue(response.headers, 'X-AUN-Ownership-Protocol') && headerValue(response.headers, 'X-AUN-Ownership-Status') !== 'not_applicable') {
|
|
423
487
|
const fallback = nameServiceAgentMdUrl(target, gatewayUrl);
|
|
424
|
-
if (fallback && fallback !==
|
|
488
|
+
if (fallback && fallback !== primary) {
|
|
425
489
|
headers.Host = target;
|
|
426
490
|
response = await request(fallback);
|
|
427
491
|
}
|
|
@@ -433,31 +497,42 @@ export class AgentMdManager {
|
|
|
433
497
|
const rawContent = String(content ?? '');
|
|
434
498
|
if (!rawContent.trim())
|
|
435
499
|
throw new ValidationError('uploadAgentMd requires non-empty content');
|
|
436
|
-
const parsed = validateAgentMdDocument(rawContent, { expectedAid: target });
|
|
500
|
+
const parsed = validateAgentMdDocument(rawContent, { expectedAid: target, validateOwnership: false });
|
|
437
501
|
if (['group', 'org'].includes(String(parsed.frontmatter.type)) && parsed.signature) {
|
|
438
502
|
const identity = await this._entitySigningIdentity(target, parsed.frontmatter, parsed.signature, signer);
|
|
439
|
-
|
|
503
|
+
await this._verifyDocument(rawContent, target, identity, true);
|
|
440
504
|
return await this._uploadPrepared(target, rawContent, identity);
|
|
441
505
|
}
|
|
506
|
+
if (['group', 'org'].includes(String(parsed.frontmatter.type)) && signer.aid !== target) {
|
|
507
|
+
const identity = await this._entityIdentityGetter?.(target, String(parsed.frontmatter.type));
|
|
508
|
+
if (!identity?.isPrivateKeyValid())
|
|
509
|
+
throw new StateError('entity agent.md signing requires its local private key');
|
|
510
|
+
signer = identity;
|
|
511
|
+
}
|
|
442
512
|
if (!signer?.isPrivateKeyValid())
|
|
443
513
|
throw new StateError('uploadAgentMd requires a valid signer identity');
|
|
444
|
-
if (
|
|
445
|
-
await this.
|
|
514
|
+
if (parseAgentMdTailSignature(rawContent).signatures.length > 1) {
|
|
515
|
+
await this._verifyDocument(rawContent, target, signer, true, true);
|
|
516
|
+
return await this._uploadPrepared(target, rawContent, signer);
|
|
517
|
+
}
|
|
446
518
|
const signed = signer.signAgentMd(rawContent);
|
|
447
519
|
if (!signed.ok || !signed.data) {
|
|
448
520
|
const message = signed.error?.message ?? 'agent.md signing failed';
|
|
449
521
|
throw new ClientSignatureError(message);
|
|
450
522
|
}
|
|
451
523
|
const signedContent = signed.data.signed;
|
|
452
|
-
|
|
524
|
+
verifyAgentMdBodyUpdate(signedContent, signer.certPem, target);
|
|
453
525
|
return await this._uploadPrepared(target, signedContent, signer);
|
|
454
526
|
}
|
|
455
|
-
|
|
456
|
-
const record = this.
|
|
527
|
+
_pendingUpload(aid, strict = false) {
|
|
528
|
+
const record = this._loadRecord(aid, strict);
|
|
457
529
|
if (record?.pending_operation !== 'replace' || !String(record.pending_content ?? '').trim())
|
|
458
530
|
return null;
|
|
459
531
|
return { content: String(record.pending_content), content_etag: String(record.pending_content_etag ?? ''), last_error: String(record.pending_error ?? ''), condition: record.pending_condition, relation: record.pending_relation, authorization: record.pending_authorization };
|
|
460
532
|
}
|
|
533
|
+
pendingUpload(aid) {
|
|
534
|
+
return this._pendingUpload(aid, true);
|
|
535
|
+
}
|
|
461
536
|
async resumePendingUpload(aid, signer) {
|
|
462
537
|
const pending = this.pendingUpload(aid);
|
|
463
538
|
if (!pending)
|
|
@@ -467,9 +542,32 @@ export class AgentMdManager {
|
|
|
467
542
|
throw ownershipFailure('persistence_unavailable', 'pending agent.md requires its original document condition');
|
|
468
543
|
const parsed = validateAgentMdDocument(pending.content, { expectedAid: aid, validateOwnership: false });
|
|
469
544
|
const meta = parsed.frontmatter;
|
|
470
|
-
if ('ownership_proof' in meta || 'ownership_release' in meta)
|
|
471
|
-
throw ownershipFailure('unsupported_protocol', 'legacy ownership pending agent.md cannot be recovered through ordinary upload');
|
|
472
545
|
try {
|
|
546
|
+
if (pending.relation?.legacy === true)
|
|
547
|
+
return await this._resumeLegacyBodyUpload(aid, pending, signer ?? this._currentAid());
|
|
548
|
+
if (legacyRelationship(meta) && pending.relation && Object.keys(pending.relation).length === 4 && ['relation_version', 'lock', 'owner_aid', 'statement'].every((field) => Object.hasOwn(pending.relation, field))) {
|
|
549
|
+
const relation = pending.relation, statement = parseOwnershipFile(pending.content, 'agent', { expectedSubAid: aid }).statement;
|
|
550
|
+
if (!statement || !isDeepStrictEqual(statement, relation.statement) || statement.relation_version !== relation.relation_version || statement.owner_aid !== relation.owner_aid || !['locked', 'unlocked'].includes(String(relation.lock)) || pending.content_etag !== AgentMdManager.contentEtag(pending.content))
|
|
551
|
+
throw ownershipFailure('persistence_unavailable', 'pending original relationship context is corrupt');
|
|
552
|
+
const identity = signer ?? this._currentAid();
|
|
553
|
+
if (!identity || identity.aid !== aid || this._currentAid()?.aid !== aid || this._ownerAid() !== aid)
|
|
554
|
+
throw ownershipFailure('forbidden', 'body recovery requires its current subject identity');
|
|
555
|
+
await this._verifyDocument(pending.content, aid, identity, true, true);
|
|
556
|
+
const remote = await this.readAgentMdFresh(aid);
|
|
557
|
+
if (!remote.exists || !isDeepStrictEqual(legacyRelationship(meta), legacyRelationship(validateAgentMdReadDocument(remote.content, { expectedAid: aid }).frontmatter)))
|
|
558
|
+
throw ownershipFailure('conflict', 'pending agent.md relationship material changed');
|
|
559
|
+
if (remote.etag === pending.content_etag && remote.content === pending.content) {
|
|
560
|
+
this.saveRecord(aid, { content: pending.content, remote_etag: remote.etag, pending_operation: '', pending_content: '', pending_content_etag: '', pending_error: '', pending_condition: {}, pending_relation: {}, pending_authorization: '' });
|
|
561
|
+
this._clearConfirmedDraft(aid, pending.content);
|
|
562
|
+
return true;
|
|
563
|
+
}
|
|
564
|
+
if (!condition.if_match || remote.etag !== condition.if_match)
|
|
565
|
+
throw ownershipFailure('conflict', 'pending agent.md original document condition changed');
|
|
566
|
+
await this._uploadPrepared(aid, pending.content, identity, condition);
|
|
567
|
+
return true;
|
|
568
|
+
}
|
|
569
|
+
if (legacyRelationship(meta))
|
|
570
|
+
throw ownershipFailure('unsupported_protocol', 'legacy ownership pending agent.md requires its original relationship snapshot');
|
|
473
571
|
if (['group', 'org'].includes(String(meta.type))) {
|
|
474
572
|
if (!parsed.signature)
|
|
475
573
|
throw ownershipFailure('invalid', 'pending entity agent.md requires a signature');
|
|
@@ -483,13 +581,13 @@ export class AgentMdManager {
|
|
|
483
581
|
throw ownershipFailure('conflict', 'pending entity upload belongs to an old rekey generation');
|
|
484
582
|
if (pending.content_etag !== AgentMdManager.contentEtag(pending.content))
|
|
485
583
|
throw ownershipFailure('persistence_unavailable', 'pending entity original bytes are corrupt');
|
|
486
|
-
|
|
487
|
-
const uploader = this._currentAid();
|
|
488
|
-
if (uploader.aid.toLowerCase() !== aid.toLowerCase())
|
|
489
|
-
this._validateEntityUploadAuthorization(pending.authorization, aid, pending.content, uploader, String(context.entity_cert_fingerprint));
|
|
584
|
+
await this._verifyDocument(pending.content, aid, identity, true);
|
|
490
585
|
const remote = await this._readEntityFresh(aid);
|
|
491
586
|
if (remote.exists && remote.content === pending.content && remote.etag === pending.content_etag) {
|
|
492
|
-
this.saveRecord(aid, { content: pending.content, remote_etag: remote.etag, pending_operation: '', pending_content: '', pending_content_etag: '', pending_error: '', pending_condition: {}, pending_relation: {}, pending_authorization: '' });
|
|
587
|
+
const saved = this.saveRecord(aid, { content: pending.content, remote_etag: remote.etag, pending_operation: '', pending_content: '', pending_content_etag: '', pending_error: '', pending_condition: {}, pending_relation: {}, pending_authorization: '' });
|
|
588
|
+
if (!saved.aid)
|
|
589
|
+
throw new StateError('agent.md pending completion persistence failed');
|
|
590
|
+
this._clearConfirmedDraft(aid, pending.content);
|
|
493
591
|
return true;
|
|
494
592
|
}
|
|
495
593
|
if (condition.if_match && (!remote.exists || remote.etag !== condition.if_match) || condition.if_none_match === '*' && remote.exists)
|
|
@@ -497,38 +595,31 @@ export class AgentMdManager {
|
|
|
497
595
|
await this._uploadPrepared(aid, pending.content, identity, condition);
|
|
498
596
|
return true;
|
|
499
597
|
}
|
|
500
|
-
const
|
|
501
|
-
const
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
throw fail('persistence_unavailable', 'pending ownership body update requires its original relationship context');
|
|
507
|
-
if (relation) {
|
|
508
|
-
if (remote.relation_version !== relation.relation_version || remote.lock !== relation.lock)
|
|
509
|
-
throw fail('conflict', 'pending agent.md original relationship changed');
|
|
510
|
-
if (!sameStatement(candidate.statement, relation.statement))
|
|
511
|
-
throw fail('unsupported_protocol', 'ownership changes require their persisted 2.1 task');
|
|
512
|
-
if (remote.exists && (remote.relation_version > 0 || candidate.statement)) {
|
|
513
|
-
const current = parseOwnershipFile(remote.content, 'agent', { expectedSubAid: aid });
|
|
514
|
-
if (!sameStatement(current.statement, relation.statement) || (remote.owner_aid ?? current.statement?.owner_aid ?? null) !== relation.owner_aid)
|
|
515
|
-
throw fail('conflict', 'pending agent.md relationship statement changed');
|
|
516
|
-
}
|
|
598
|
+
const multipleSignatures = parseAgentMdTailSignature(pending.content).signatures.length > 1;
|
|
599
|
+
const identity = signer ?? this._currentAid();
|
|
600
|
+
if (multipleSignatures) {
|
|
601
|
+
if (!identity)
|
|
602
|
+
throw new StateError('pending agent.md requires a signer identity');
|
|
603
|
+
await this._verifyDocument(pending.content, aid, identity, true, true);
|
|
517
604
|
}
|
|
518
|
-
if (
|
|
519
|
-
throw
|
|
605
|
+
if (pending.content_etag !== AgentMdManager.contentEtag(pending.content))
|
|
606
|
+
throw ownershipFailure('persistence_unavailable', 'pending agent.md original bytes are corrupt');
|
|
607
|
+
if (!identity)
|
|
608
|
+
throw new StateError('pending agent.md requires a signer identity');
|
|
609
|
+
if (!multipleSignatures)
|
|
610
|
+
verifyAgentMdBodyUpdate(pending.content, identity.certPem, aid);
|
|
611
|
+
const remote = await this.readAgentMdFresh(aid);
|
|
520
612
|
if (remote.exists && remote.etag === pending.content_etag && remote.content === pending.content) {
|
|
521
613
|
const saved = this.saveRecord(aid, { content: pending.content, remote_etag: remote.etag, pending_operation: '', pending_content: '', pending_content_etag: '', pending_error: '', pending_condition: {}, pending_relation: {}, pending_authorization: '' });
|
|
522
614
|
if (!saved.aid)
|
|
523
615
|
throw new StateError('agent.md pending completion persistence failed');
|
|
616
|
+
this._clearConfirmedDraft(aid, pending.content);
|
|
524
617
|
return true;
|
|
525
618
|
}
|
|
526
619
|
if (condition.if_match && (!remote.exists || remote.etag !== condition.if_match) || condition.if_none_match === '*' && remote.exists)
|
|
527
620
|
throw ownershipFailure('conflict', 'pending agent.md original document condition changed');
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
throw new StateError('pending agent.md requires a signer identity');
|
|
531
|
-
verifyAgentMdUpload(pending.content, identity.certPem, aid);
|
|
621
|
+
if (remote.exists && legacyRelationship(validateAgentMdReadDocument(remote.content, { expectedAid: aid }).frontmatter))
|
|
622
|
+
throw ownershipFailure('conflict', 'legacy relationship material cannot be removed or replaced');
|
|
532
623
|
await this._uploadPrepared(aid, pending.content, identity, condition);
|
|
533
624
|
return true;
|
|
534
625
|
}
|
|
@@ -538,32 +629,38 @@ export class AgentMdManager {
|
|
|
538
629
|
}
|
|
539
630
|
}
|
|
540
631
|
async _uploadPrepared(target, signedContent, signer, originalCondition) {
|
|
541
|
-
const meta = validateAgentMdDocument(signedContent, { expectedAid: target }).frontmatter;
|
|
632
|
+
const meta = validateAgentMdDocument(signedContent, { expectedAid: target, validateOwnership: false }).frontmatter;
|
|
542
633
|
const entity = ['group', 'org'].includes(String(meta.type));
|
|
634
|
+
const legacy = legacyRelationship(meta);
|
|
635
|
+
const draftStrict = !originalCondition || !this._pendingUpload(target, true);
|
|
636
|
+
if (legacy && (signer.aid !== target || this._currentAid()?.aid !== target || this._ownerAid() !== target))
|
|
637
|
+
throw ownershipFailure('forbidden', 'legacy relationship body updates require current subject identity');
|
|
638
|
+
if (draftStrict && this._saveRecord(target, { local_draft_content: signedContent }, true).local_draft_content !== signedContent)
|
|
639
|
+
throw new StateError('agent.md local draft persistence failed');
|
|
543
640
|
const entityContext = entity ? await this._entityContext(target, meta, signer) : undefined;
|
|
544
641
|
let condition = originalCondition;
|
|
545
|
-
let relation = entityContext ?? (this.
|
|
642
|
+
let relation = entityContext ?? (this._pendingUpload(target, draftStrict))?.relation;
|
|
546
643
|
if (!condition) {
|
|
547
|
-
const fresh = entity ? await this._readEntityFresh(target) : await this.
|
|
644
|
+
const fresh = entity ? await this._readEntityFresh(target) : legacy ? await this._readLegacyBodyBase(target) : await this.readAgentMdFresh(target);
|
|
548
645
|
condition = fresh.exists ? { if_match: fresh.etag } : { if_none_match: '*' };
|
|
549
646
|
if (!entity) {
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
647
|
+
if (legacy) {
|
|
648
|
+
if (!fresh.exists || !fresh.etag || !isDeepStrictEqual(legacy, legacyRelationship(validateAgentMdReadDocument(fresh.content, { expectedAid: target }).frontmatter)))
|
|
649
|
+
throw ownershipFailure('conflict', 'legacy relationship material must remain unchanged');
|
|
650
|
+
relation = { legacy: true, base_content: fresh.content, base_content_etag: AgentMdManager.contentEtag(fresh.content), base_etag: fresh.etag, fields: legacy };
|
|
651
|
+
}
|
|
652
|
+
else {
|
|
653
|
+
if (fresh.exists && legacyRelationship(validateAgentMdReadDocument(fresh.content, { expectedAid: target }).frontmatter))
|
|
654
|
+
throw ownershipFailure('conflict', 'legacy relationship material cannot be removed or replaced');
|
|
655
|
+
relation = undefined;
|
|
656
|
+
}
|
|
554
657
|
}
|
|
555
658
|
}
|
|
556
659
|
const uploader = this._currentAid();
|
|
557
|
-
const
|
|
558
|
-
const
|
|
559
|
-
if (entityOwnerUpload) {
|
|
560
|
-
const timestamp = this._validateEntityUploadAuthorization(entityAuthorization, target, signedContent, uploader, String(entityContext.entity_cert_fingerprint));
|
|
561
|
-
if (Math.abs(Math.floor(Date.now() / 1000) - timestamp) > 300)
|
|
562
|
-
throw ownershipFailure('conflict', 'pending entity V2 authorization expired; explicit upload is required');
|
|
563
|
-
}
|
|
564
|
-
const pending = this.saveRecord(target, { pending_operation: 'replace', pending_target_aid: target, pending_content: signedContent, pending_content_etag: AgentMdManager.contentEtag(signedContent), pending_error: '', pending_condition: condition, pending_relation: relation, pending_authorization: entityAuthorization });
|
|
660
|
+
const entityAuthorization = '';
|
|
661
|
+
const pending = this._saveRecord(target, { pending_operation: 'replace', pending_target_aid: target, pending_content: signedContent, pending_content_etag: AgentMdManager.contentEtag(signedContent), pending_error: '', pending_condition: condition, pending_relation: relation, pending_authorization: entityAuthorization }, draftStrict);
|
|
565
662
|
const savedCondition = pending.pending_condition;
|
|
566
|
-
if (pending.pending_content !== signedContent || savedCondition?.if_match !== condition.if_match || savedCondition?.if_none_match !== condition.if_none_match || pending.pending_authorization !== entityAuthorization)
|
|
663
|
+
if (pending.pending_content !== signedContent || savedCondition?.if_match !== condition.if_match || savedCondition?.if_none_match !== condition.if_none_match || pending.pending_authorization !== entityAuthorization || legacy && !isDeepStrictEqual(pending.pending_relation, relation))
|
|
567
664
|
throw new StateError('agent.md pending upload persistence failed');
|
|
568
665
|
try {
|
|
569
666
|
const owner = this._currentAid();
|
|
@@ -573,24 +670,71 @@ export class AgentMdManager {
|
|
|
573
670
|
const authorization = entity ? entityAuthorization : delegated ? this._ownerUploadAuthorization(target, signedContent, owner) : '';
|
|
574
671
|
const result = await this._uploadHttp(target, signedContent, authorization, condition);
|
|
575
672
|
const remoteEtag = String(result.etag ?? '').trim();
|
|
576
|
-
const saved = this.
|
|
673
|
+
const saved = this._saveRecord(target, {
|
|
577
674
|
content: signedContent, local_etag: AgentMdManager.contentEtag(signedContent), remote_etag: remoteEtag || undefined,
|
|
578
675
|
last_modified: String(result.last_modified ?? result.lastModified ?? '').trim(), fetched_at: Date.now(), checked_at: Date.now(),
|
|
579
676
|
remote_status: remoteEtag ? 'found' : 'unknown', last_error: '',
|
|
580
677
|
pending_operation: '', pending_content: '', pending_content_etag: '', pending_error: '', pending_condition: {}, pending_relation: {}, pending_authorization: '',
|
|
581
|
-
});
|
|
678
|
+
}, draftStrict);
|
|
582
679
|
if (!saved.aid)
|
|
583
680
|
throw new StateError('agent.md upload completion persistence failed');
|
|
681
|
+
if (remoteEtag === AgentMdManager.contentEtag(signedContent))
|
|
682
|
+
this._clearConfirmedDraft(target, signedContent);
|
|
584
683
|
return { ...result };
|
|
585
684
|
}
|
|
586
685
|
catch (error) {
|
|
587
|
-
this.
|
|
686
|
+
this._saveRecord(target, { pending_error: error instanceof Error ? error.message : String(error) }, draftStrict);
|
|
588
687
|
throw error;
|
|
589
688
|
}
|
|
590
689
|
}
|
|
690
|
+
async _verifyLegacyBodyContent(target, content) {
|
|
691
|
+
const parsed = validateAgentMdReadDocument(content, { expectedAid: target, requireSignature: true });
|
|
692
|
+
if (!legacyRelationship(parsed.frontmatter))
|
|
693
|
+
throw ownershipFailure('unsupported_protocol', 'stored agent.md does not contain a legacy relationship');
|
|
694
|
+
const peer = await this._resolvePeer(target, parsed.signature.cert_fingerprint, Number(parsed.signature.timestamp));
|
|
695
|
+
const verified = await this._verifyDocument(content, target, peer);
|
|
696
|
+
if (verified.status !== 'verified')
|
|
697
|
+
throw ownershipFailure('invalid', 'stored legacy agent.md signature is invalid');
|
|
698
|
+
return parsed;
|
|
699
|
+
}
|
|
700
|
+
async _readLegacyBodyBase(target) {
|
|
701
|
+
const response = await this.readAgentMdFresh(target);
|
|
702
|
+
if (response.exists)
|
|
703
|
+
await this._verifyLegacyBodyContent(target, response.content);
|
|
704
|
+
return response;
|
|
705
|
+
}
|
|
706
|
+
async _resumeLegacyBodyUpload(target, pending, identity) {
|
|
707
|
+
const relation = pending.relation;
|
|
708
|
+
if (!identity || identity.aid !== target || this._currentAid()?.aid !== target || this._ownerAid() !== target)
|
|
709
|
+
throw ownershipFailure('forbidden', 'legacy relationship recovery requires current subject identity');
|
|
710
|
+
if (typeof relation.base_content !== 'string' || relation.base_content_etag !== AgentMdManager.contentEtag(relation.base_content) || typeof relation.base_etag !== 'string' || !relation.base_etag || pending.condition?.if_match !== relation.base_etag || pending.content_etag !== AgentMdManager.contentEtag(pending.content))
|
|
711
|
+
throw ownershipFailure('persistence_unavailable', 'pending legacy relationship original bytes or condition are missing');
|
|
712
|
+
if (parseAgentMdTailSignature(pending.content).signatures.length > 1)
|
|
713
|
+
await this._verifyDocument(pending.content, target, identity, true, true);
|
|
714
|
+
else
|
|
715
|
+
verifyAgentMdBodyUpdate(pending.content, identity.certPem, target);
|
|
716
|
+
const base = await this._verifyLegacyBodyContent(target, relation.base_content);
|
|
717
|
+
const candidate = validateAgentMdDocument(pending.content, { expectedAid: target, validateOwnership: false });
|
|
718
|
+
if (!isDeepStrictEqual(legacyRelationship(base.frontmatter), relation.fields) || !isDeepStrictEqual(relationshipFields(candidate.frontmatter), relation.fields))
|
|
719
|
+
throw ownershipFailure('persistence_unavailable', 'pending legacy relationship material changed');
|
|
720
|
+
const remote = await this._readLegacyBodyBase(target);
|
|
721
|
+
if (!remote.exists)
|
|
722
|
+
throw ownershipFailure('conflict', 'pending legacy agent.md original document is missing');
|
|
723
|
+
if (remote.content === pending.content && remote.etag === pending.content_etag) {
|
|
724
|
+
const saved = this.saveRecord(target, { content: pending.content, remote_etag: remote.etag, pending_operation: '', pending_content: '', pending_content_etag: '', pending_error: '', pending_condition: {}, pending_relation: {}, pending_authorization: '' });
|
|
725
|
+
if (!saved.aid)
|
|
726
|
+
throw new StateError('agent.md pending completion persistence failed');
|
|
727
|
+
this._clearConfirmedDraft(target, pending.content);
|
|
728
|
+
return true;
|
|
729
|
+
}
|
|
730
|
+
if (remote.content !== relation.base_content || remote.etag !== relation.base_etag)
|
|
731
|
+
throw ownershipFailure('conflict', 'pending legacy agent.md original document changed');
|
|
732
|
+
await this._uploadPrepared(target, pending.content, identity, pending.condition);
|
|
733
|
+
return true;
|
|
734
|
+
}
|
|
591
735
|
async _readEntityFresh(target) {
|
|
592
736
|
const gateway = await this._resolveGateway(target);
|
|
593
|
-
const headers = { 'Cache-Control': 'no-cache', ...(this._uploadViaGatewayHost ? { Host: target } : {}) };
|
|
737
|
+
const headers = { 'X-AUN-Ownership-Check': '1', 'X-AUN-Signature-Format': 'multi-v1', 'Cache-Control': 'no-cache', ...(this._uploadViaGatewayHost ? { Host: target } : {}) };
|
|
594
738
|
const request = (url) => requestText(url, { method: 'GET', verifySsl: this._verifySsl, headers, timeoutMs: DEFAULT_HTTP_TIMEOUT_MS, redirectsLeft: 0, strictUtf8: true });
|
|
595
739
|
const response = await request(this._url(target, gateway, this._uploadViaGatewayHost));
|
|
596
740
|
if (response.status !== 200 && response.status !== 404)
|
|
@@ -604,19 +748,23 @@ export class AgentMdManager {
|
|
|
604
748
|
}
|
|
605
749
|
async readEntityAuthorityFresh(target) {
|
|
606
750
|
const observed = await this._readEntityFresh(this.safeAid(target));
|
|
607
|
-
|
|
751
|
+
try {
|
|
752
|
+
ownershipAid(observed.owner_aid, 'owner_aid');
|
|
753
|
+
}
|
|
754
|
+
catch {
|
|
755
|
+
throw ownershipFailure('unavailable', 'entity owner authority metadata is unavailable', true);
|
|
756
|
+
}
|
|
757
|
+
if (!/^sha256:[0-9a-f]{64}$/.test(observed.entity_cert_fingerprint))
|
|
608
758
|
throw ownershipFailure('unavailable', 'entity owner authority metadata is unavailable', true);
|
|
609
759
|
return { owner_aid: observed.owner_aid, entity_cert_fingerprint: observed.entity_cert_fingerprint };
|
|
610
760
|
}
|
|
611
761
|
async _entityContext(target, meta, signer) {
|
|
612
762
|
const owner = String(meta.owner ?? '').trim().toLowerCase();
|
|
613
763
|
const uploader = this._currentAid();
|
|
614
|
-
if (
|
|
615
|
-
throw ownershipFailure('forbidden', 'group/org agent.md requires
|
|
764
|
+
if (signer.aid.toLowerCase() !== target.toLowerCase() || !uploader || uploader.aid.toLowerCase() !== owner || uploader.aid.toLowerCase() !== this._ownerAid().toLowerCase())
|
|
765
|
+
throw ownershipFailure('forbidden', 'group/org agent.md requires current owner identity');
|
|
616
766
|
const context = await this._entityCurrentContext(target, meta, true);
|
|
617
|
-
if (signer.
|
|
618
|
-
await this._verifyEntityOwnerSigner(signer);
|
|
619
|
-
else if (signer.certFingerprint !== context.entity_cert_fingerprint)
|
|
767
|
+
if (signer.certFingerprint !== context.entity_cert_fingerprint)
|
|
620
768
|
throw ownershipFailure('conflict', 'entity signing certificate changed');
|
|
621
769
|
if (uploader.aid.toLowerCase() === target.toLowerCase()) {
|
|
622
770
|
if (uploader.certFingerprint !== context.entity_cert_fingerprint)
|
|
@@ -636,6 +784,25 @@ export class AgentMdManager {
|
|
|
636
784
|
throw ownershipFailure('unavailable', 'entity owner signing certificate resolver is unavailable', true);
|
|
637
785
|
return await this._entitySignerResolver(signer, signature.cert_fingerprint);
|
|
638
786
|
}
|
|
787
|
+
async _verifyDocument(content, target, identity, current = false, bodyUpdate = false) {
|
|
788
|
+
const certificates = new Map();
|
|
789
|
+
const parsed = parseAgentMdTailSignature(content, !current);
|
|
790
|
+
const meta = validateAgentMdReadDocument(content, { expectedAid: target }).frontmatter;
|
|
791
|
+
const historicalEntityOwner = !current && ['group', 'org'].includes(String(meta.type)) && parsed.signatures.length === 1 && parsed.fields?.signer_aid !== undefined && parsed.fields.signer_aid === meta.owner && parsed.fields.signer_aid !== meta.aid;
|
|
792
|
+
const readCompatibility = !current && (!['group', 'org'].includes(String(meta.type)) || meta.type === 'group' && !Object.hasOwn(meta, 'entity_cert_fingerprint') && parsed.signatures.length === 1 && (parsed.fields?.signer_aid === undefined || parsed.fields.signer_aid === meta.aid));
|
|
793
|
+
for (const item of parsed.signatures.slice(1)) {
|
|
794
|
+
const fields = item.fields;
|
|
795
|
+
const peer = current && this._entitySignerResolver ? await this._entitySignerResolver(fields.signer_aid, fields.cert_fingerprint) : await this._resolvePeer(fields.signer_aid, fields.cert_fingerprint, Number(fields.timestamp));
|
|
796
|
+
certificates.set(`${fields.signer_aid};${fields.cert_fingerprint}`, peer.certPem);
|
|
797
|
+
}
|
|
798
|
+
const resolver = (aid, fingerprint) => certificates.get(`${aid};${fingerprint}`);
|
|
799
|
+
if (current)
|
|
800
|
+
(bodyUpdate ? verifyAgentMdBodyUpdate : verifyAgentMdUpload)(content, identity.certPem, target, undefined, resolver);
|
|
801
|
+
const result = verifyDocument(content, { expectedAid: historicalEntityOwner ? String(meta.owner) : target, primaryCertPem: identity.certPem, requireCurrentValidity: current, readCompatibility, certificateResolver: resolver });
|
|
802
|
+
if (result.status === 'invalid')
|
|
803
|
+
throw ownershipFailure('invalid', result.reason ?? 'document signature verification failed');
|
|
804
|
+
return result;
|
|
805
|
+
}
|
|
639
806
|
async _verifyEntityOwnerSigner(owner) {
|
|
640
807
|
if (!this._entitySignerResolver)
|
|
641
808
|
throw ownershipFailure('unavailable', 'entity owner signing certificate resolver is unavailable', true);
|
|
@@ -663,52 +830,21 @@ export class AgentMdManager {
|
|
|
663
830
|
throw ownershipFailure('conflict', 'entity agent.md certificate generation changed');
|
|
664
831
|
return { type: meta.type, owner_aid: owner, entity_cert_fingerprint: entity.certFingerprint };
|
|
665
832
|
}
|
|
666
|
-
|
|
667
|
-
const invalid = ownershipFailure('persistence_unavailable', 'pending entity original V2 authorization is missing or invalid');
|
|
668
|
-
try {
|
|
669
|
-
if (typeof authorization !== 'string' || !/^[A-Za-z0-9_-]+={0,2}$/.test(authorization) || authorization.includes('=') && authorization.length % 4 !== 0)
|
|
670
|
-
throw invalid;
|
|
671
|
-
const raw = Buffer.from(authorization, 'base64url');
|
|
672
|
-
if (raw.toString('base64url') !== authorization.replace(/=+$/, ''))
|
|
673
|
-
throw invalid;
|
|
674
|
-
const text = new TextDecoder('utf-8', { fatal: true }).decode(raw);
|
|
675
|
-
const value = JSON.parse(text);
|
|
676
|
-
const lexical = parseDocument(text, { uniqueKeys: true });
|
|
677
|
-
if (lexical.errors.length || lexical.get('version', true)?.source !== '2' || !/^[1-9][0-9]*$/.test(lexical.get('timestamp', true)?.source ?? ''))
|
|
678
|
-
throw invalid;
|
|
679
|
-
const fields = ['version', 'target_aid', 'uploader_aid', 'body_sha256', 'entity_cert_fingerprint', 'timestamp', 'nonce', 'cert_fingerprint', 'signature'];
|
|
680
|
-
if (!isJsonObject(value) || Object.keys(value).length !== fields.length || fields.some(field => !(field in value)) || value.version !== 2)
|
|
681
|
-
throw invalid;
|
|
682
|
-
const timestamp = value.timestamp, nonce = value.nonce;
|
|
683
|
-
if (typeof timestamp !== 'number' || !Number.isSafeInteger(timestamp) || timestamp <= 0 || typeof nonce !== 'string' || !nonce.trim() || Buffer.byteLength(nonce, 'utf8') > 128)
|
|
684
|
-
throw invalid;
|
|
685
|
-
if (value.target_aid !== target.toLowerCase() || value.uploader_aid !== owner.aid.toLowerCase() || value.body_sha256 !== crypto.createHash('sha256').update(content, 'utf-8').digest('hex') || value.entity_cert_fingerprint !== entityFingerprint || value.cert_fingerprint !== owner.certFingerprint || typeof value.signature !== 'string')
|
|
686
|
-
throw invalid;
|
|
687
|
-
const verified = owner.verify(`AUN-AGENTMD-OWNER-AUTH-V2;${value.uploader_aid};${value.target_aid};${value.body_sha256};${entityFingerprint};${timestamp};${nonce}`, value.signature);
|
|
688
|
-
if (!verified.ok || !verified.data?.valid)
|
|
689
|
-
throw invalid;
|
|
690
|
-
return timestamp;
|
|
691
|
-
}
|
|
692
|
-
catch {
|
|
693
|
-
throw invalid;
|
|
694
|
-
}
|
|
695
|
-
}
|
|
696
|
-
_ownerUploadAuthorization(target, signedContent, owner, entityFingerprint = '') {
|
|
833
|
+
_ownerUploadAuthorization(target, signedContent, owner) {
|
|
697
834
|
const bodySha256 = crypto.createHash('sha256').update(signedContent, 'utf-8').digest('hex');
|
|
698
835
|
const timestamp = Math.floor(Date.now() / 1000);
|
|
699
836
|
const nonce = crypto.randomBytes(16).toString('hex');
|
|
700
|
-
const canonical =
|
|
837
|
+
const canonical = `AUN-AGENT-MD-UPLOAD-V1;${owner.aid.toLowerCase()};${target.toLowerCase()};${bodySha256};${timestamp};${nonce}`;
|
|
701
838
|
const signed = owner.sign(canonical);
|
|
702
839
|
if (!signed.ok || !signed.data) {
|
|
703
840
|
const message = signed.error?.message ?? 'owner authorization signing failed';
|
|
704
841
|
throw new ClientSignatureError(message);
|
|
705
842
|
}
|
|
706
843
|
const payload = {
|
|
707
|
-
version:
|
|
844
|
+
version: 1,
|
|
708
845
|
target_aid: target.toLowerCase(),
|
|
709
846
|
uploader_aid: owner.aid.toLowerCase(),
|
|
710
847
|
body_sha256: bodySha256,
|
|
711
|
-
...(entityFingerprint ? { entity_cert_fingerprint: entityFingerprint } : {}),
|
|
712
848
|
timestamp,
|
|
713
849
|
nonce,
|
|
714
850
|
cert_fingerprint: owner.certFingerprint,
|
|
@@ -721,13 +857,14 @@ export class AgentMdManager {
|
|
|
721
857
|
throw ownershipFailure('persistence_unavailable', 'agent.md upload requires its original document condition');
|
|
722
858
|
const gatewayUrl = await this._resolveGateway(target);
|
|
723
859
|
const authorization = String(ownerAuthorization ?? '').trim();
|
|
724
|
-
const
|
|
860
|
+
const meta = validateAgentMdDocument(signedContent, { expectedAid: target, validateOwnership: false }).frontmatter;
|
|
861
|
+
const entity = ['group', 'org'].includes(String(meta.type));
|
|
725
862
|
const uploadHeaders = {
|
|
863
|
+
'X-AUN-Signature-Format': 'multi-v1',
|
|
726
864
|
'Content-Type': 'text/markdown; charset=utf-8',
|
|
727
|
-
...(!entity ? { 'X-AUN-Ownership-Check': '1' } : {}),
|
|
728
865
|
...(condition.if_match ? { 'If-Match': condition.if_match } : { 'If-None-Match': '*' }),
|
|
729
866
|
...(this._uploadViaGatewayHost ? { Host: target } : {}),
|
|
730
|
-
...(authorization
|
|
867
|
+
...(authorization && !entity ? { 'X-AUN-AgentMD-Owner-Authorization': authorization } : {}),
|
|
731
868
|
};
|
|
732
869
|
if (!authorization || entity) {
|
|
733
870
|
const token = await this._uploadAccessToken(entity ? this._ownerAid() : target, gatewayUrl);
|
|
@@ -985,14 +1122,14 @@ export class AgentMdManager {
|
|
|
985
1122
|
return String(await this._gatewayResolver?.(aid) ?? '');
|
|
986
1123
|
}
|
|
987
1124
|
async _resolvePeer(aid, certFingerprint, signedAt) {
|
|
988
|
-
const expectedFp =
|
|
1125
|
+
const expectedFp = certFingerprint ? `sha256:${normalizeFingerprintHex(certFingerprint)}` : '';
|
|
989
1126
|
const current = this._currentAid();
|
|
990
|
-
if (current && current.aid.toLowerCase() === aid.toLowerCase()) {
|
|
991
|
-
if (!expectedFp || certMatchesFingerprint(current.certPem, expectedFp))
|
|
1127
|
+
if (signedAt === undefined && current && current.aid.toLowerCase() === aid.toLowerCase()) {
|
|
1128
|
+
if (!expectedFp || certMatchesFingerprint(current.certPem, expectedFp) || publicKeyMatchesFingerprint(current.certPem, expectedFp))
|
|
992
1129
|
return current;
|
|
993
1130
|
}
|
|
994
1131
|
const peer = await this._peerResolver?.(aid, expectedFp || null, signedAt);
|
|
995
|
-
if (!(peer instanceof AID)) {
|
|
1132
|
+
if (!(peer instanceof AID) || peer.aid.toLowerCase() !== aid.toLowerCase()) {
|
|
996
1133
|
throw new StateError(`agent.md peer resolver did not return AID for ${aid}`);
|
|
997
1134
|
}
|
|
998
1135
|
return peer;
|
|
@@ -1016,7 +1153,7 @@ export class AgentMdManager {
|
|
|
1016
1153
|
const response = await requestText(this._url(target, gatewayUrl), {
|
|
1017
1154
|
method: 'HEAD',
|
|
1018
1155
|
verifySsl: this._verifySsl,
|
|
1019
|
-
headers: { Accept: 'text/markdown' },
|
|
1156
|
+
headers: { 'X-AUN-Signature-Format': 'multi-v1', Accept: 'text/markdown' },
|
|
1020
1157
|
timeoutMs: HEAD_HTTP_TIMEOUT_MS,
|
|
1021
1158
|
});
|
|
1022
1159
|
if (response.status === 404) {
|
|
@@ -1054,7 +1191,7 @@ export class AgentMdManager {
|
|
|
1054
1191
|
this._log.debug(`downloadAgentMd enter: aid=${target}`);
|
|
1055
1192
|
const gatewayUrl = await this._resolveGateway(target);
|
|
1056
1193
|
const cached = this.loadRecord(target) ?? {};
|
|
1057
|
-
const headers = { Accept: 'text/markdown' };
|
|
1194
|
+
const headers = { 'X-AUN-Signature-Format': 'multi-v1', Accept: 'text/markdown' };
|
|
1058
1195
|
const etag = String(cached.remote_etag ?? cached.etag ?? cached.local_etag ?? '').trim();
|
|
1059
1196
|
const lastModified = String(cached.last_modified ?? '').trim();
|
|
1060
1197
|
let response = await requestText(this._url(target, gatewayUrl), {
|
|
@@ -1062,6 +1199,7 @@ export class AgentMdManager {
|
|
|
1062
1199
|
verifySsl: this._verifySsl,
|
|
1063
1200
|
headers,
|
|
1064
1201
|
timeoutMs,
|
|
1202
|
+
redirectsLeft: 0,
|
|
1065
1203
|
strictUtf8: true,
|
|
1066
1204
|
});
|
|
1067
1205
|
let reusedCachedNotModified = false;
|
|
@@ -1081,8 +1219,9 @@ export class AgentMdManager {
|
|
|
1081
1219
|
response = await requestText(this._url(target, gatewayUrl), {
|
|
1082
1220
|
method: 'GET',
|
|
1083
1221
|
verifySsl: this._verifySsl,
|
|
1084
|
-
headers: { Accept: 'text/markdown' },
|
|
1222
|
+
headers: { 'X-AUN-Signature-Format': 'multi-v1', Accept: 'text/markdown' },
|
|
1085
1223
|
timeoutMs,
|
|
1224
|
+
redirectsLeft: 0,
|
|
1086
1225
|
strictUtf8: true,
|
|
1087
1226
|
});
|
|
1088
1227
|
}
|
|
@@ -1095,29 +1234,24 @@ export class AgentMdManager {
|
|
|
1095
1234
|
throw new AUNError(`download agent.md failed: HTTP ${response.status}${message ? ` - ${message}` : ''}`);
|
|
1096
1235
|
}
|
|
1097
1236
|
const content = response.text;
|
|
1098
|
-
|
|
1099
|
-
throw ownershipFailure('conflict', 'entity agent.md is not from the current lifecycle generation');
|
|
1100
|
-
const parsed = parseAgentMdTailSignature(content);
|
|
1237
|
+
const parsed = parseAgentMdTailSignature(content, true);
|
|
1101
1238
|
let entity = false;
|
|
1102
1239
|
let frontmatter = {};
|
|
1103
1240
|
try {
|
|
1104
|
-
frontmatter =
|
|
1241
|
+
frontmatter = validateAgentMdReadDocument(content).frontmatter;
|
|
1105
1242
|
entity = ['group', 'org'].includes(String(frontmatter.type));
|
|
1106
1243
|
}
|
|
1107
1244
|
catch { }
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
throw ownershipFailure('invalid', error instanceof Error ? error.message : String(error));
|
|
1114
|
-
}
|
|
1245
|
+
try {
|
|
1246
|
+
frontmatter = validateAgentMdReadDocument(content, { expectedAid: target, requireSignature: entity }).frontmatter;
|
|
1247
|
+
}
|
|
1248
|
+
catch (error) {
|
|
1249
|
+
throw ownershipFailure('invalid', error instanceof Error ? error.message : String(error));
|
|
1115
1250
|
}
|
|
1116
|
-
if (entity)
|
|
1117
|
-
await this._entityCurrentContext(target, frontmatter);
|
|
1118
1251
|
const expectedFp = parsed.fields?.cert_fingerprint || parsed.fields?.public_key_fingerprint || null;
|
|
1119
|
-
const peer =
|
|
1120
|
-
const
|
|
1252
|
+
const peer = await this._resolvePeer(entity ? parsed.fields.signer_aid ?? target : target, expectedFp, parsed.fields?.timestamp ? Number(parsed.fields.timestamp) : undefined);
|
|
1253
|
+
const generic = await this._verifyDocument(content, target, peer);
|
|
1254
|
+
const verified = resultOk({ ...generic, aid: target, cert_fingerprint: parsed.fields?.cert_fingerprint, timestamp: Number(parsed.fields?.timestamp) });
|
|
1121
1255
|
if (!verified.ok || !verified.data) {
|
|
1122
1256
|
const message = verified.error?.message ?? 'agent.md verification failed';
|
|
1123
1257
|
throw new AUNError(message);
|
|
@@ -1128,8 +1262,6 @@ export class AgentMdManager {
|
|
|
1128
1262
|
if (statusText === 'invalid') {
|
|
1129
1263
|
throw new ClientSignatureError(`agent.md verification failed for ${target}${reason ? `: ${reason}` : ''}`, entity ? { stringCode: 'invalid' } : {});
|
|
1130
1264
|
}
|
|
1131
|
-
if (!entity)
|
|
1132
|
-
frontmatter = validateAgentMdDocument(content, { expectedAid: target, validateOwnership: false }).frontmatter;
|
|
1133
1265
|
let ownershipStatus;
|
|
1134
1266
|
if (entity)
|
|
1135
1267
|
ownershipStatus = 'not_applicable';
|
|
@@ -1180,6 +1312,7 @@ export class AgentMdManager {
|
|
|
1180
1312
|
return {
|
|
1181
1313
|
aid: target,
|
|
1182
1314
|
content,
|
|
1315
|
+
frontmatter,
|
|
1183
1316
|
verification,
|
|
1184
1317
|
signature,
|
|
1185
1318
|
cert_pem: peer.certPem,
|
|
@@ -1328,14 +1461,17 @@ export class AgentMdManager {
|
|
|
1328
1461
|
payload.aid = this.safeAid(aid);
|
|
1329
1462
|
this._atomicWriteText(this.metaPath(aid), `${JSON.stringify(payload, null, 2)}\n`);
|
|
1330
1463
|
}
|
|
1331
|
-
_readRecordUnlocked(aid) {
|
|
1464
|
+
_readRecordUnlocked(aid, strict = false) {
|
|
1332
1465
|
const filePath = this.metaPath(aid);
|
|
1333
1466
|
if (!fs.existsSync(filePath))
|
|
1334
1467
|
return {};
|
|
1335
1468
|
try {
|
|
1336
1469
|
const parsed = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
1337
|
-
if (!isJsonObject(parsed))
|
|
1470
|
+
if (!isJsonObject(parsed)) {
|
|
1471
|
+
if (strict)
|
|
1472
|
+
throw ownershipFailure('persistence_unavailable', `agent.md metadata is not a mapping: aid=${aid}`);
|
|
1338
1473
|
return {};
|
|
1474
|
+
}
|
|
1339
1475
|
const record = {};
|
|
1340
1476
|
for (const [key, value] of Object.entries(parsed)) {
|
|
1341
1477
|
if (key !== 'content')
|
|
@@ -1348,6 +1484,11 @@ export class AgentMdManager {
|
|
|
1348
1484
|
return record;
|
|
1349
1485
|
}
|
|
1350
1486
|
catch (err) {
|
|
1487
|
+
if (strict) {
|
|
1488
|
+
if (err instanceof AUNError && err.stringCode === 'persistence_unavailable')
|
|
1489
|
+
throw err;
|
|
1490
|
+
throw ownershipFailure('persistence_unavailable', `agent.md metadata is damaged: aid=${aid}`);
|
|
1491
|
+
}
|
|
1351
1492
|
this._log.warn(`agent.md metadata damaged, ignoring: aid=${aid} err=${err instanceof Error ? err.message : String(err)}`);
|
|
1352
1493
|
return {};
|
|
1353
1494
|
}
|