@aikdna/kdna-core 0.17.0 → 0.19.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.
- package/CHANGELOG.md +227 -64
- package/README.md +114 -77
- package/package.json +8 -2
- package/schema/agent-host-capabilities.schema.json +62 -0
- package/schema/agent-host-receipt.schema.json +371 -0
- package/schema/agent-host-request.schema.json +223 -0
- package/schema/{bundle-profile-v1.schema.json → bundle-profile.schema.json} +9 -5
- package/schema/checksums.schema.json +42 -3
- package/schema/consumption-plan.schema.json +428 -0
- package/schema/digest-evidence.schema.json +212 -0
- package/schema/{kdna-envelope-external-grant-v1.schema.json → external-grant-envelope.schema.json} +17 -4
- package/schema/{kdna-key-grant-v1.schema.json → external-key-grant.schema.json} +39 -7
- package/schema/judgment-trace.schema.json +1251 -0
- package/schema/load-plan.schema.json +7 -5
- package/schema/manifest.schema.json +70 -10
- package/schema/{payload-profile-v1.schema.json → payload-profile.schema.json} +30 -6
- package/schema/runtime-capsule.schema.json +116 -0
- package/src/asset-reader.js +106 -120
- package/src/{v1 → container}/index.js +566 -209
- package/src/container/index.mjs +20 -0
- package/src/container-dispatcher.js +10 -7
- package/src/crypto-profile.js +37 -107
- package/src/external-key-grant.js +41 -15
- package/src/index.js +11 -4
- package/src/index.mjs +143 -24
- package/src/loader-compatibility.js +59 -0
- package/src/loader.js +1 -1
- package/src/public-api.js +59 -163
- package/src/remote-runtime.d.ts +13 -0
- package/src/remote-runtime.js +27 -0
- package/src/remote-runtime.mjs +3 -0
- package/src/runtime-api.js +9 -10
- package/src/runtime-capsule.js +529 -0
- package/src/runtime-contract.js +1882 -0
- package/src/types.d.ts +1127 -136
- package/src/workpack-pure.js +1 -1
- package/src/lint-pure.js +0 -430
- package/src/v1/index.mjs +0 -20
package/src/asset-reader.js
CHANGED
|
@@ -6,15 +6,14 @@ const fs = require('fs');
|
|
|
6
6
|
const crypto = require('crypto');
|
|
7
7
|
const zlib = require('zlib');
|
|
8
8
|
const cbor = require('cbor-x');
|
|
9
|
-
const {
|
|
9
|
+
const { loaderVersionUnsupportedMessage } = require('./loader-compatibility');
|
|
10
10
|
|
|
11
11
|
const KDNA_MEDIA_TYPE = 'application/vnd.kdna.asset';
|
|
12
|
+
const ASSET_SOURCE_BYTES = Symbol.for('@aikdna/kdna-core.asset-source-bytes');
|
|
12
13
|
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
// add them here. KDNA_Cluster is intentionally omitted — its schema exists but
|
|
17
|
-
// is not yet wired into the loader contract.
|
|
14
|
+
// Deprecated legacy authoring source entry names. Current Runtime assets carry
|
|
15
|
+
// judgment only in payload.kdnab and never expose these as distribution entries.
|
|
16
|
+
// Kept for source-migration callers; loadProfile does not use this list.
|
|
18
17
|
const STANDARD_ENTRIES = Object.freeze([
|
|
19
18
|
'KDNA_Core.json',
|
|
20
19
|
'KDNA_Patterns.json',
|
|
@@ -43,6 +42,14 @@ function stableStringify(value) {
|
|
|
43
42
|
return JSON.stringify(value);
|
|
44
43
|
}
|
|
45
44
|
|
|
45
|
+
function compareEntryNamesByUtf8(left, right) {
|
|
46
|
+
return Buffer.compare(Buffer.from(left, 'utf8'), Buffer.from(right, 'utf8'));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function sortedEntryNames(entries) {
|
|
50
|
+
return [...entries.keys()].sort(compareEntryNamesByUtf8);
|
|
51
|
+
}
|
|
52
|
+
|
|
46
53
|
function parseJson(buf, entryName) {
|
|
47
54
|
try {
|
|
48
55
|
return JSON.parse(Buffer.isBuffer(buf) ? buf.toString('utf8') : String(buf));
|
|
@@ -51,6 +58,17 @@ function parseJson(buf, entryName) {
|
|
|
51
58
|
}
|
|
52
59
|
}
|
|
53
60
|
|
|
61
|
+
function validateDecodedEntry(buf, entryName) {
|
|
62
|
+
if (entryName === 'payload.kdnab') {
|
|
63
|
+
try {
|
|
64
|
+
return cbor.decode(buf);
|
|
65
|
+
} catch (e) {
|
|
66
|
+
throw new Error(`${entryName}: invalid CBOR: ${e.message}`, { cause: e });
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return parseJson(buf, entryName);
|
|
70
|
+
}
|
|
71
|
+
|
|
54
72
|
function encryptedEntries(manifest) {
|
|
55
73
|
const entries = manifest?.encryption?.encrypted_entries;
|
|
56
74
|
return Array.isArray(entries) ? entries : [];
|
|
@@ -188,7 +206,7 @@ function manifestForDigest(manifest) {
|
|
|
188
206
|
function buildContentDigest(asset) {
|
|
189
207
|
const parts = [];
|
|
190
208
|
const excluded = new Set(['.DS_Store', 'signature.json', 'build-receipt.json']);
|
|
191
|
-
for (const entryName of
|
|
209
|
+
for (const entryName of sortedEntryNames(asset.entries)) {
|
|
192
210
|
if (excluded.has(entryName)) continue;
|
|
193
211
|
if (entryName.startsWith('reports/')) continue;
|
|
194
212
|
const entryBuf = asset.readEntry(entryName);
|
|
@@ -243,7 +261,7 @@ function buildSigningPayload(asset, options = {}) {
|
|
|
243
261
|
// byte strings and the signature would fail to verify. The fix
|
|
244
262
|
// mirrors `buildContentDigest`'s exclusion set exactly.
|
|
245
263
|
const parts = [];
|
|
246
|
-
for (const entryName of
|
|
264
|
+
for (const entryName of sortedEntryNames(asset.entries)) {
|
|
247
265
|
if (entryName === '.DS_Store' || entryName === 'signature.json') continue;
|
|
248
266
|
if (entryName === 'build-receipt.json') continue;
|
|
249
267
|
if (entryName.startsWith('reports/')) continue;
|
|
@@ -324,7 +342,9 @@ function verifyHumanLockSignatures(coreData, manifest, errors, warnings) {
|
|
|
324
342
|
['stances', 'stance'],
|
|
325
343
|
];
|
|
326
344
|
|
|
327
|
-
let verified = 0,
|
|
345
|
+
let verified = 0,
|
|
346
|
+
missing = 0,
|
|
347
|
+
invalid = 0;
|
|
328
348
|
for (const [arrayName] of CORE_CARD_ARRAYS) {
|
|
329
349
|
const cards = coreData?.[arrayName];
|
|
330
350
|
if (!Array.isArray(cards) || cards.length === 0) continue;
|
|
@@ -342,8 +362,11 @@ function verifyHumanLockSignatures(coreData, manifest, errors, warnings) {
|
|
|
342
362
|
const sig = Buffer.from(String(hl.signature).replace(/^ed25519:/, ''), 'hex');
|
|
343
363
|
const key = crypto.createPublicKey(publicKeyPEM);
|
|
344
364
|
const ok = crypto.verify(null, Buffer.from(payload), key, sig);
|
|
345
|
-
if (ok) {
|
|
346
|
-
|
|
365
|
+
if (ok) {
|
|
366
|
+
verified++;
|
|
367
|
+
} else {
|
|
368
|
+
invalid++;
|
|
369
|
+
}
|
|
347
370
|
} catch {
|
|
348
371
|
invalid++;
|
|
349
372
|
}
|
|
@@ -351,7 +374,9 @@ function verifyHumanLockSignatures(coreData, manifest, errors, warnings) {
|
|
|
351
374
|
}
|
|
352
375
|
|
|
353
376
|
if (invalid > 0) {
|
|
354
|
-
errors.push(
|
|
377
|
+
errors.push(
|
|
378
|
+
`${invalid} Human Lock signature(s) failed verification — judgment may have been altered`,
|
|
379
|
+
);
|
|
355
380
|
}
|
|
356
381
|
if (missing > 0) {
|
|
357
382
|
warnings.push(`${missing} locked card(s) have no Human Lock signature`);
|
|
@@ -361,29 +386,10 @@ function verifyHumanLockSignatures(coreData, manifest, errors, warnings) {
|
|
|
361
386
|
}
|
|
362
387
|
}
|
|
363
388
|
|
|
364
|
-
function validateManifestIdentity(manifest, errors, _warnings) {
|
|
365
|
-
if (manifest.kdna_spec) {
|
|
366
|
-
errors.push('kdna.json: kdna_spec is not allowed. Use kdna_version.');
|
|
367
|
-
}
|
|
368
|
-
if (manifest.format && manifest.format !== 'kdna') {
|
|
369
|
-
errors.push(`kdna.json.format: invalid value "${manifest.format}". Expected "kdna".`);
|
|
370
|
-
}
|
|
371
|
-
if (!manifest.kdna_version) {
|
|
372
|
-
errors.push('kdna.json: missing required field "kdna_version"');
|
|
373
|
-
} else if (manifest.kdna_version !== '1.0') {
|
|
374
|
-
errors.push(
|
|
375
|
-
`kdna.json.kdna_version: invalid value "${manifest.kdna_version}". Expected "1.0".`,
|
|
376
|
-
);
|
|
377
|
-
}
|
|
378
|
-
if (manifest.language) {
|
|
379
|
-
errors.push('kdna.json: language is not allowed. Use default_language and languages.');
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
|
|
383
389
|
function openAsset(input) {
|
|
384
390
|
const { buffer, path } = normalizeInput(input);
|
|
385
391
|
const entries = parseZipEntries(buffer);
|
|
386
|
-
|
|
392
|
+
const asset = {
|
|
387
393
|
path,
|
|
388
394
|
size: buffer.length,
|
|
389
395
|
asset_digest: `sha256:${sha256Hex(buffer)}`,
|
|
@@ -394,6 +400,34 @@ function openAsset(input) {
|
|
|
394
400
|
return readZipEntry(buffer, entry);
|
|
395
401
|
},
|
|
396
402
|
};
|
|
403
|
+
Object.defineProperty(asset, ASSET_SOURCE_BYTES, {
|
|
404
|
+
value: buffer,
|
|
405
|
+
enumerable: false,
|
|
406
|
+
writable: false,
|
|
407
|
+
});
|
|
408
|
+
return asset;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function validateCurrentContainer(asset) {
|
|
412
|
+
const bytes = asset[ASSET_SOURCE_BYTES];
|
|
413
|
+
if (!Buffer.isBuffer(bytes)) {
|
|
414
|
+
throw new Error('Current KDNA validation requires the original .kdna container bytes');
|
|
415
|
+
}
|
|
416
|
+
return require('./container').validate(bytes);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function appendCurrentValidationErrors(asset, errors) {
|
|
420
|
+
try {
|
|
421
|
+
const validation = validateCurrentContainer(asset);
|
|
422
|
+
if (!validation.overall_valid) errors.push(...validation.problems);
|
|
423
|
+
if (validation.loader_compatible === false) {
|
|
424
|
+
errors.push(
|
|
425
|
+
loaderVersionUnsupportedMessage(validation.min_loader_version),
|
|
426
|
+
);
|
|
427
|
+
}
|
|
428
|
+
} catch (e) {
|
|
429
|
+
errors.push(e.message);
|
|
430
|
+
}
|
|
397
431
|
}
|
|
398
432
|
|
|
399
433
|
function listEntries(asset) {
|
|
@@ -409,30 +443,18 @@ function readManifest(asset) {
|
|
|
409
443
|
return parseJson(asset.readEntry('kdna.json'), 'kdna.json');
|
|
410
444
|
}
|
|
411
445
|
|
|
412
|
-
function readDataMapSync(asset,
|
|
413
|
-
const dataMap = {};
|
|
414
|
-
const manifest = readManifest(asset);
|
|
415
|
-
|
|
446
|
+
function readDataMapSync(asset, _options = {}) {
|
|
416
447
|
if (asset.entries.has('payload.kdnab')) {
|
|
417
|
-
const
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
if (payload.judgment.scenarios) dataMap['KDNA_Scenarios.json'] = payload.judgment.scenarios;
|
|
423
|
-
if (payload.judgment.cases) dataMap['KDNA_Cases.json'] = payload.judgment.cases;
|
|
424
|
-
if (payload.judgment.reasoning) dataMap['KDNA_Reasoning.json'] = payload.judgment.reasoning;
|
|
425
|
-
if (payload.judgment.evolution) dataMap['KDNA_Evolution.json'] = payload.judgment.evolution;
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
const encrypted = encryptedEntries(manifest);
|
|
429
|
-
if (encrypted.length && typeof options.decryptEntry !== 'function') {
|
|
430
|
-
throw new Error(`encrypted entries require decryptEntry hook: ${encrypted.join(', ')}`);
|
|
431
|
-
}
|
|
432
|
-
return dataMap;
|
|
448
|
+
const error = new Error(
|
|
449
|
+
'readDataMap is a legacy source-tree API and does not project current payload.kdnab into KDNA_Core.json/KDNA_Patterns.json. Use loadProfile or loadAuthorized.',
|
|
450
|
+
);
|
|
451
|
+
error.code = 'KDNA_LEGACY_DATA_MAP_UNSUPPORTED';
|
|
452
|
+
throw error;
|
|
433
453
|
}
|
|
434
454
|
|
|
435
|
-
|
|
455
|
+
const error = new Error('Not a current KDNA asset: missing payload.kdnab');
|
|
456
|
+
error.code = 'KDNA_FORMAT_INVALID';
|
|
457
|
+
throw error;
|
|
436
458
|
}
|
|
437
459
|
|
|
438
460
|
function verifySync(asset, options = {}) {
|
|
@@ -440,6 +462,12 @@ function verifySync(asset, options = {}) {
|
|
|
440
462
|
const warnings = [];
|
|
441
463
|
const entries = listEntries(asset);
|
|
442
464
|
|
|
465
|
+
// The current implementation is the sole authority for container,
|
|
466
|
+
// manifest, CBOR payload, checksum, and load-contract validity. The reader
|
|
467
|
+
// adds transport identity and legacy signature hooks; it must not maintain a
|
|
468
|
+
// second manifest or payload dialect.
|
|
469
|
+
appendCurrentValidationErrors(asset, errors);
|
|
470
|
+
|
|
443
471
|
if (!asset.entries.has('kdna.json')) errors.push('required entry missing: kdna.json');
|
|
444
472
|
verifyMediaType(asset, errors);
|
|
445
473
|
if (!asset.entries.has('payload.kdnab')) errors.push('required entry missing: payload.kdnab');
|
|
@@ -450,7 +478,9 @@ function verifySync(asset, options = {}) {
|
|
|
450
478
|
errors.push(`asset digest mismatch: expected ${options.asset_digest}, got ${asset_digest}`);
|
|
451
479
|
}
|
|
452
480
|
if (options.content_digest && options.content_digest !== content_digest) {
|
|
453
|
-
errors.push(
|
|
481
|
+
errors.push(
|
|
482
|
+
`content digest mismatch: expected ${options.content_digest}, got ${content_digest}`,
|
|
483
|
+
);
|
|
454
484
|
}
|
|
455
485
|
|
|
456
486
|
let manifest = null;
|
|
@@ -458,7 +488,6 @@ function verifySync(asset, options = {}) {
|
|
|
458
488
|
if (asset.entries.has('kdna.json')) {
|
|
459
489
|
try {
|
|
460
490
|
manifest = readManifest(asset);
|
|
461
|
-
validateManifestIdentity(manifest, errors, warnings);
|
|
462
491
|
const encrypted = encryptedEntries(manifest);
|
|
463
492
|
if (encrypted.length) {
|
|
464
493
|
warnings.push(`encrypted entries present: ${encrypted.join(', ')}`);
|
|
@@ -479,7 +508,7 @@ function verifySync(asset, options = {}) {
|
|
|
479
508
|
asset.readEntry(entryName),
|
|
480
509
|
options,
|
|
481
510
|
);
|
|
482
|
-
|
|
511
|
+
validateDecodedEntry(decrypted, entryName);
|
|
483
512
|
} catch (e) {
|
|
484
513
|
errors.push(e.message);
|
|
485
514
|
}
|
|
@@ -516,29 +545,15 @@ function verifySync(asset, options = {}) {
|
|
|
516
545
|
}
|
|
517
546
|
|
|
518
547
|
function loadProfileSync(asset, profile = 'compact', options = {}) {
|
|
519
|
-
const
|
|
520
|
-
if (
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
content_digest: buildContentDigest(asset),
|
|
526
|
-
entries: listEntries(asset),
|
|
527
|
-
name: manifest.name || manifest.asset_id || null,
|
|
528
|
-
version: manifest.version || null,
|
|
529
|
-
judgment_version: manifest.judgment_version || null,
|
|
530
|
-
keywords: manifest.keywords || [],
|
|
531
|
-
};
|
|
532
|
-
}
|
|
533
|
-
const dataMap = readDataMapSync(asset, STANDARD_ENTRIES, options);
|
|
534
|
-
const mode = profile === 'full' ? 'all' : profile === 'scenario' ? 'auto' : 'minimum';
|
|
535
|
-
const domain = loadDomainFromFiles(dataMap, { mode, input: options.input || '' });
|
|
536
|
-
return {
|
|
548
|
+
const bytes = asset[ASSET_SOURCE_BYTES];
|
|
549
|
+
if (!Buffer.isBuffer(bytes)) {
|
|
550
|
+
throw new Error('loadProfile requires the original .kdna container bytes');
|
|
551
|
+
}
|
|
552
|
+
return require('./runtime-api').loadAuthorized(bytes, {
|
|
553
|
+
...options,
|
|
537
554
|
profile,
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
context: options.context === false || !domain ? null : formatContext(domain),
|
|
541
|
-
};
|
|
555
|
+
as: options.as || 'json',
|
|
556
|
+
});
|
|
542
557
|
}
|
|
543
558
|
|
|
544
559
|
function createKdnaAssetReader() {
|
|
@@ -565,7 +580,13 @@ function createKdnaAssetReader() {
|
|
|
565
580
|
if (!asset.entries.has(entryName)) return null;
|
|
566
581
|
const manifest =
|
|
567
582
|
entryName === 'kdna.json' ? null : parseJson(asset.readEntry('kdna.json'), 'kdna.json');
|
|
568
|
-
const buf = maybeDecryptEntrySync(
|
|
583
|
+
const buf = maybeDecryptEntrySync(
|
|
584
|
+
asset,
|
|
585
|
+
manifest,
|
|
586
|
+
entryName,
|
|
587
|
+
asset.readEntry(entryName),
|
|
588
|
+
options,
|
|
589
|
+
);
|
|
569
590
|
return parseJson(buf, entryName);
|
|
570
591
|
},
|
|
571
592
|
|
|
@@ -591,21 +612,8 @@ function createKdnaAssetReader() {
|
|
|
591
612
|
|
|
592
613
|
readDataMapSync,
|
|
593
614
|
|
|
594
|
-
async readDataMap(asset,
|
|
595
|
-
|
|
596
|
-
const manifest = await this.readManifest(asset);
|
|
597
|
-
for (const entryName of entries) {
|
|
598
|
-
if (!asset.entries.has(entryName)) continue;
|
|
599
|
-
const buf = await maybeDecryptEntry(
|
|
600
|
-
asset,
|
|
601
|
-
manifest,
|
|
602
|
-
entryName,
|
|
603
|
-
asset.readEntry(entryName),
|
|
604
|
-
options,
|
|
605
|
-
);
|
|
606
|
-
dataMap[entryName] = parseJson(buf, entryName);
|
|
607
|
-
}
|
|
608
|
-
return dataMap;
|
|
615
|
+
async readDataMap(asset, _entries = STANDARD_ENTRIES, options = {}) {
|
|
616
|
+
return readDataMapSync(asset, options);
|
|
609
617
|
},
|
|
610
618
|
|
|
611
619
|
contentDigestSync: buildContentDigest,
|
|
@@ -621,6 +629,8 @@ function createKdnaAssetReader() {
|
|
|
621
629
|
const warnings = [];
|
|
622
630
|
const entries = [...asset.entries.keys()].sort();
|
|
623
631
|
|
|
632
|
+
appendCurrentValidationErrors(asset, errors);
|
|
633
|
+
|
|
624
634
|
if (!asset.entries.has('kdna.json')) errors.push('required entry missing: kdna.json');
|
|
625
635
|
verifyMediaType(asset, errors);
|
|
626
636
|
if (!asset.entries.has('payload.kdnab')) errors.push('required entry missing: payload.kdnab');
|
|
@@ -641,7 +651,6 @@ function createKdnaAssetReader() {
|
|
|
641
651
|
if (asset.entries.has('kdna.json')) {
|
|
642
652
|
try {
|
|
643
653
|
manifest = parseJson(asset.readEntry('kdna.json'), 'kdna.json');
|
|
644
|
-
validateManifestIdentity(manifest, errors, warnings);
|
|
645
654
|
const encrypted = encryptedEntries(manifest);
|
|
646
655
|
if (encrypted.length) {
|
|
647
656
|
warnings.push(`encrypted entries present: ${encrypted.join(', ')}`);
|
|
@@ -662,7 +671,7 @@ function createKdnaAssetReader() {
|
|
|
662
671
|
asset.readEntry(entryName),
|
|
663
672
|
options,
|
|
664
673
|
);
|
|
665
|
-
|
|
674
|
+
validateDecodedEntry(decrypted, entryName);
|
|
666
675
|
} catch (e) {
|
|
667
676
|
errors.push(e.message);
|
|
668
677
|
}
|
|
@@ -692,30 +701,7 @@ function createKdnaAssetReader() {
|
|
|
692
701
|
loadProfileSync,
|
|
693
702
|
|
|
694
703
|
async loadProfile(asset, profile = 'compact', options = {}) {
|
|
695
|
-
|
|
696
|
-
if (profile === 'index') {
|
|
697
|
-
return {
|
|
698
|
-
profile,
|
|
699
|
-
manifest,
|
|
700
|
-
asset_digest: asset.asset_digest,
|
|
701
|
-
content_digest: buildContentDigest(asset),
|
|
702
|
-
entries: await this.listEntries(asset),
|
|
703
|
-
name: manifest.name || manifest.asset_id || null,
|
|
704
|
-
version: manifest.version || null,
|
|
705
|
-
judgment_version: manifest.judgment_version || null,
|
|
706
|
-
keywords: manifest.keywords || [],
|
|
707
|
-
};
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
const dataMap = await this.readDataMap(asset, STANDARD_ENTRIES, options);
|
|
711
|
-
const mode = profile === 'full' ? 'all' : profile === 'scenario' ? 'auto' : 'minimum';
|
|
712
|
-
const domain = loadDomainFromFiles(dataMap, { mode, input: options.input || '' });
|
|
713
|
-
return {
|
|
714
|
-
profile,
|
|
715
|
-
manifest,
|
|
716
|
-
domain,
|
|
717
|
-
context: options.context === false || !domain ? null : formatContext(domain),
|
|
718
|
-
};
|
|
704
|
+
return loadProfileSync(asset, profile, options);
|
|
719
705
|
},
|
|
720
706
|
};
|
|
721
707
|
}
|