@aikdna/kdna-core 0.17.0 → 0.18.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 +131 -2
- package/README.md +145 -2
- package/package.json +3 -2
- package/schema/agent-host-2-receipt.schema.json +206 -0
- package/schema/agent-host-2-request.schema.json +135 -0
- package/schema/agent-host-capabilities-1.schema.json +40 -0
- package/schema/checksums.schema.json +40 -1
- package/schema/consumption-plan-1.schema.json +280 -0
- package/schema/digest-evidence.schema.json +123 -0
- package/schema/judgment-trace-1.schema.json +748 -0
- package/schema/load-plan.schema.json +2 -2
- package/schema/runtime-capsule-1.schema.json +68 -0
- package/schema/runtime-capsule-2.schema.json +140 -0
- package/src/asset-reader.js +100 -120
- package/src/capsule-v2.js +687 -0
- package/src/container-dispatcher.js +4 -1
- package/src/execution-contract-v1.js +1850 -0
- package/src/index.js +4 -0
- package/src/index.mjs +136 -21
- package/src/lint-pure.js +6 -3
- package/src/public-api.js +59 -163
- package/src/runtime-api.js +5 -6
- package/src/types.d.ts +1181 -113
- package/src/v1/index.js +225 -55
- package/src/v1/index.mjs +1 -0
package/src/v1/index.js
CHANGED
|
@@ -228,9 +228,13 @@ function detectContainerFormat(absPath) {
|
|
|
228
228
|
* truncated input.
|
|
229
229
|
*/
|
|
230
230
|
function listZipEntries(absPath, opts = {}) {
|
|
231
|
+
return listZipEntriesFromBuffer(fs.readFileSync(absPath), opts);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function listZipEntriesFromBuffer(input, opts = {}) {
|
|
231
235
|
const secure = opts.secure !== false;
|
|
232
236
|
const limits = { ...DEFAULT_CONTAINER_LIMITS, ...(opts.limits || {}) };
|
|
233
|
-
const buf =
|
|
237
|
+
const buf = Buffer.isBuffer(input) ? input : Buffer.from(input);
|
|
234
238
|
if (secure && buf.length > limits.maxContainerBytes) {
|
|
235
239
|
throw new Error(`container exceeds maximum size (${limits.maxContainerBytes} bytes)`);
|
|
236
240
|
}
|
|
@@ -533,6 +537,7 @@ function readV1Layout(absPath) {
|
|
|
533
537
|
const map = {};
|
|
534
538
|
let entries = null; // ZIP entries if container
|
|
535
539
|
let kind; // 'dir' | 'file'
|
|
540
|
+
let containerBytes = null;
|
|
536
541
|
|
|
537
542
|
if (stat.isDirectory()) {
|
|
538
543
|
kind = 'dir';
|
|
@@ -558,13 +563,10 @@ function readV1Layout(absPath) {
|
|
|
558
563
|
}
|
|
559
564
|
} else if (stat.isFile()) {
|
|
560
565
|
kind = 'file';
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
if (entries[0].method !== 0) {
|
|
566
|
-
throw new Error('not a KDNA Asset Container: mimetype must be uncompressed');
|
|
567
|
-
}
|
|
566
|
+
// Open once so parsing and the whole-file digest come from the same
|
|
567
|
+
// immutable snapshot, without a path-level TOCTOU window.
|
|
568
|
+
containerBytes = fs.readFileSync(absPath);
|
|
569
|
+
entries = listZipEntriesFromBuffer(containerBytes);
|
|
568
570
|
for (const e of entries) {
|
|
569
571
|
// We only need the well-known entries; signatures/ attachments/ etc.
|
|
570
572
|
// are passed through unchanged by the loader but not parsed here.
|
|
@@ -577,25 +579,36 @@ function readV1Layout(absPath) {
|
|
|
577
579
|
map[e.name] = e.data;
|
|
578
580
|
}
|
|
579
581
|
}
|
|
580
|
-
for (const f of V1_REQUIRED_DIR_ENTRIES) {
|
|
581
|
-
if (!map[f]) {
|
|
582
|
-
throw new Error(`not a KDNA Asset Container: missing ${f}`);
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
582
|
} else {
|
|
586
583
|
throw new Error(`not a file or directory: ${absPath}`);
|
|
587
584
|
}
|
|
588
585
|
|
|
589
|
-
|
|
586
|
+
const containerDigest = kind === 'file'
|
|
587
|
+
? `sha256:${crypto.createHash('sha256').update(containerBytes).digest('hex')}`
|
|
588
|
+
: null;
|
|
589
|
+
|
|
590
|
+
return finalizeV1Layout({ kind, map, entries, containerDigest });
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
function finalizeV1Layout({ kind, map, entries, containerDigest }) {
|
|
594
|
+
if (kind !== 'dir' && (!entries || entries.length === 0 || entries[0].name !== 'mimetype')) {
|
|
595
|
+
throw new Error('not a KDNA Asset Container: first entry is not mimetype');
|
|
596
|
+
}
|
|
597
|
+
if (kind !== 'dir' && entries[0].method !== 0) {
|
|
598
|
+
throw new Error('not a KDNA Asset Container: mimetype must be uncompressed');
|
|
599
|
+
}
|
|
600
|
+
for (const required of V1_REQUIRED_DIR_ENTRIES) {
|
|
601
|
+
if (!map[required]) {
|
|
602
|
+
const source = kind === 'dir' ? 'KDNA authoring source directory' : 'KDNA Asset Container';
|
|
603
|
+
throw new Error(`not a ${source}: missing ${required}`);
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
|
|
590
607
|
const mime = map.mimetype.toString('utf8');
|
|
591
608
|
if (mime !== MIMETYPE) {
|
|
592
|
-
throw new Error(
|
|
593
|
-
`not a KDNA layout: mimetype is "${mime}", expected "${MIMETYPE}"`,
|
|
594
|
-
);
|
|
609
|
+
throw new Error(`not a KDNA layout: mimetype is "${mime}", expected "${MIMETYPE}"`);
|
|
595
610
|
}
|
|
596
611
|
|
|
597
|
-
// Lineage must be a single object, not an array. (Format rule from
|
|
598
|
-
// docs/core/manifest.md / schema/manifest.schema.json.)
|
|
599
612
|
let manifest;
|
|
600
613
|
try {
|
|
601
614
|
manifest = parseJsonEntry('kdna.json', map['kdna.json']);
|
|
@@ -606,7 +619,60 @@ function readV1Layout(absPath) {
|
|
|
606
619
|
throw new Error('kdna.json.lineage must be an object, not an array');
|
|
607
620
|
}
|
|
608
621
|
|
|
609
|
-
return { kind, map, manifest, entries };
|
|
622
|
+
return { kind, map, manifest, entries, containerDigest };
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
function readV1LayoutBytes(input) {
|
|
626
|
+
const bytes = Buffer.isBuffer(input) ? input : Buffer.from(input);
|
|
627
|
+
const entries = listZipEntriesFromBuffer(bytes);
|
|
628
|
+
const map = {};
|
|
629
|
+
for (const entry of entries) {
|
|
630
|
+
if (
|
|
631
|
+
entry.name === 'mimetype' ||
|
|
632
|
+
entry.name === 'kdna.json' ||
|
|
633
|
+
entry.name === 'payload.kdnab' ||
|
|
634
|
+
entry.name === 'checksums.json'
|
|
635
|
+
) {
|
|
636
|
+
map[entry.name] = entry.data;
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
return finalizeV1Layout({
|
|
640
|
+
kind: 'memory',
|
|
641
|
+
map,
|
|
642
|
+
entries,
|
|
643
|
+
containerDigest: `sha256:${crypto.createHash('sha256').update(bytes).digest('hex')}`,
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
function readInputLayout(input) {
|
|
648
|
+
if (Buffer.isBuffer(input) || input instanceof Uint8Array) {
|
|
649
|
+
return readV1LayoutBytes(input);
|
|
650
|
+
}
|
|
651
|
+
return readV1Layout(path.resolve(input));
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
function snapshotPackagedInput(input) {
|
|
655
|
+
if (Buffer.isBuffer(input) || input instanceof Uint8Array) {
|
|
656
|
+
return Buffer.from(input);
|
|
657
|
+
}
|
|
658
|
+
if (typeof input !== 'string') return input;
|
|
659
|
+
const resolved = path.resolve(input);
|
|
660
|
+
let stat;
|
|
661
|
+
try {
|
|
662
|
+
stat = fs.statSync(resolved);
|
|
663
|
+
} catch {
|
|
664
|
+
return input;
|
|
665
|
+
}
|
|
666
|
+
// Authoring directories remain available to inspect/validate. Runtime
|
|
667
|
+
// wrappers reject them; this helper only freezes packaged-file loads.
|
|
668
|
+
return stat.isFile() ? fs.readFileSync(resolved) : input;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
function inputSource(input, kind) {
|
|
672
|
+
return {
|
|
673
|
+
kind,
|
|
674
|
+
path: typeof input === 'string' ? path.resolve(input) : null,
|
|
675
|
+
};
|
|
610
676
|
}
|
|
611
677
|
|
|
612
678
|
function parseJsonEntry(name, bytes, opts = {}) {
|
|
@@ -739,6 +805,21 @@ function runValidate(v1) {
|
|
|
739
805
|
}
|
|
740
806
|
}
|
|
741
807
|
|
|
808
|
+
// Known pre-1.0 aliases are not extension fields. Rejecting them here keeps
|
|
809
|
+
// the current v1 validator as the single manifest authority while allowing
|
|
810
|
+
// genuinely namespaced/forward-compatible manifest extensions.
|
|
811
|
+
const removedAliases = {
|
|
812
|
+
kdna_spec: 'kdna_version',
|
|
813
|
+
format_version: 'kdna_version',
|
|
814
|
+
spec_version: 'kdna_version',
|
|
815
|
+
};
|
|
816
|
+
for (const [field, replacement] of Object.entries(removedAliases)) {
|
|
817
|
+
if (Object.prototype.hasOwnProperty.call(v1.manifest, field)) {
|
|
818
|
+
result.schema_valid = false;
|
|
819
|
+
problems.push(`kdna.json: ${field} is not allowed. Use ${replacement}.`);
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
|
|
742
823
|
// payload gate — payload.kdnab against payload-profile-v1.schema.json
|
|
743
824
|
// For actually encrypted payloads (envelopes), skip schema validation.
|
|
744
825
|
let payload;
|
|
@@ -828,6 +909,43 @@ function runValidate(v1) {
|
|
|
828
909
|
}
|
|
829
910
|
|
|
830
911
|
function verifyDigests(checksums, map, problems, result) {
|
|
912
|
+
const digestMetadataPresent =
|
|
913
|
+
checksums.digest_profile !== undefined || checksums.covered_entries !== undefined;
|
|
914
|
+
if (digestMetadataPresent) {
|
|
915
|
+
const requiredDigestFields = [
|
|
916
|
+
'digest_profile',
|
|
917
|
+
'covered_entries',
|
|
918
|
+
'manifest_digest',
|
|
919
|
+
'payload_digest',
|
|
920
|
+
'entry_set_digest',
|
|
921
|
+
];
|
|
922
|
+
const missingDigestFields = requiredDigestFields.filter(
|
|
923
|
+
(field) => checksums[field] === undefined,
|
|
924
|
+
);
|
|
925
|
+
if (missingDigestFields.length) {
|
|
926
|
+
problems.push(
|
|
927
|
+
`checksums: kdna-runtime-entry-set-v1 metadata requires ${missingDigestFields.join(', ')}`,
|
|
928
|
+
);
|
|
929
|
+
result.checksums_valid = false;
|
|
930
|
+
return false;
|
|
931
|
+
}
|
|
932
|
+
if (checksums.digest_profile !== 'kdna-runtime-entry-set-v1') {
|
|
933
|
+
problems.push('checksums: unsupported digest_profile (expected kdna-runtime-entry-set-v1)');
|
|
934
|
+
result.checksums_valid = false;
|
|
935
|
+
return false;
|
|
936
|
+
}
|
|
937
|
+
const coveredEntries = checksums.covered_entries;
|
|
938
|
+
if (
|
|
939
|
+
!Array.isArray(coveredEntries)
|
|
940
|
+
|| coveredEntries.length !== 2
|
|
941
|
+
|| coveredEntries[0] !== 'kdna.json'
|
|
942
|
+
|| coveredEntries[1] !== 'payload.kdnab'
|
|
943
|
+
) {
|
|
944
|
+
problems.push('checksums: covered_entries does not match kdna-runtime-entry-set-v1');
|
|
945
|
+
result.checksums_valid = false;
|
|
946
|
+
return false;
|
|
947
|
+
}
|
|
948
|
+
}
|
|
831
949
|
const algo = checksums.algorithm || 'sha256';
|
|
832
950
|
if (algo !== 'sha256') {
|
|
833
951
|
problems.push(`checksums: unsupported digest algorithm ${algo} (supported: sha256)`);
|
|
@@ -857,21 +975,30 @@ function verifyDigests(checksums, map, problems, result) {
|
|
|
857
975
|
stillValid = false;
|
|
858
976
|
}
|
|
859
977
|
}
|
|
860
|
-
if (
|
|
861
|
-
|
|
978
|
+
if (
|
|
979
|
+
checksums.entry_set_digest
|
|
980
|
+
&& checksums.asset_digest
|
|
981
|
+
&& checksums.entry_set_digest !== checksums.asset_digest
|
|
982
|
+
) {
|
|
983
|
+
problems.push('checksums: entry_set_digest does not match deprecated asset_digest alias');
|
|
984
|
+
stillValid = false;
|
|
985
|
+
}
|
|
986
|
+
const declaredEntrySetDigest = checksums.entry_set_digest || checksums.asset_digest;
|
|
987
|
+
if (declaredEntrySetDigest) {
|
|
988
|
+
const digestLabel = checksums.entry_set_digest ? 'entry_set_digest' : 'asset_digest';
|
|
989
|
+
const expectedEntrySetDigest = declaredEntrySetDigest.replace(/^sha256:/, '');
|
|
862
990
|
const missingAssetInputs = ['kdna.json', 'payload.kdnab'].filter((entryName) => !entryDigests[entryName]);
|
|
863
991
|
if (missingAssetInputs.length) {
|
|
864
|
-
problems.push(`checksums:
|
|
992
|
+
problems.push(`checksums: ${digestLabel} cannot be verified without ${missingAssetInputs.join(', ')}`);
|
|
865
993
|
stillValid = false;
|
|
866
994
|
} else {
|
|
867
|
-
const
|
|
868
|
-
.
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
if (actualAssetDigest !== expectedAssetDigest) {
|
|
995
|
+
const actualEntrySetDigest = computeRuntimeEntrySetDigest(
|
|
996
|
+
map['kdna.json'],
|
|
997
|
+
map['payload.kdnab'],
|
|
998
|
+
).slice('sha256:'.length);
|
|
999
|
+
if (actualEntrySetDigest !== expectedEntrySetDigest) {
|
|
873
1000
|
problems.push(
|
|
874
|
-
`checksums:
|
|
1001
|
+
`checksums: ${digestLabel} mismatch (declared ${expectedEntrySetDigest.slice(0, 8)}..., actual ${actualEntrySetDigest.slice(0, 8)}...)`,
|
|
875
1002
|
);
|
|
876
1003
|
stillValid = false;
|
|
877
1004
|
}
|
|
@@ -904,6 +1031,28 @@ function digestEntry(sourceDir, entry) {
|
|
|
904
1031
|
};
|
|
905
1032
|
}
|
|
906
1033
|
|
|
1034
|
+
function computeRuntimeEntrySetDigest(manifestBytes, payloadBytes) {
|
|
1035
|
+
if (
|
|
1036
|
+
(!Buffer.isBuffer(manifestBytes) && !(manifestBytes instanceof Uint8Array))
|
|
1037
|
+
|| (!Buffer.isBuffer(payloadBytes) && !(payloadBytes instanceof Uint8Array))
|
|
1038
|
+
) {
|
|
1039
|
+
const error = new Error(
|
|
1040
|
+
'E requires raw uncompressed kdna.json and payload.kdnab bytes.',
|
|
1041
|
+
);
|
|
1042
|
+
error.code = 'KDNA_DIGEST_INPUT_INVALID';
|
|
1043
|
+
throw error;
|
|
1044
|
+
}
|
|
1045
|
+
const entries = {
|
|
1046
|
+
'kdna.json': crypto.createHash('sha256').update(manifestBytes).digest('hex'),
|
|
1047
|
+
'payload.kdnab': crypto.createHash('sha256').update(payloadBytes).digest('hex'),
|
|
1048
|
+
};
|
|
1049
|
+
const combined = Object.keys(entries)
|
|
1050
|
+
.sort()
|
|
1051
|
+
.map((name) => `${name}:${entries[name]}`)
|
|
1052
|
+
.join('\n');
|
|
1053
|
+
return `sha256:${crypto.createHash('sha256').update(combined).digest('hex')}`;
|
|
1054
|
+
}
|
|
1055
|
+
|
|
907
1056
|
function buildChecksumsV1(sourceDir) {
|
|
908
1057
|
const absSrc = path.resolve(sourceDir);
|
|
909
1058
|
if (!fs.existsSync(absSrc) || !fs.statSync(absSrc).isDirectory()) {
|
|
@@ -914,16 +1063,18 @@ function buildChecksumsV1(sourceDir) {
|
|
|
914
1063
|
'kdna.json': digestEntry(absSrc, 'kdna.json'),
|
|
915
1064
|
'payload.kdnab': digestEntry(absSrc, 'payload.kdnab'),
|
|
916
1065
|
};
|
|
917
|
-
const
|
|
918
|
-
.
|
|
919
|
-
.
|
|
920
|
-
|
|
921
|
-
|
|
1066
|
+
const entrySetDigest = computeRuntimeEntrySetDigest(
|
|
1067
|
+
fs.readFileSync(path.join(absSrc, 'kdna.json')),
|
|
1068
|
+
fs.readFileSync(path.join(absSrc, 'payload.kdnab')),
|
|
1069
|
+
);
|
|
922
1070
|
return {
|
|
1071
|
+
digest_profile: 'kdna-runtime-entry-set-v1',
|
|
1072
|
+
covered_entries: ['kdna.json', 'payload.kdnab'],
|
|
923
1073
|
algorithm: 'sha256',
|
|
924
1074
|
manifest_digest: `sha256:${entries['kdna.json'].value}`,
|
|
925
1075
|
payload_digest: `sha256:${entries['payload.kdnab'].value}`,
|
|
926
|
-
|
|
1076
|
+
entry_set_digest: entrySetDigest,
|
|
1077
|
+
asset_digest: entrySetDigest,
|
|
927
1078
|
entries,
|
|
928
1079
|
};
|
|
929
1080
|
}
|
|
@@ -1061,7 +1212,7 @@ function unpack(inputPath, outputDir) {
|
|
|
1061
1212
|
// ─── Public router entry points ────────────────────────────────────────
|
|
1062
1213
|
|
|
1063
1214
|
function inspect(inputPath, _opts = {}) {
|
|
1064
|
-
const v1 =
|
|
1215
|
+
const v1 = readInputLayout(inputPath);
|
|
1065
1216
|
const out = buildInspectOutput(v1);
|
|
1066
1217
|
// Guard against accidental forbidden wording in any future field additions.
|
|
1067
1218
|
assertNoForbiddenTerms(out);
|
|
@@ -1069,7 +1220,7 @@ function inspect(inputPath, _opts = {}) {
|
|
|
1069
1220
|
}
|
|
1070
1221
|
|
|
1071
1222
|
function validate(inputPath, _opts = {}) {
|
|
1072
|
-
const v1 =
|
|
1223
|
+
const v1 = readInputLayout(inputPath);
|
|
1073
1224
|
return runValidate(v1);
|
|
1074
1225
|
}
|
|
1075
1226
|
|
|
@@ -1178,7 +1329,7 @@ function baseLoadPlan(inputPath, v1, validation, opts = {}) {
|
|
|
1178
1329
|
issues: [],
|
|
1179
1330
|
source: {
|
|
1180
1331
|
kind: v1.kind,
|
|
1181
|
-
path: path.resolve(inputPath),
|
|
1332
|
+
path: typeof inputPath === 'string' ? path.resolve(inputPath) : null,
|
|
1182
1333
|
},
|
|
1183
1334
|
};
|
|
1184
1335
|
|
|
@@ -1218,6 +1369,7 @@ function canonicalToV1Layout(asset) {
|
|
|
1218
1369
|
map,
|
|
1219
1370
|
manifest: asset.manifest,
|
|
1220
1371
|
entries: Object.keys(map),
|
|
1372
|
+
containerDigest: asset.containerDigest || null,
|
|
1221
1373
|
};
|
|
1222
1374
|
}
|
|
1223
1375
|
|
|
@@ -1229,9 +1381,26 @@ function canonicalToV1Layout(asset) {
|
|
|
1229
1381
|
function planLoad(inputPath, opts = {}) {
|
|
1230
1382
|
let v1;
|
|
1231
1383
|
|
|
1384
|
+
if (Buffer.isBuffer(inputPath) || inputPath instanceof Uint8Array) {
|
|
1385
|
+
try {
|
|
1386
|
+
v1 = readInputLayout(inputPath);
|
|
1387
|
+
} catch (e) {
|
|
1388
|
+
return finalizeLoadPlan({
|
|
1389
|
+
kdna_version: null,
|
|
1390
|
+
asset: { asset_id: null, asset_uid: null, title: null, version: null, judgment_version: null },
|
|
1391
|
+
access: null, access_alias: null, entitlement_profile: null,
|
|
1392
|
+
state: 'invalid', required_action: 'block', can_load_now: false, projection_policy: 'none',
|
|
1393
|
+
input_fingerprint: null,
|
|
1394
|
+
checks: { format_valid: false, schema_valid: false, payload_valid: false, checksums_valid: false, load_contract_valid: false, overall_valid: false },
|
|
1395
|
+
issues: [buildLoadPlanIssue('KDNA_FORMAT_INVALID', 'blocking', e.message)],
|
|
1396
|
+
source: inputSource(inputPath, 'memory'),
|
|
1397
|
+
});
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1232
1401
|
// Try the unified container dispatcher first; the internal compatibility
|
|
1233
1402
|
// reader below remains available only for authoring and migration tooling.
|
|
1234
|
-
if (readAsset !== null) {
|
|
1403
|
+
if (!v1 && readAsset !== null) {
|
|
1235
1404
|
try {
|
|
1236
1405
|
const asset = readAsset(path.resolve(inputPath));
|
|
1237
1406
|
v1 = canonicalToV1Layout(asset);
|
|
@@ -1251,7 +1420,7 @@ function planLoad(inputPath, opts = {}) {
|
|
|
1251
1420
|
e.code === 'KDNA_FORMAT_UNKNOWN' ? 'KDNA_FORMAT_UNKNOWN' : 'KDNA_FORMAT_INVALID',
|
|
1252
1421
|
'blocking', e.message,
|
|
1253
1422
|
)],
|
|
1254
|
-
source:
|
|
1423
|
+
source: inputSource(inputPath, null),
|
|
1255
1424
|
});
|
|
1256
1425
|
}
|
|
1257
1426
|
}
|
|
@@ -1259,7 +1428,7 @@ function planLoad(inputPath, opts = {}) {
|
|
|
1259
1428
|
|
|
1260
1429
|
if (!v1) {
|
|
1261
1430
|
try {
|
|
1262
|
-
v1 =
|
|
1431
|
+
v1 = readInputLayout(inputPath);
|
|
1263
1432
|
} catch (e) {
|
|
1264
1433
|
return finalizeLoadPlan({
|
|
1265
1434
|
kdna_version: null,
|
|
@@ -1269,7 +1438,7 @@ function planLoad(inputPath, opts = {}) {
|
|
|
1269
1438
|
input_fingerprint: null,
|
|
1270
1439
|
checks: { format_valid: false, schema_valid: false, payload_valid: false, checksums_valid: false, load_contract_valid: false, overall_valid: false },
|
|
1271
1440
|
issues: [buildLoadPlanIssue('KDNA_FORMAT_INVALID', 'blocking', e.message)],
|
|
1272
|
-
source:
|
|
1441
|
+
source: inputSource(inputPath, null),
|
|
1273
1442
|
});
|
|
1274
1443
|
}
|
|
1275
1444
|
}
|
|
@@ -1714,6 +1883,7 @@ module.exports = {
|
|
|
1714
1883
|
planLoad,
|
|
1715
1884
|
loadAuthorized,
|
|
1716
1885
|
buildChecksums: buildChecksumsV1,
|
|
1886
|
+
computeRuntimeEntrySetDigest,
|
|
1717
1887
|
pack,
|
|
1718
1888
|
unpack,
|
|
1719
1889
|
load: loadV1,
|
|
@@ -1806,7 +1976,11 @@ function sanitizePrompt(s) {
|
|
|
1806
1976
|
}
|
|
1807
1977
|
|
|
1808
1978
|
function loadAuthorized(inputPath, opts = {}) {
|
|
1809
|
-
|
|
1979
|
+
// A packaged path is read exactly once. Authorization and projection must
|
|
1980
|
+
// operate on the same immutable byte snapshot even if the path changes
|
|
1981
|
+
// while this synchronous call is in progress.
|
|
1982
|
+
const inputSnapshot = snapshotPackagedInput(inputPath);
|
|
1983
|
+
const plan = planLoad(inputSnapshot, opts);
|
|
1810
1984
|
if (plan.can_load_now !== true) {
|
|
1811
1985
|
const issueCodes = Array.isArray(plan.issues)
|
|
1812
1986
|
? plan.issues.map((issue) => issue.code).filter(Boolean)
|
|
@@ -1827,7 +2001,7 @@ function loadAuthorized(inputPath, opts = {}) {
|
|
|
1827
2001
|
signature_valid: plan.signature_valid === true,
|
|
1828
2002
|
},
|
|
1829
2003
|
};
|
|
1830
|
-
return loadV1Unsafe(
|
|
2004
|
+
return loadV1Unsafe(inputSnapshot, mergedOpts);
|
|
1831
2005
|
}
|
|
1832
2006
|
|
|
1833
2007
|
function loadV1(inputPath, opts = {}) {
|
|
@@ -1895,7 +2069,7 @@ function hasCompactPromptContent(content) {
|
|
|
1895
2069
|
}
|
|
1896
2070
|
|
|
1897
2071
|
function loadV1Unsafe(inputPath, opts = {}) {
|
|
1898
|
-
const v1 =
|
|
2072
|
+
const v1 = readInputLayout(inputPath);
|
|
1899
2073
|
const m = v1.manifest;
|
|
1900
2074
|
const profile = opts.profile || (m.load_contract ? m.load_contract.default_profile : 'compact') || 'compact';
|
|
1901
2075
|
const as = opts.as || 'json';
|
|
@@ -2218,7 +2392,7 @@ function loadV1Unsafe(inputPath, opts = {}) {
|
|
|
2218
2392
|
return result;
|
|
2219
2393
|
}
|
|
2220
2394
|
|
|
2221
|
-
return wrapAsCapsule(result, v1, profile, opts);
|
|
2395
|
+
return wrapAsCapsule(result, v1, profile, { ...opts, as });
|
|
2222
2396
|
}
|
|
2223
2397
|
|
|
2224
2398
|
function wrapAsCapsule(result, v1, profile, opts) {
|
|
@@ -2231,14 +2405,10 @@ function wrapAsCapsule(result, v1, profile, opts) {
|
|
|
2231
2405
|
function buildCapsule(loadResult, v1, profile, opts = {}) {
|
|
2232
2406
|
const m = v1.manifest;
|
|
2233
2407
|
const val = opts._validation || {};
|
|
2234
|
-
const assetDigest =
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
return checks.asset_digest || null;
|
|
2239
|
-
} catch { return null; }
|
|
2240
|
-
})()
|
|
2241
|
-
: null);
|
|
2408
|
+
const assetDigest = computeRuntimeEntrySetDigest(
|
|
2409
|
+
v1.map && v1.map['kdna.json'],
|
|
2410
|
+
v1.map && v1.map['payload.kdnab'],
|
|
2411
|
+
);
|
|
2242
2412
|
|
|
2243
2413
|
const pubkey = (m.creator && m.creator.pubkey) || (m.author && m.author.pubkey) || null;
|
|
2244
2414
|
const sigVerified = val.signature_valid === true;
|
package/src/v1/index.mjs
CHANGED
|
@@ -10,6 +10,7 @@ export const validate = v1.validate;
|
|
|
10
10
|
export const planLoad = v1.planLoad;
|
|
11
11
|
export const loadAuthorized = v1.loadAuthorized;
|
|
12
12
|
export const buildChecksums = v1.buildChecksums;
|
|
13
|
+
export const computeRuntimeEntrySetDigest = v1.computeRuntimeEntrySetDigest;
|
|
13
14
|
export const pack = v1.pack;
|
|
14
15
|
export const unpack = v1.unpack;
|
|
15
16
|
export const load = v1.load;
|