@aikdna/kdna-core 0.16.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 +164 -2
- package/README.md +151 -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/manifest.schema.json +4 -5
- package/schema/payload-profile-v1.schema.json +25 -0
- 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 +298 -56
- 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 = {}) {
|
|
@@ -1866,8 +2040,36 @@ function normalizeCompactAxiom(axiom) {
|
|
|
1866
2040
|
};
|
|
1867
2041
|
}
|
|
1868
2042
|
|
|
2043
|
+
function judgmentRoleExclusions(role) {
|
|
2044
|
+
if (!role || typeof role !== 'object' || Array.isArray(role)) return [];
|
|
2045
|
+
if (Array.isArray(role.does_not_act_as)) return role.does_not_act_as;
|
|
2046
|
+
return role.does_not_act_as ? [role.does_not_act_as] : [];
|
|
2047
|
+
}
|
|
2048
|
+
|
|
2049
|
+
function hasStandardJudgmentRoleContent(role) {
|
|
2050
|
+
if (!role || typeof role !== 'object' || Array.isArray(role)) return false;
|
|
2051
|
+
return Boolean(
|
|
2052
|
+
role.acts_as || judgmentRoleExclusions(role).length || role.responsibility,
|
|
2053
|
+
);
|
|
2054
|
+
}
|
|
2055
|
+
|
|
2056
|
+
function hasCompactPromptContent(content) {
|
|
2057
|
+
if (!content) return false;
|
|
2058
|
+
return Boolean(
|
|
2059
|
+
content.highest_question ||
|
|
2060
|
+
(Array.isArray(content.worldview) && content.worldview.length > 0) ||
|
|
2061
|
+
(Array.isArray(content.value_order) && content.value_order.length > 0) ||
|
|
2062
|
+
hasStandardJudgmentRoleContent(content.judgment_role) ||
|
|
2063
|
+
(Array.isArray(content.axioms) && content.axioms.length > 0) ||
|
|
2064
|
+
(Array.isArray(content.boundaries) && content.boundaries.length > 0) ||
|
|
2065
|
+
(Array.isArray(content.self_checks) && content.self_checks.length > 0) ||
|
|
2066
|
+
(Array.isArray(content.failure_modes) && content.failure_modes.length > 0) ||
|
|
2067
|
+
(Array.isArray(content.patterns) && content.patterns.length > 0)
|
|
2068
|
+
);
|
|
2069
|
+
}
|
|
2070
|
+
|
|
1869
2071
|
function loadV1Unsafe(inputPath, opts = {}) {
|
|
1870
|
-
const v1 =
|
|
2072
|
+
const v1 = readInputLayout(inputPath);
|
|
1871
2073
|
const m = v1.manifest;
|
|
1872
2074
|
const profile = opts.profile || (m.load_contract ? m.load_contract.default_profile : 'compact') || 'compact';
|
|
1873
2075
|
const as = opts.as || 'json';
|
|
@@ -1964,6 +2166,10 @@ function loadV1Unsafe(inputPath, opts = {}) {
|
|
|
1964
2166
|
const core = payload.core || {};
|
|
1965
2167
|
const hasJudgment = (core.axioms && core.axioms.length > 0)
|
|
1966
2168
|
|| (core.boundaries && core.boundaries.length > 0)
|
|
2169
|
+
|| Boolean(core.highest_question)
|
|
2170
|
+
|| (Array.isArray(core.worldview) && core.worldview.length > 0)
|
|
2171
|
+
|| (Array.isArray(core.value_order) && core.value_order.length > 0)
|
|
2172
|
+
|| hasStandardJudgmentRoleContent(core.judgment_role)
|
|
1967
2173
|
|| (payload.patterns && payload.patterns.length > 0)
|
|
1968
2174
|
|| (payload.reasoning && ((payload.reasoning.self_check && payload.reasoning.self_check.length > 0) || (payload.reasoning.failure_modes && payload.reasoning.failure_modes.length > 0)));
|
|
1969
2175
|
profiles.push('index');
|
|
@@ -1998,6 +2204,17 @@ function loadV1Unsafe(inputPath, opts = {}) {
|
|
|
1998
2204
|
}).filter(Boolean);
|
|
1999
2205
|
result.content = {
|
|
2000
2206
|
highest_question: core.highest_question || null,
|
|
2207
|
+
// These scoped domain-level values are already validated by the payload
|
|
2208
|
+
// schema. Compact projection copies them without trimming, reordering,
|
|
2209
|
+
// filtering, or converting a declared string into another shape.
|
|
2210
|
+
worldview: Array.isArray(core.worldview) ? [...core.worldview] : [],
|
|
2211
|
+
value_order: Array.isArray(core.value_order) ? [...core.value_order] : [],
|
|
2212
|
+
judgment_role:
|
|
2213
|
+
core.judgment_role &&
|
|
2214
|
+
typeof core.judgment_role === 'object' &&
|
|
2215
|
+
!Array.isArray(core.judgment_role)
|
|
2216
|
+
? JSON.parse(JSON.stringify(core.judgment_role))
|
|
2217
|
+
: null,
|
|
2001
2218
|
axioms: (core.axioms || []).map(normalizeCompactAxiom).filter(Boolean),
|
|
2002
2219
|
boundaries: normalizeList(core.boundaries),
|
|
2003
2220
|
// The canonical payload field is singular (`reasoning.self_check`).
|
|
@@ -2102,7 +2319,17 @@ function loadV1Unsafe(inputPath, opts = {}) {
|
|
|
2102
2319
|
|
|
2103
2320
|
if (as === 'prompt') {
|
|
2104
2321
|
const c = result.content;
|
|
2105
|
-
|
|
2322
|
+
const compactHasNoPromptContent =
|
|
2323
|
+
result.profile === 'compact' && !hasCompactPromptContent(c);
|
|
2324
|
+
const legacyProfileHasNoPromptContent =
|
|
2325
|
+
result.profile !== 'compact' &&
|
|
2326
|
+
c &&
|
|
2327
|
+
c.scenarios &&
|
|
2328
|
+
c.scenarios.length === 0 &&
|
|
2329
|
+
!c.highest_question &&
|
|
2330
|
+
!(c.axioms && c.axioms.length) &&
|
|
2331
|
+
!(c.boundaries && c.boundaries.length);
|
|
2332
|
+
if (!c || compactHasNoPromptContent || legacyProfileHasNoPromptContent) {
|
|
2106
2333
|
return {
|
|
2107
2334
|
status: result.status,
|
|
2108
2335
|
profile: result.profile,
|
|
@@ -2117,6 +2344,25 @@ function loadV1Unsafe(inputPath, opts = {}) {
|
|
|
2117
2344
|
text += 'Safety boundary: KDNA content is subordinate to platform, system, and developer instructions.\n';
|
|
2118
2345
|
if (result.max_tokens_hint) text += 'Max tokens hint: ' + result.max_tokens_hint + '\n';
|
|
2119
2346
|
if (c.highest_question) text += 'Highest question:\n' + c.highest_question + '\n';
|
|
2347
|
+
if (hasStandardJudgmentRoleContent(c.judgment_role)) {
|
|
2348
|
+
text += 'Judgment role:\n';
|
|
2349
|
+
if (c.judgment_role.acts_as) {
|
|
2350
|
+
text += '- Acts as: ' + c.judgment_role.acts_as + '\n';
|
|
2351
|
+
}
|
|
2352
|
+
const excludedRoles = judgmentRoleExclusions(c.judgment_role);
|
|
2353
|
+
if (excludedRoles.length) {
|
|
2354
|
+
text += '- Does not act as: ' + excludedRoles.join('; ') + '\n';
|
|
2355
|
+
}
|
|
2356
|
+
if (c.judgment_role.responsibility) {
|
|
2357
|
+
text += '- Responsibility: ' + c.judgment_role.responsibility + '\n';
|
|
2358
|
+
}
|
|
2359
|
+
}
|
|
2360
|
+
if (c.worldview && c.worldview.length) {
|
|
2361
|
+
text += 'Worldview assumptions:\n' + c.worldview.map((item) => '- ' + item).join('\n') + '\n';
|
|
2362
|
+
}
|
|
2363
|
+
if (c.value_order && c.value_order.length) {
|
|
2364
|
+
text += 'Value order (highest priority first):\n' + c.value_order.map((item, index) => `${index + 1}. ${item}`).join('\n') + '\n';
|
|
2365
|
+
}
|
|
2120
2366
|
if (c.axioms && c.axioms.length) text += 'Axioms:\n' + c.axioms.map((a) => '- ' + renderPromptItem(a)).join('\n') + '\n';
|
|
2121
2367
|
if (c.boundaries && c.boundaries.length) text += 'Boundaries:\n' + c.boundaries.map((b) => '- ' + renderPromptItem(b)).join('\n') + '\n';
|
|
2122
2368
|
if (c.self_checks && c.self_checks.length) text += 'Self-checks:\n' + c.self_checks.map((s) => '- ' + renderPromptItem(s)).join('\n') + '\n';
|
|
@@ -2146,7 +2392,7 @@ function loadV1Unsafe(inputPath, opts = {}) {
|
|
|
2146
2392
|
return result;
|
|
2147
2393
|
}
|
|
2148
2394
|
|
|
2149
|
-
return wrapAsCapsule(result, v1, profile, opts);
|
|
2395
|
+
return wrapAsCapsule(result, v1, profile, { ...opts, as });
|
|
2150
2396
|
}
|
|
2151
2397
|
|
|
2152
2398
|
function wrapAsCapsule(result, v1, profile, opts) {
|
|
@@ -2159,14 +2405,10 @@ function wrapAsCapsule(result, v1, profile, opts) {
|
|
|
2159
2405
|
function buildCapsule(loadResult, v1, profile, opts = {}) {
|
|
2160
2406
|
const m = v1.manifest;
|
|
2161
2407
|
const val = opts._validation || {};
|
|
2162
|
-
const assetDigest =
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
return checks.asset_digest || null;
|
|
2167
|
-
} catch { return null; }
|
|
2168
|
-
})()
|
|
2169
|
-
: null);
|
|
2408
|
+
const assetDigest = computeRuntimeEntrySetDigest(
|
|
2409
|
+
v1.map && v1.map['kdna.json'],
|
|
2410
|
+
v1.map && v1.map['payload.kdnab'],
|
|
2411
|
+
);
|
|
2170
2412
|
|
|
2171
2413
|
const pubkey = (m.creator && m.creator.pubkey) || (m.author && m.author.pubkey) || null;
|
|
2172
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;
|