@aikdna/kdna-core 0.4.0 → 0.6.0

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.
@@ -0,0 +1,619 @@
1
+ /**
2
+ * KDNA Asset Reader — direct .kdna container access.
3
+ *
4
+ * This module intentionally uses only Node.js built-ins. It reads ZIP central
5
+ * directory records directly so runtimes can inspect, verify, and load .kdna
6
+ * assets without persistent extraction to a domain directory.
7
+ */
8
+
9
+ const fs = require('fs');
10
+ const crypto = require('crypto');
11
+ const zlib = require('zlib');
12
+ const { loadDomainFromFiles, formatContext } = require('./loader');
13
+
14
+ const STANDARD_ENTRIES = [
15
+ 'kdna.json',
16
+ 'KDNA_Core.json',
17
+ 'KDNA_Patterns.json',
18
+ 'KDNA_Scenarios.json',
19
+ 'KDNA_Cases.json',
20
+ 'KDNA_Reasoning.json',
21
+ 'KDNA_Evolution.json',
22
+ ];
23
+
24
+ const JSON_ENTRY_RE = /\.json$/i;
25
+ const KDNA_MEDIA_TYPE = 'application/vnd.aikdna.kdna+zip';
26
+
27
+ function sha256Hex(buf) {
28
+ return crypto.createHash('sha256').update(buf).digest('hex');
29
+ }
30
+
31
+ function stableStringify(value) {
32
+ if (Array.isArray(value)) return `[${value.map(stableStringify).join(',')}]`;
33
+ if (value && typeof value === 'object') {
34
+ return `{${Object.keys(value)
35
+ .sort()
36
+ .map((key) => `${JSON.stringify(key)}:${stableStringify(value[key])}`)
37
+ .join(',')}}`;
38
+ }
39
+ return JSON.stringify(value);
40
+ }
41
+
42
+ function parseJson(buf, entryName) {
43
+ try {
44
+ return JSON.parse(Buffer.isBuffer(buf) ? buf.toString('utf8') : String(buf));
45
+ } catch (e) {
46
+ throw new Error(`${entryName}: invalid JSON: ${e.message}`);
47
+ }
48
+ }
49
+
50
+ function encryptedEntries(manifest) {
51
+ const entries = manifest?.encryption?.encrypted_entries;
52
+ return Array.isArray(entries) ? entries : [];
53
+ }
54
+
55
+ function isEncryptedEntry(manifest, entryName) {
56
+ return encryptedEntries(manifest).includes(entryName);
57
+ }
58
+
59
+ async function maybeDecryptEntry(asset, manifest, entryName, buf, options = {}) {
60
+ if (!isEncryptedEntry(manifest, entryName)) return buf;
61
+ if (typeof options.decryptEntry !== 'function') {
62
+ throw new Error(`${entryName}: encrypted entry requires decryptEntry hook`);
63
+ }
64
+ const decrypted = await options.decryptEntry({
65
+ asset,
66
+ manifest,
67
+ entryName,
68
+ ciphertext: buf,
69
+ });
70
+ if (typeof decrypted === 'string') return Buffer.from(decrypted);
71
+ if (Buffer.isBuffer(decrypted)) return decrypted;
72
+ if (decrypted instanceof Uint8Array) return Buffer.from(decrypted);
73
+ throw new Error(`${entryName}: decryptEntry hook must return string, Buffer, or Uint8Array`);
74
+ }
75
+
76
+ function normalizeDecryptedEntry(decrypted, entryName) {
77
+ if (typeof decrypted === 'string') return Buffer.from(decrypted);
78
+ if (Buffer.isBuffer(decrypted)) return decrypted;
79
+ if (decrypted instanceof Uint8Array) return Buffer.from(decrypted);
80
+ throw new Error(`${entryName}: decryptEntry hook must return string, Buffer, or Uint8Array`);
81
+ }
82
+
83
+ function maybeDecryptEntrySync(asset, manifest, entryName, buf, options = {}) {
84
+ if (!isEncryptedEntry(manifest, entryName)) return buf;
85
+ if (typeof options.decryptEntry !== 'function') {
86
+ throw new Error(`${entryName}: encrypted entry requires decryptEntry hook`);
87
+ }
88
+ const decrypted = options.decryptEntry({
89
+ asset,
90
+ manifest,
91
+ entryName,
92
+ ciphertext: buf,
93
+ });
94
+ if (decrypted && typeof decrypted.then === 'function') {
95
+ throw new Error(`${entryName}: decryptEntry hook must be synchronous for sync reads`);
96
+ }
97
+ return normalizeDecryptedEntry(decrypted, entryName);
98
+ }
99
+
100
+ function findEndOfCentralDirectory(buf) {
101
+ const min = Math.max(0, buf.length - 65557);
102
+ for (let i = buf.length - 22; i >= min; i--) {
103
+ if (buf.readUInt32LE(i) === 0x06054b50) return i;
104
+ }
105
+ throw new Error('Invalid .kdna asset: ZIP end-of-central-directory not found');
106
+ }
107
+
108
+ function parseZipEntries(buf) {
109
+ const eocd = findEndOfCentralDirectory(buf);
110
+ const totalEntries = buf.readUInt16LE(eocd + 10);
111
+ const centralDirOffset = buf.readUInt32LE(eocd + 16);
112
+ const entries = new Map();
113
+ let offset = centralDirOffset;
114
+
115
+ for (let i = 0; i < totalEntries; i++) {
116
+ if (buf.readUInt32LE(offset) !== 0x02014b50) {
117
+ throw new Error(`Invalid .kdna asset: ZIP central directory is corrupt at ${offset}`);
118
+ }
119
+
120
+ const method = buf.readUInt16LE(offset + 10);
121
+ const compressedSize = buf.readUInt32LE(offset + 20);
122
+ const uncompressedSize = buf.readUInt32LE(offset + 24);
123
+ const nameLen = buf.readUInt16LE(offset + 28);
124
+ const extraLen = buf.readUInt16LE(offset + 30);
125
+ const commentLen = buf.readUInt16LE(offset + 32);
126
+ const localHeaderOffset = buf.readUInt32LE(offset + 42);
127
+ const name = buf.slice(offset + 46, offset + 46 + nameLen).toString('utf8');
128
+
129
+ offset += 46 + nameLen + extraLen + commentLen;
130
+ if (!name || name.endsWith('/')) continue;
131
+
132
+ entries.set(name, {
133
+ name,
134
+ method,
135
+ compressedSize,
136
+ uncompressedSize,
137
+ localHeaderOffset,
138
+ });
139
+ }
140
+
141
+ return entries;
142
+ }
143
+
144
+ function readZipEntry(buf, entry) {
145
+ const offset = entry.localHeaderOffset;
146
+ if (buf.readUInt32LE(offset) !== 0x04034b50) {
147
+ throw new Error(`Invalid .kdna asset: local header missing for ${entry.name}`);
148
+ }
149
+
150
+ const nameLen = buf.readUInt16LE(offset + 26);
151
+ const extraLen = buf.readUInt16LE(offset + 28);
152
+ const dataStart = offset + 30 + nameLen + extraLen;
153
+ const compressed = buf.slice(dataStart, dataStart + entry.compressedSize);
154
+
155
+ if (entry.method === 0) return compressed;
156
+ if (entry.method === 8) return zlib.inflateRawSync(compressed);
157
+ throw new Error(`${entry.name}: unsupported ZIP compression method ${entry.method}`);
158
+ }
159
+
160
+ function normalizeInput(input) {
161
+ if (Buffer.isBuffer(input)) return { buffer: input, path: null };
162
+ if (input instanceof Uint8Array) return { buffer: Buffer.from(input), path: null };
163
+ if (typeof input !== 'string') {
164
+ throw new Error('KdnaAssetReader.open expects a file path, Buffer, or Uint8Array');
165
+ }
166
+ return { buffer: fs.readFileSync(input), path: input };
167
+ }
168
+
169
+ function manifestForDigest(manifest) {
170
+ const copy = { ...(manifest || {}) };
171
+ delete copy.signature;
172
+ delete copy.asset_digest;
173
+ delete copy.container_sha256;
174
+ delete copy.content_digest;
175
+ delete copy._source;
176
+ return copy;
177
+ }
178
+
179
+ function buildContentDigest(asset) {
180
+ const parts = [];
181
+ for (const entryName of [...asset.entries.keys()].sort()) {
182
+ if (entryName === '.DS_Store' || entryName === 'signature.json') continue;
183
+ const entryBuf = asset.readEntry(entryName);
184
+ let digestBuf = entryBuf;
185
+ if (JSON_ENTRY_RE.test(entryName)) {
186
+ const parsed = parseJson(entryBuf, entryName);
187
+ const value = entryName === 'kdna.json' ? manifestForDigest(parsed) : parsed;
188
+ digestBuf = Buffer.from(stableStringify(value));
189
+ }
190
+ parts.push(`${entryName}:${sha256Hex(digestBuf)}`);
191
+ }
192
+ return `sha256:${sha256Hex(Buffer.from(parts.join('\n')))}`;
193
+ }
194
+
195
+ function manifestForSignature(manifest, { stripDigestFields = true } = {}) {
196
+ const copy = { ...(manifest || {}) };
197
+ delete copy.signature;
198
+ delete copy._source;
199
+ if (stripDigestFields) {
200
+ delete copy.asset_digest;
201
+ delete copy.container_sha256;
202
+ delete copy.content_digest;
203
+ }
204
+ return copy;
205
+ }
206
+
207
+ function canonicalJsonEntry(entryName, entryBuf, options = {}) {
208
+ const parsed = parseJson(entryBuf, entryName);
209
+ const value = entryName === 'kdna.json' ? manifestForSignature(parsed, options) : parsed;
210
+ return Buffer.from(stableStringify(value));
211
+ }
212
+
213
+ function buildSigningPayload(asset, options = {}) {
214
+ const parts = [];
215
+ for (const entryName of [...asset.entries.keys()].sort()) {
216
+ if (entryName === '.DS_Store' || entryName === 'signature.json') continue;
217
+ const entryBuf = asset.readEntry(entryName);
218
+ let payloadBuf = entryBuf;
219
+ if (JSON_ENTRY_RE.test(entryName)) {
220
+ payloadBuf = canonicalJsonEntry(entryName, entryBuf, options);
221
+ }
222
+ parts.push(`${entryName}:${sha256Hex(payloadBuf)}`);
223
+ }
224
+ return parts.join('\n');
225
+ }
226
+
227
+ function verifySignature(asset, manifest, errors, warnings) {
228
+ if (!manifest.signature) {
229
+ warnings.push('kdna.json.signature missing');
230
+ return null;
231
+ }
232
+ if (!manifest.author?.public_key_pem) {
233
+ errors.push('kdna.json.author.public_key_pem missing');
234
+ return false;
235
+ }
236
+ if (!manifest.author?.pubkey) {
237
+ errors.push('kdna.json.author.pubkey missing');
238
+ return false;
239
+ }
240
+
241
+ const fingerprint = `ed25519:${sha256Hex(Buffer.from(manifest.author.public_key_pem))}`;
242
+ if (fingerprint !== manifest.author.pubkey) {
243
+ errors.push('author.public_key_pem fingerprint does not match author.pubkey');
244
+ return false;
245
+ }
246
+
247
+ try {
248
+ const signature = Buffer.from(String(manifest.signature).replace(/^ed25519:/, ''), 'hex');
249
+ const publicKey = crypto.createPublicKey(manifest.author.public_key_pem);
250
+ const ok = crypto.verify(null, Buffer.from(buildSigningPayload(asset)), publicKey, signature);
251
+ if (!ok) errors.push('Ed25519 signature invalid');
252
+ return ok;
253
+ } catch (e) {
254
+ errors.push(`signature verification failed: ${e.message}`);
255
+ return false;
256
+ }
257
+ }
258
+
259
+ function verifyMediaType(asset, errors) {
260
+ if (!asset.entries.has('mimetype')) {
261
+ errors.push('required entry missing: mimetype');
262
+ return;
263
+ }
264
+ const value = asset.readEntry('mimetype').toString('utf8');
265
+ if (value !== KDNA_MEDIA_TYPE) {
266
+ errors.push(`mimetype: expected ${KDNA_MEDIA_TYPE}, got ${JSON.stringify(value)}`);
267
+ }
268
+ }
269
+
270
+ function validateManifestIdentity(manifest, errors, _warnings) {
271
+ if (manifest.kdna_spec) {
272
+ errors.push('kdna.json: kdna_spec is not allowed. Use spec_version.');
273
+ }
274
+ if (manifest.format && manifest.format !== 'kdna') {
275
+ errors.push(`kdna.json.format: invalid value "${manifest.format}". Expected "kdna".`);
276
+ }
277
+ if (manifest.format_version && manifest.format_version !== '1.0') {
278
+ errors.push(
279
+ `kdna.json.format_version: invalid value "${manifest.format_version}". Expected "1.0".`,
280
+ );
281
+ }
282
+ if (!manifest.spec_version) errors.push('kdna.json: missing required field "spec_version"');
283
+ if (!manifest.format) errors.push('kdna.json: missing required field "format"');
284
+ if (!manifest.format_version) errors.push('kdna.json: missing required field "format_version"');
285
+ if (manifest.language) {
286
+ errors.push('kdna.json: language is not allowed. Use default_language and languages.');
287
+ }
288
+ }
289
+
290
+ function openAsset(input) {
291
+ const { buffer, path } = normalizeInput(input);
292
+ const entries = parseZipEntries(buffer);
293
+ return {
294
+ path,
295
+ size: buffer.length,
296
+ asset_digest: `sha256:${sha256Hex(buffer)}`,
297
+ entries,
298
+ readEntry(name) {
299
+ const entry = entries.get(name);
300
+ if (!entry) throw new Error(`Entry not found in .kdna asset: ${name}`);
301
+ return readZipEntry(buffer, entry);
302
+ },
303
+ };
304
+ }
305
+
306
+ function listEntries(asset) {
307
+ return [...asset.entries.keys()].sort();
308
+ }
309
+
310
+ function readEntry(asset, entryName, encoding) {
311
+ const buf = asset.readEntry(entryName);
312
+ return encoding ? buf.toString(encoding) : buf;
313
+ }
314
+
315
+ function readManifest(asset) {
316
+ return parseJson(asset.readEntry('kdna.json'), 'kdna.json');
317
+ }
318
+
319
+ function readDataMapSync(asset, entries = STANDARD_ENTRIES, options = {}) {
320
+ const dataMap = {};
321
+ const manifest = readManifest(asset);
322
+ const encrypted = encryptedEntries(manifest).filter((entryName) => entries.includes(entryName));
323
+ if (encrypted.length && typeof options.decryptEntry !== 'function') {
324
+ throw new Error(`encrypted entries require decryptEntry hook: ${encrypted.join(', ')}`);
325
+ }
326
+ for (const entryName of entries) {
327
+ if (!asset.entries.has(entryName)) continue;
328
+ const buf = maybeDecryptEntrySync(asset, manifest, entryName, asset.readEntry(entryName), options);
329
+ dataMap[entryName] = parseJson(buf, entryName);
330
+ }
331
+ return dataMap;
332
+ }
333
+
334
+ function verifySync(asset, options = {}) {
335
+ const errors = [];
336
+ const warnings = [];
337
+ const entries = listEntries(asset);
338
+
339
+ if (!asset.entries.has('kdna.json')) errors.push('required entry missing: kdna.json');
340
+ verifyMediaType(asset, errors);
341
+ if (!asset.entries.has('KDNA_Core.json')) errors.push('required entry missing: KDNA_Core.json');
342
+ if (!asset.entries.has('KDNA_Patterns.json')) {
343
+ errors.push('required entry missing: KDNA_Patterns.json');
344
+ }
345
+
346
+ const content_digest = buildContentDigest(asset);
347
+ const asset_digest = asset.asset_digest;
348
+ if (options.asset_digest && options.asset_digest !== asset_digest) {
349
+ errors.push(`asset digest mismatch: expected ${options.asset_digest}, got ${asset_digest}`);
350
+ }
351
+ if (options.content_digest && options.content_digest !== content_digest) {
352
+ errors.push(`content digest mismatch: expected ${options.content_digest}, got ${content_digest}`);
353
+ }
354
+
355
+ let manifest = null;
356
+ let signature_valid = null;
357
+ if (asset.entries.has('kdna.json')) {
358
+ try {
359
+ manifest = readManifest(asset);
360
+ validateManifestIdentity(manifest, errors, warnings);
361
+ const encrypted = encryptedEntries(manifest);
362
+ if (encrypted.length) {
363
+ warnings.push(`encrypted entries present: ${encrypted.join(', ')}`);
364
+ if (options.requireDecryption && typeof options.decryptEntry !== 'function') {
365
+ errors.push('decryptEntry hook required for encrypted entries');
366
+ }
367
+ if (typeof options.decryptEntry === 'function') {
368
+ for (const entryName of encrypted) {
369
+ if (!asset.entries.has(entryName)) {
370
+ errors.push(`encrypted entry listed but missing: ${entryName}`);
371
+ continue;
372
+ }
373
+ try {
374
+ const decrypted = maybeDecryptEntrySync(
375
+ asset,
376
+ manifest,
377
+ entryName,
378
+ asset.readEntry(entryName),
379
+ options,
380
+ );
381
+ parseJson(decrypted, entryName);
382
+ } catch (e) {
383
+ errors.push(e.message);
384
+ }
385
+ }
386
+ }
387
+ }
388
+ if (options.requireSignature || manifest.signature) {
389
+ signature_valid = verifySignature(asset, manifest, errors, warnings);
390
+ }
391
+ } catch (e) {
392
+ errors.push(e.message);
393
+ }
394
+ }
395
+
396
+ return {
397
+ ok: errors.length === 0,
398
+ errors,
399
+ warnings,
400
+ entries,
401
+ manifest,
402
+ asset_digest,
403
+ content_digest,
404
+ signature_valid,
405
+ };
406
+ }
407
+
408
+ function loadProfileSync(asset, profile = 'compact', options = {}) {
409
+ const manifest = readManifest(asset);
410
+ if (profile === 'index') {
411
+ return {
412
+ profile,
413
+ manifest,
414
+ asset_digest: asset.asset_digest,
415
+ content_digest: buildContentDigest(asset),
416
+ entries: listEntries(asset),
417
+ name: manifest.name || manifest.asset_id || null,
418
+ version: manifest.version || null,
419
+ judgment_version: manifest.judgment_version || null,
420
+ keywords: manifest.keywords || [],
421
+ };
422
+ }
423
+ const dataMap = readDataMapSync(asset, STANDARD_ENTRIES, options);
424
+ const mode = profile === 'full' ? 'all' : profile === 'scenario' ? 'auto' : 'minimum';
425
+ const domain = loadDomainFromFiles(dataMap, { mode, input: options.input || '' });
426
+ return {
427
+ profile,
428
+ manifest,
429
+ domain,
430
+ context: options.context === false || !domain ? null : formatContext(domain),
431
+ };
432
+ }
433
+
434
+ function createKdnaAssetReader() {
435
+ return {
436
+ openSync: openAsset,
437
+
438
+ async open(input) {
439
+ return openAsset(input);
440
+ },
441
+
442
+ listEntriesSync: listEntries,
443
+
444
+ async listEntries(asset) {
445
+ return listEntries(asset);
446
+ },
447
+
448
+ readEntrySync: readEntry,
449
+
450
+ async readEntry(asset, entryName, encoding) {
451
+ return readEntry(asset, entryName, encoding);
452
+ },
453
+
454
+ readJsonSync(asset, entryName, options = {}) {
455
+ if (!asset.entries.has(entryName)) return null;
456
+ const manifest =
457
+ entryName === 'kdna.json' ? null : parseJson(asset.readEntry('kdna.json'), 'kdna.json');
458
+ const buf = maybeDecryptEntrySync(asset, manifest, entryName, asset.readEntry(entryName), options);
459
+ return parseJson(buf, entryName);
460
+ },
461
+
462
+ async readJson(asset, entryName, options = {}) {
463
+ if (!asset.entries.has(entryName)) return null;
464
+ const manifest =
465
+ entryName === 'kdna.json' ? null : parseJson(asset.readEntry('kdna.json'), 'kdna.json');
466
+ const buf = await maybeDecryptEntry(
467
+ asset,
468
+ manifest,
469
+ entryName,
470
+ asset.readEntry(entryName),
471
+ options,
472
+ );
473
+ return parseJson(buf, entryName);
474
+ },
475
+
476
+ readManifestSync: readManifest,
477
+
478
+ async readManifest(asset) {
479
+ return readManifest(asset);
480
+ },
481
+
482
+ readDataMapSync,
483
+
484
+ async readDataMap(asset, entries = STANDARD_ENTRIES, options = {}) {
485
+ const dataMap = {};
486
+ const manifest = await this.readManifest(asset);
487
+ for (const entryName of entries) {
488
+ if (!asset.entries.has(entryName)) continue;
489
+ const buf = await maybeDecryptEntry(
490
+ asset,
491
+ manifest,
492
+ entryName,
493
+ asset.readEntry(entryName),
494
+ options,
495
+ );
496
+ dataMap[entryName] = parseJson(buf, entryName);
497
+ }
498
+ return dataMap;
499
+ },
500
+
501
+ contentDigestSync: buildContentDigest,
502
+
503
+ async contentDigest(asset) {
504
+ return buildContentDigest(asset);
505
+ },
506
+
507
+ verifySync,
508
+
509
+ async verify(asset, options = {}) {
510
+ const errors = [];
511
+ const warnings = [];
512
+ const entries = [...asset.entries.keys()].sort();
513
+
514
+ if (!asset.entries.has('kdna.json')) errors.push('required entry missing: kdna.json');
515
+ verifyMediaType(asset, errors);
516
+ if (!asset.entries.has('KDNA_Core.json')) errors.push('required entry missing: KDNA_Core.json');
517
+ if (!asset.entries.has('KDNA_Patterns.json')) {
518
+ errors.push('required entry missing: KDNA_Patterns.json');
519
+ }
520
+
521
+ const content_digest = buildContentDigest(asset);
522
+ const asset_digest = asset.asset_digest;
523
+ if (options.asset_digest && options.asset_digest !== asset_digest) {
524
+ errors.push(`asset digest mismatch: expected ${options.asset_digest}, got ${asset_digest}`);
525
+ }
526
+ if (options.content_digest && options.content_digest !== content_digest) {
527
+ errors.push(
528
+ `content digest mismatch: expected ${options.content_digest}, got ${content_digest}`,
529
+ );
530
+ }
531
+
532
+ let manifest = null;
533
+ let signature_valid = null;
534
+ if (asset.entries.has('kdna.json')) {
535
+ try {
536
+ manifest = parseJson(asset.readEntry('kdna.json'), 'kdna.json');
537
+ validateManifestIdentity(manifest, errors, warnings);
538
+ const encrypted = encryptedEntries(manifest);
539
+ if (encrypted.length) {
540
+ warnings.push(`encrypted entries present: ${encrypted.join(', ')}`);
541
+ if (options.requireDecryption && typeof options.decryptEntry !== 'function') {
542
+ errors.push('decryptEntry hook required for encrypted entries');
543
+ }
544
+ if (typeof options.decryptEntry === 'function') {
545
+ for (const entryName of encrypted) {
546
+ if (!asset.entries.has(entryName)) {
547
+ errors.push(`encrypted entry listed but missing: ${entryName}`);
548
+ continue;
549
+ }
550
+ try {
551
+ const decrypted = await maybeDecryptEntry(
552
+ asset,
553
+ manifest,
554
+ entryName,
555
+ asset.readEntry(entryName),
556
+ options,
557
+ );
558
+ parseJson(decrypted, entryName);
559
+ } catch (e) {
560
+ errors.push(e.message);
561
+ }
562
+ }
563
+ }
564
+ }
565
+ if (options.requireSignature || manifest.signature) {
566
+ signature_valid = verifySignature(asset, manifest, errors, warnings);
567
+ }
568
+ } catch (e) {
569
+ errors.push(e.message);
570
+ }
571
+ }
572
+
573
+ return {
574
+ ok: errors.length === 0,
575
+ errors,
576
+ warnings,
577
+ entries,
578
+ manifest,
579
+ asset_digest,
580
+ content_digest,
581
+ signature_valid,
582
+ };
583
+ },
584
+
585
+ loadProfileSync,
586
+
587
+ async loadProfile(asset, profile = 'compact', options = {}) {
588
+ const manifest = await this.readManifest(asset);
589
+ if (profile === 'index') {
590
+ return {
591
+ profile,
592
+ manifest,
593
+ asset_digest: asset.asset_digest,
594
+ content_digest: buildContentDigest(asset),
595
+ entries: await this.listEntries(asset),
596
+ name: manifest.name || manifest.asset_id || null,
597
+ version: manifest.version || null,
598
+ judgment_version: manifest.judgment_version || null,
599
+ keywords: manifest.keywords || [],
600
+ };
601
+ }
602
+
603
+ const dataMap = await this.readDataMap(asset, STANDARD_ENTRIES, options);
604
+ const mode = profile === 'full' ? 'all' : profile === 'scenario' ? 'auto' : 'minimum';
605
+ const domain = loadDomainFromFiles(dataMap, { mode, input: options.input || '' });
606
+ return {
607
+ profile,
608
+ manifest,
609
+ domain,
610
+ context: options.context === false || !domain ? null : formatContext(domain),
611
+ };
612
+ },
613
+ };
614
+ }
615
+
616
+ module.exports = {
617
+ STANDARD_ENTRIES,
618
+ createKdnaAssetReader,
619
+ };