@aikdna/kdna-core 0.18.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 +128 -94
- package/README.md +109 -215
- package/package.json +7 -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 +8 -8
- package/schema/consumption-plan.schema.json +428 -0
- package/schema/digest-evidence.schema.json +115 -26
- 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 +5 -3
- package/schema/manifest.schema.json +70 -10
- package/schema/{payload-profile-v1.schema.json → payload-profile.schema.json} +30 -6
- package/schema/{runtime-capsule-2.schema.json → runtime-capsule.schema.json} +7 -31
- package/src/asset-reader.js +8 -2
- package/src/{v1 → container}/index.js +385 -198
- package/src/container/index.mjs +20 -0
- package/src/container-dispatcher.js +6 -6
- package/src/crypto-profile.js +37 -107
- package/src/external-key-grant.js +41 -15
- package/src/index.js +11 -8
- package/src/index.mjs +49 -45
- package/src/loader-compatibility.js +59 -0
- package/src/loader.js +1 -1
- package/src/public-api.js +5 -5
- 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 +5 -5
- package/src/{capsule-v2.js → runtime-capsule.js} +96 -254
- package/src/{execution-contract-v1.js → runtime-contract.js} +184 -152
- package/src/types.d.ts +200 -277
- package/src/workpack-pure.js +1 -1
- package/schema/agent-host-2-receipt.schema.json +0 -206
- package/schema/agent-host-2-request.schema.json +0 -135
- package/schema/agent-host-capabilities-1.schema.json +0 -40
- package/schema/consumption-plan-1.schema.json +0 -280
- package/schema/judgment-trace-1.schema.json +0 -748
- package/schema/runtime-capsule-1.schema.json +0 -68
- package/src/lint-pure.js +0 -433
- package/src/v1/index.mjs +0 -21
|
@@ -20,7 +20,10 @@
|
|
|
20
20
|
* - the source directory must contain mimetype, kdna.json, payload.kdnab
|
|
21
21
|
* - checksums.json and signatures/ are optional
|
|
22
22
|
* - lineage must be a single object (not an array)
|
|
23
|
-
* - pack output
|
|
23
|
+
* - pack output is reproducible within one pinned toolchain/compressor;
|
|
24
|
+
* DEFLATE bytes may differ across compressors, zlib versions, or systems
|
|
25
|
+
* - entry_set_digest/source_fingerprint identify logical entries; A binds
|
|
26
|
+
* the exact immutable package bytes
|
|
24
27
|
*
|
|
25
28
|
* Output language must stay content-neutral. We never say "trusted",
|
|
26
29
|
* "recommended", "high_quality", or "officially_approved". We say
|
|
@@ -37,6 +40,11 @@ const path = require('node:path');
|
|
|
37
40
|
const zlib = require('node:zlib');
|
|
38
41
|
const crypto = require('node:crypto');
|
|
39
42
|
const cbor = require('cbor-x');
|
|
43
|
+
const {
|
|
44
|
+
KDNA_LOADER_VERSION,
|
|
45
|
+
assessLoaderCompatibility,
|
|
46
|
+
loaderVersionUnsupportedMessage,
|
|
47
|
+
} = require('../loader-compatibility');
|
|
40
48
|
const {
|
|
41
49
|
EXTERNAL_ENVELOPE_PROFILE,
|
|
42
50
|
validateExternalEnvelope,
|
|
@@ -67,12 +75,6 @@ const FORBIDDEN_LEGACY_TOP_LEVEL = new Set([
|
|
|
67
75
|
'KDNA_Evolution.json',
|
|
68
76
|
]);
|
|
69
77
|
|
|
70
|
-
// Legacy aliases — prefer the unversioned names above
|
|
71
|
-
const V1_REQUIRED_DIR_ENTRIES = REQUIRED_DIR_ENTRIES;
|
|
72
|
-
const V1_OPTIONAL_DIR_ENTRIES = OPTIONAL_DIR_ENTRIES;
|
|
73
|
-
const V1_ALLOWED_TOP_LEVEL_ENTRIES = ALLOWED_TOP_LEVEL_ENTRIES;
|
|
74
|
-
const V1_FORBIDDEN_LEGACY_TOP_LEVEL = FORBIDDEN_LEGACY_TOP_LEVEL;
|
|
75
|
-
|
|
76
78
|
const DEFAULT_CONTAINER_LIMITS = Object.freeze({
|
|
77
79
|
maxContainerBytes: 25 * 1024 * 1024,
|
|
78
80
|
maxEntries: 128,
|
|
@@ -84,7 +86,7 @@ const DEFAULT_CONTAINER_LIMITS = Object.freeze({
|
|
|
84
86
|
maxJsonStringLength: 1024 * 1024,
|
|
85
87
|
});
|
|
86
88
|
|
|
87
|
-
// Words that must never appear in
|
|
89
|
+
// Words that must never appear in layout CLI output as positive claims.
|
|
88
90
|
// Schema-valid, signature-valid, compatible — those are fine.
|
|
89
91
|
// "trusted", "recommended", "high_quality", "officially_approved" — never.
|
|
90
92
|
const FORBIDDEN_OUTPUT_TERMS = Object.freeze([
|
|
@@ -103,8 +105,8 @@ let _validators = null;
|
|
|
103
105
|
function loadPackagedSchemas() {
|
|
104
106
|
return {
|
|
105
107
|
manifestSchema: require('../../schema/manifest.schema.json'),
|
|
106
|
-
payloadSchema: require('../../schema/payload-profile
|
|
107
|
-
bundlePayloadSchema: require('../../schema/bundle-profile
|
|
108
|
+
payloadSchema: require('../../schema/payload-profile.schema.json'),
|
|
109
|
+
bundlePayloadSchema: require('../../schema/bundle-profile.schema.json'),
|
|
108
110
|
checksumsSchema: require('../../schema/checksums.schema.json'),
|
|
109
111
|
loadContractSchema: require('../../schema/load-contract.schema.json'),
|
|
110
112
|
};
|
|
@@ -131,10 +133,10 @@ function loadSchemasFromDisk() {
|
|
|
131
133
|
return {
|
|
132
134
|
manifestSchema: JSON.parse(fs.readFileSync(path.join(schemaDir, 'manifest.schema.json'), 'utf8')),
|
|
133
135
|
payloadSchema: JSON.parse(
|
|
134
|
-
fs.readFileSync(path.join(schemaDir, 'payload-profile
|
|
136
|
+
fs.readFileSync(path.join(schemaDir, 'payload-profile.schema.json'), 'utf8'),
|
|
135
137
|
),
|
|
136
138
|
bundlePayloadSchema: JSON.parse(
|
|
137
|
-
fs.readFileSync(path.join(schemaDir, 'bundle-profile
|
|
139
|
+
fs.readFileSync(path.join(schemaDir, 'bundle-profile.schema.json'), 'utf8'),
|
|
138
140
|
),
|
|
139
141
|
checksumsSchema: JSON.parse(
|
|
140
142
|
fs.readFileSync(path.join(schemaDir, 'checksums.schema.json'), 'utf8'),
|
|
@@ -187,7 +189,7 @@ function loadSchemas() {
|
|
|
187
189
|
*/
|
|
188
190
|
function isKdnaSourceDir(absPath) {
|
|
189
191
|
if (!fs.existsSync(absPath) || !fs.statSync(absPath).isDirectory()) return false;
|
|
190
|
-
for (const f of
|
|
192
|
+
for (const f of REQUIRED_DIR_ENTRIES) {
|
|
191
193
|
if (!fs.existsSync(path.join(absPath, f))) return false;
|
|
192
194
|
}
|
|
193
195
|
const mime = fs.readFileSync(path.join(absPath, 'mimetype'), 'utf8');
|
|
@@ -397,8 +399,8 @@ function validateContainerEntryMetadata(entry) {
|
|
|
397
399
|
throw new Error(`container has duplicate entry: ${normalizedName}`);
|
|
398
400
|
}
|
|
399
401
|
const topLevel = normalizedName.split('/')[0];
|
|
400
|
-
if (!
|
|
401
|
-
if (
|
|
402
|
+
if (!ALLOWED_TOP_LEVEL_ENTRIES.has(topLevel)) {
|
|
403
|
+
if (FORBIDDEN_LEGACY_TOP_LEVEL.has(topLevel)) {
|
|
402
404
|
throw new Error(`container includes forbidden top-level source entry: ${topLevel}`);
|
|
403
405
|
}
|
|
404
406
|
throw new Error(`container includes unsupported top-level entry: ${topLevel}`);
|
|
@@ -516,17 +518,17 @@ function listSourceDir(dir, opts = {}) {
|
|
|
516
518
|
return out;
|
|
517
519
|
}
|
|
518
520
|
|
|
519
|
-
// ─── Read
|
|
521
|
+
// ─── Read layout from either source dir or container ───────────────────────
|
|
520
522
|
|
|
521
523
|
/**
|
|
522
|
-
* Read a
|
|
524
|
+
* Read a layout layout (source dir or .kdna container) and return a single
|
|
523
525
|
* normalized map of { mimetype, kdna.json, payload.kdnab, checksums.json? }.
|
|
524
526
|
* `where` describes the origin for error messages.
|
|
525
527
|
*
|
|
526
528
|
* Throws an Error with a clear, content-neutral message if the layout
|
|
527
529
|
* is malformed (missing entry, wrong mimetype, etc.).
|
|
528
530
|
*/
|
|
529
|
-
function
|
|
531
|
+
function readLayout(absPath) {
|
|
530
532
|
let stat;
|
|
531
533
|
try {
|
|
532
534
|
stat = fs.statSync(absPath);
|
|
@@ -541,7 +543,7 @@ function readV1Layout(absPath) {
|
|
|
541
543
|
|
|
542
544
|
if (stat.isDirectory()) {
|
|
543
545
|
kind = 'dir';
|
|
544
|
-
for (const f of
|
|
546
|
+
for (const f of REQUIRED_DIR_ENTRIES) {
|
|
545
547
|
const full = path.join(absPath, f);
|
|
546
548
|
if (!fs.existsSync(full)) {
|
|
547
549
|
throw new Error(`not a KDNA authoring source directory: missing ${f}`);
|
|
@@ -550,7 +552,7 @@ function readV1Layout(absPath) {
|
|
|
550
552
|
throw new Error(`not a KDNA authoring source directory: ${f} must not be a symlink`);
|
|
551
553
|
}
|
|
552
554
|
}
|
|
553
|
-
for (const f of [...
|
|
555
|
+
for (const f of [...REQUIRED_DIR_ENTRIES, ...OPTIONAL_DIR_ENTRIES]) {
|
|
554
556
|
const full = path.join(absPath, f);
|
|
555
557
|
if (fs.existsSync(full)) {
|
|
556
558
|
if (fs.statSync(full).isFile()) {
|
|
@@ -587,17 +589,17 @@ function readV1Layout(absPath) {
|
|
|
587
589
|
? `sha256:${crypto.createHash('sha256').update(containerBytes).digest('hex')}`
|
|
588
590
|
: null;
|
|
589
591
|
|
|
590
|
-
return
|
|
592
|
+
return finalizeContainerLayout({ kind, map, entries, containerDigest });
|
|
591
593
|
}
|
|
592
594
|
|
|
593
|
-
function
|
|
595
|
+
function finalizeContainerLayout({ kind, map, entries, containerDigest }) {
|
|
594
596
|
if (kind !== 'dir' && (!entries || entries.length === 0 || entries[0].name !== 'mimetype')) {
|
|
595
597
|
throw new Error('not a KDNA Asset Container: first entry is not mimetype');
|
|
596
598
|
}
|
|
597
599
|
if (kind !== 'dir' && entries[0].method !== 0) {
|
|
598
600
|
throw new Error('not a KDNA Asset Container: mimetype must be uncompressed');
|
|
599
601
|
}
|
|
600
|
-
for (const required of
|
|
602
|
+
for (const required of REQUIRED_DIR_ENTRIES) {
|
|
601
603
|
if (!map[required]) {
|
|
602
604
|
const source = kind === 'dir' ? 'KDNA authoring source directory' : 'KDNA Asset Container';
|
|
603
605
|
throw new Error(`not a ${source}: missing ${required}`);
|
|
@@ -622,7 +624,7 @@ function finalizeV1Layout({ kind, map, entries, containerDigest }) {
|
|
|
622
624
|
return { kind, map, manifest, entries, containerDigest };
|
|
623
625
|
}
|
|
624
626
|
|
|
625
|
-
function
|
|
627
|
+
function readLayoutBytes(input) {
|
|
626
628
|
const bytes = Buffer.isBuffer(input) ? input : Buffer.from(input);
|
|
627
629
|
const entries = listZipEntriesFromBuffer(bytes);
|
|
628
630
|
const map = {};
|
|
@@ -636,7 +638,7 @@ function readV1LayoutBytes(input) {
|
|
|
636
638
|
map[entry.name] = entry.data;
|
|
637
639
|
}
|
|
638
640
|
}
|
|
639
|
-
return
|
|
641
|
+
return finalizeContainerLayout({
|
|
640
642
|
kind: 'memory',
|
|
641
643
|
map,
|
|
642
644
|
entries,
|
|
@@ -646,9 +648,9 @@ function readV1LayoutBytes(input) {
|
|
|
646
648
|
|
|
647
649
|
function readInputLayout(input) {
|
|
648
650
|
if (Buffer.isBuffer(input) || input instanceof Uint8Array) {
|
|
649
|
-
return
|
|
651
|
+
return readLayoutBytes(input);
|
|
650
652
|
}
|
|
651
|
-
return
|
|
653
|
+
return readLayout(path.resolve(input));
|
|
652
654
|
}
|
|
653
655
|
|
|
654
656
|
function snapshotPackagedInput(input) {
|
|
@@ -724,7 +726,7 @@ function parsePayloadEntry(name, bytes, opts = {}) {
|
|
|
724
726
|
try {
|
|
725
727
|
parsed = cbor.decode(bytes);
|
|
726
728
|
} catch {
|
|
727
|
-
throw new Error(`${name}: not valid CBOR. payload.kdnab must be CBOR-encoded (
|
|
729
|
+
throw new Error(`${name}: not valid CBOR. payload.kdnab must be CBOR-encoded (format 0.1.0).`);
|
|
728
730
|
}
|
|
729
731
|
assertJsonWithinLimits(parsed, name, limits);
|
|
730
732
|
return parsed;
|
|
@@ -736,10 +738,10 @@ function parsePayloadEntry(name, bytes, opts = {}) {
|
|
|
736
738
|
* Print a content-neutral manifest summary. Always JSON. Never emits
|
|
737
739
|
* the words trusted / recommended / high_quality / officially_approved.
|
|
738
740
|
*/
|
|
739
|
-
function buildInspectOutput(
|
|
740
|
-
const m =
|
|
741
|
+
function buildInspectOutput(container) {
|
|
742
|
+
const m = container.manifest;
|
|
741
743
|
const out = {
|
|
742
|
-
|
|
744
|
+
format_version: m.format_version ?? null,
|
|
743
745
|
asset_id: m.asset_id ?? null,
|
|
744
746
|
asset_uid: m.asset_uid ?? null,
|
|
745
747
|
asset_type: m.asset_type ?? null,
|
|
@@ -749,10 +751,11 @@ function buildInspectOutput(v1) {
|
|
|
749
751
|
payload: m.payload ? m.payload.path : null,
|
|
750
752
|
payload_encrypted: m.payload ? m.payload.encrypted : null,
|
|
751
753
|
profile: m.compatibility ? m.compatibility.profile : null,
|
|
754
|
+
...assessLoaderCompatibility(m),
|
|
752
755
|
load_contract_default_profile: m.load_contract ? m.load_contract.default_profile : null,
|
|
753
756
|
};
|
|
754
757
|
if (m.signatures !== undefined) out.signature_count = Array.isArray(m.signatures) ? m.signatures.length : 0;
|
|
755
|
-
if (
|
|
758
|
+
if (container.map['checksums.json']) out.checksums_present = true;
|
|
756
759
|
return out;
|
|
757
760
|
}
|
|
758
761
|
|
|
@@ -763,26 +766,27 @@ function buildInspectOutput(v1) {
|
|
|
763
766
|
* reports each gate independently. Never includes trust / recommended
|
|
764
767
|
* / high_quality / officially_approved as a positive claim.
|
|
765
768
|
*/
|
|
766
|
-
function runValidate(
|
|
769
|
+
function runValidate(layout) {
|
|
767
770
|
const result = {
|
|
768
771
|
format_valid: true,
|
|
769
772
|
schema_valid: true,
|
|
770
773
|
payload_valid: true,
|
|
771
774
|
checksums_valid: true,
|
|
772
775
|
load_contract_valid: true,
|
|
776
|
+
...assessLoaderCompatibility(layout.manifest),
|
|
773
777
|
};
|
|
774
778
|
const problems = [];
|
|
775
779
|
|
|
776
|
-
// format gate — already proven by
|
|
780
|
+
// format gate — already proven by readLayout, but we re-state the gates
|
|
777
781
|
// so the report matches the spec.
|
|
778
|
-
for (const f of
|
|
779
|
-
if (!
|
|
782
|
+
for (const f of REQUIRED_DIR_ENTRIES) {
|
|
783
|
+
if (!layout.map[f]) {
|
|
780
784
|
result.format_valid = false;
|
|
781
785
|
problems.push(`format: missing required entry ${f}`);
|
|
782
786
|
}
|
|
783
787
|
}
|
|
784
|
-
if (
|
|
785
|
-
const mimeStr =
|
|
788
|
+
if (layout.map.mimetype) {
|
|
789
|
+
const mimeStr = layout.map.mimetype.toString('utf8');
|
|
786
790
|
if (mimeStr !== MIMETYPE) {
|
|
787
791
|
result.format_valid = false;
|
|
788
792
|
problems.push(`format: mimetype is not ${MIMETYPE}`);
|
|
@@ -798,7 +802,7 @@ function runValidate(v1) {
|
|
|
798
802
|
);
|
|
799
803
|
return finalizeValidate(result, problems);
|
|
800
804
|
}
|
|
801
|
-
if (!validators.manifest(
|
|
805
|
+
if (!validators.manifest(layout.manifest)) {
|
|
802
806
|
result.schema_valid = false;
|
|
803
807
|
for (const err of validators.manifest.errors) {
|
|
804
808
|
problems.push(`manifest: ${err.instancePath || '<root>'} ${err.message}`);
|
|
@@ -806,42 +810,74 @@ function runValidate(v1) {
|
|
|
806
810
|
}
|
|
807
811
|
|
|
808
812
|
// Known pre-1.0 aliases are not extension fields. Rejecting them here keeps
|
|
809
|
-
// the current
|
|
813
|
+
// the current layout validator as the single manifest authority while allowing
|
|
810
814
|
// genuinely namespaced/forward-compatible manifest extensions.
|
|
811
815
|
const removedAliases = {
|
|
812
|
-
kdna_spec: '
|
|
813
|
-
|
|
814
|
-
spec_version: '
|
|
816
|
+
kdna_spec: 'format_version',
|
|
817
|
+
kdna_version: 'format_version',
|
|
818
|
+
spec_version: 'format_version',
|
|
815
819
|
};
|
|
816
820
|
for (const [field, replacement] of Object.entries(removedAliases)) {
|
|
817
|
-
if (Object.prototype.hasOwnProperty.call(
|
|
821
|
+
if (Object.prototype.hasOwnProperty.call(layout.manifest, field)) {
|
|
818
822
|
result.schema_valid = false;
|
|
819
823
|
problems.push(`kdna.json: ${field} is not allowed. Use ${replacement}.`);
|
|
820
824
|
}
|
|
821
825
|
}
|
|
822
826
|
|
|
823
|
-
// payload gate — payload.kdnab against payload-profile
|
|
827
|
+
// payload gate — payload.kdnab against payload-profile.schema.json
|
|
824
828
|
// For actually encrypted payloads (envelopes), skip schema validation.
|
|
825
829
|
let payload;
|
|
826
830
|
try {
|
|
827
|
-
payload = parsePayloadEntry("payload.kdnab",
|
|
831
|
+
payload = parsePayloadEntry("payload.kdnab", layout.map['payload.kdnab']);
|
|
828
832
|
} catch (e) {
|
|
829
833
|
result.payload_valid = false;
|
|
830
834
|
problems.push(`payload: not valid KDNA payload (${e.message})`);
|
|
831
835
|
return finalizeValidate(result, problems);
|
|
832
836
|
}
|
|
833
837
|
|
|
834
|
-
const
|
|
835
|
-
|
|
836
|
-
|
|
838
|
+
const payloadIsEncryptedEnvelope = Boolean(payload.profile && payload.ciphertext);
|
|
839
|
+
const encryptedEntries = layout.manifest.encryption?.encrypted_entries;
|
|
840
|
+
const manifestDeclaresEncryptedPayload = Boolean(
|
|
841
|
+
layout.manifest.payload?.encrypted
|
|
842
|
+
|| (Array.isArray(encryptedEntries) && encryptedEntries.includes('payload.kdnab')),
|
|
843
|
+
);
|
|
844
|
+
if (manifestDeclaresEncryptedPayload && !payloadIsEncryptedEnvelope) {
|
|
845
|
+
result.payload_valid = false;
|
|
846
|
+
problems.push('payload: manifest declares encryption but payload.kdnab is not an encrypted envelope');
|
|
847
|
+
}
|
|
848
|
+
if (payloadIsEncryptedEnvelope && !manifestDeclaresEncryptedPayload) {
|
|
849
|
+
result.payload_valid = false;
|
|
850
|
+
problems.push('payload: encrypted envelope is missing its manifest encryption declaration');
|
|
851
|
+
}
|
|
852
|
+
const isEncryptedPayload = payloadIsEncryptedEnvelope && manifestDeclaresEncryptedPayload;
|
|
837
853
|
|
|
838
854
|
if (isEncryptedPayload) {
|
|
839
855
|
// Encrypted payload — verify it's a proper encryption envelope, not plaintext.
|
|
840
|
-
const encProfile =
|
|
856
|
+
const encProfile = layout.manifest.encryption?.profile;
|
|
857
|
+
const encProfileVersion = layout.manifest.encryption?.profile_version;
|
|
858
|
+
if (!layout.manifest.encryption) {
|
|
859
|
+
result.payload_valid = false;
|
|
860
|
+
problems.push('payload: encrypted payload requires a manifest encryption declaration');
|
|
861
|
+
}
|
|
862
|
+
if (
|
|
863
|
+
!Array.isArray(encryptedEntries)
|
|
864
|
+
|| encryptedEntries.length !== 1
|
|
865
|
+
|| encryptedEntries[0] !== 'payload.kdnab'
|
|
866
|
+
) {
|
|
867
|
+
result.payload_valid = false;
|
|
868
|
+
problems.push('payload: manifest encrypted_entries must declare only payload.kdnab');
|
|
869
|
+
}
|
|
841
870
|
if (encProfile && payload.profile !== encProfile) {
|
|
842
871
|
result.payload_valid = false;
|
|
843
872
|
problems.push(`payload: encrypted envelope profile ${payload.profile || 'unknown'} does not match manifest encryption profile ${encProfile}`);
|
|
844
873
|
}
|
|
874
|
+
const payloadProfileVersion = payload.profile === EXTERNAL_ENVELOPE_PROFILE
|
|
875
|
+
? payload.contract_version
|
|
876
|
+
: payload.profile_version;
|
|
877
|
+
if (encProfileVersion && payloadProfileVersion !== encProfileVersion) {
|
|
878
|
+
result.payload_valid = false;
|
|
879
|
+
problems.push(`payload: encrypted envelope compatibility coordinate ${payloadProfileVersion || 'unknown'} does not match manifest encryption profile_version ${encProfileVersion}`);
|
|
880
|
+
}
|
|
845
881
|
let hasKeyMaterial = !!payload.wrapped_key || (Array.isArray(payload.key_slots) && payload.key_slots.length > 0);
|
|
846
882
|
if (payload.profile === EXTERNAL_ENVELOPE_PROFILE) {
|
|
847
883
|
try {
|
|
@@ -859,7 +895,7 @@ function runValidate(v1) {
|
|
|
859
895
|
// Encrypted payload passes payload gate if envelope is structurally valid.
|
|
860
896
|
} else {
|
|
861
897
|
// Plaintext payload — full schema validation.
|
|
862
|
-
const isBundleProfile =
|
|
898
|
+
const isBundleProfile = layout.manifest.compatibility?.profile === 'kdna.payload.bundle' || layout.manifest.asset_type === 'bundle';
|
|
863
899
|
const payloadValidator = isBundleProfile ? validators.bundlePayload : validators.payload;
|
|
864
900
|
if (!payloadValidator(payload)) {
|
|
865
901
|
result.payload_valid = false;
|
|
@@ -870,10 +906,10 @@ function runValidate(v1) {
|
|
|
870
906
|
}
|
|
871
907
|
|
|
872
908
|
// checksums gate — checksums.json against checksums.schema.json
|
|
873
|
-
if (
|
|
909
|
+
if (layout.map['checksums.json']) {
|
|
874
910
|
let checks;
|
|
875
911
|
try {
|
|
876
|
-
checks = parseJsonEntry('checksums.json',
|
|
912
|
+
checks = parseJsonEntry('checksums.json', layout.map['checksums.json']);
|
|
877
913
|
} catch (e) {
|
|
878
914
|
result.checksums_valid = false;
|
|
879
915
|
problems.push(`checksums: not valid JSON (${e.message})`);
|
|
@@ -886,13 +922,13 @@ function runValidate(v1) {
|
|
|
886
922
|
}
|
|
887
923
|
// Digest matching verification — compute actual hashes and compare.
|
|
888
924
|
if (checks) {
|
|
889
|
-
result.checksums_valid = verifyDigests(checks,
|
|
925
|
+
result.checksums_valid = verifyDigests(checks, layout.map, problems, result);
|
|
890
926
|
}
|
|
891
927
|
}
|
|
892
928
|
|
|
893
929
|
// load_contract gate — only if manifest references a load_contract block
|
|
894
|
-
if (
|
|
895
|
-
const lc =
|
|
930
|
+
if (layout.manifest.load_contract) {
|
|
931
|
+
const lc = layout.manifest.load_contract;
|
|
896
932
|
const validLc = _ajv.getSchema('load-contract.schema.json');
|
|
897
933
|
if (validLc && !validLc(lc)) {
|
|
898
934
|
result.load_contract_valid = false;
|
|
@@ -910,10 +946,13 @@ function runValidate(v1) {
|
|
|
910
946
|
|
|
911
947
|
function verifyDigests(checksums, map, problems, result) {
|
|
912
948
|
const digestMetadataPresent =
|
|
913
|
-
checksums.digest_profile !== undefined ||
|
|
949
|
+
checksums.digest_profile !== undefined ||
|
|
950
|
+
checksums.digest_profile_version !== undefined ||
|
|
951
|
+
checksums.covered_entries !== undefined;
|
|
914
952
|
if (digestMetadataPresent) {
|
|
915
953
|
const requiredDigestFields = [
|
|
916
954
|
'digest_profile',
|
|
955
|
+
'digest_profile_version',
|
|
917
956
|
'covered_entries',
|
|
918
957
|
'manifest_digest',
|
|
919
958
|
'payload_digest',
|
|
@@ -924,13 +963,18 @@ function verifyDigests(checksums, map, problems, result) {
|
|
|
924
963
|
);
|
|
925
964
|
if (missingDigestFields.length) {
|
|
926
965
|
problems.push(
|
|
927
|
-
`checksums: kdna-runtime-entry-set
|
|
966
|
+
`checksums: kdna.digest-basis.runtime-entry-set metadata requires ${missingDigestFields.join(', ')}`,
|
|
928
967
|
);
|
|
929
968
|
result.checksums_valid = false;
|
|
930
969
|
return false;
|
|
931
970
|
}
|
|
932
|
-
if (checksums.digest_profile !== 'kdna-runtime-entry-set
|
|
933
|
-
problems.push('checksums: unsupported digest_profile (expected kdna-runtime-entry-set
|
|
971
|
+
if (checksums.digest_profile !== 'kdna.digest-basis.runtime-entry-set') {
|
|
972
|
+
problems.push('checksums: unsupported digest_profile (expected kdna.digest-basis.runtime-entry-set)');
|
|
973
|
+
result.checksums_valid = false;
|
|
974
|
+
return false;
|
|
975
|
+
}
|
|
976
|
+
if (checksums.digest_profile_version !== '0.1.0') {
|
|
977
|
+
problems.push('checksums: unsupported digest_profile_version (expected 0.1.0)');
|
|
934
978
|
result.checksums_valid = false;
|
|
935
979
|
return false;
|
|
936
980
|
}
|
|
@@ -941,7 +985,7 @@ function verifyDigests(checksums, map, problems, result) {
|
|
|
941
985
|
|| coveredEntries[0] !== 'kdna.json'
|
|
942
986
|
|| coveredEntries[1] !== 'payload.kdnab'
|
|
943
987
|
) {
|
|
944
|
-
problems.push('checksums: covered_entries does not match kdna-runtime-entry-set
|
|
988
|
+
problems.push('checksums: covered_entries does not match kdna.digest-basis.runtime-entry-set');
|
|
945
989
|
result.checksums_valid = false;
|
|
946
990
|
return false;
|
|
947
991
|
}
|
|
@@ -975,21 +1019,12 @@ function verifyDigests(checksums, map, problems, result) {
|
|
|
975
1019
|
stillValid = false;
|
|
976
1020
|
}
|
|
977
1021
|
}
|
|
978
|
-
|
|
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;
|
|
1022
|
+
const declaredEntrySetDigest = checksums.entry_set_digest;
|
|
987
1023
|
if (declaredEntrySetDigest) {
|
|
988
|
-
const digestLabel = checksums.entry_set_digest ? 'entry_set_digest' : 'asset_digest';
|
|
989
1024
|
const expectedEntrySetDigest = declaredEntrySetDigest.replace(/^sha256:/, '');
|
|
990
1025
|
const missingAssetInputs = ['kdna.json', 'payload.kdnab'].filter((entryName) => !entryDigests[entryName]);
|
|
991
1026
|
if (missingAssetInputs.length) {
|
|
992
|
-
problems.push(`checksums:
|
|
1027
|
+
problems.push(`checksums: entry_set_digest cannot be verified without ${missingAssetInputs.join(', ')}`);
|
|
993
1028
|
stillValid = false;
|
|
994
1029
|
} else {
|
|
995
1030
|
const actualEntrySetDigest = computeRuntimeEntrySetDigest(
|
|
@@ -998,7 +1033,7 @@ function verifyDigests(checksums, map, problems, result) {
|
|
|
998
1033
|
).slice('sha256:'.length);
|
|
999
1034
|
if (actualEntrySetDigest !== expectedEntrySetDigest) {
|
|
1000
1035
|
problems.push(
|
|
1001
|
-
`checksums:
|
|
1036
|
+
`checksums: entry_set_digest mismatch (declared ${expectedEntrySetDigest.slice(0, 8)}..., actual ${actualEntrySetDigest.slice(0, 8)}...)`,
|
|
1002
1037
|
);
|
|
1003
1038
|
stillValid = false;
|
|
1004
1039
|
}
|
|
@@ -1053,7 +1088,7 @@ function computeRuntimeEntrySetDigest(manifestBytes, payloadBytes) {
|
|
|
1053
1088
|
return `sha256:${crypto.createHash('sha256').update(combined).digest('hex')}`;
|
|
1054
1089
|
}
|
|
1055
1090
|
|
|
1056
|
-
function
|
|
1091
|
+
function buildChecksums(sourceDir) {
|
|
1057
1092
|
const absSrc = path.resolve(sourceDir);
|
|
1058
1093
|
if (!fs.existsSync(absSrc) || !fs.statSync(absSrc).isDirectory()) {
|
|
1059
1094
|
throw new Error(`not a directory: ${absSrc}`);
|
|
@@ -1068,13 +1103,13 @@ function buildChecksumsV1(sourceDir) {
|
|
|
1068
1103
|
fs.readFileSync(path.join(absSrc, 'payload.kdnab')),
|
|
1069
1104
|
);
|
|
1070
1105
|
return {
|
|
1071
|
-
digest_profile: 'kdna-runtime-entry-set
|
|
1106
|
+
digest_profile: 'kdna.digest-basis.runtime-entry-set',
|
|
1107
|
+
digest_profile_version: '0.1.0',
|
|
1072
1108
|
covered_entries: ['kdna.json', 'payload.kdnab'],
|
|
1073
1109
|
algorithm: 'sha256',
|
|
1074
1110
|
manifest_digest: `sha256:${entries['kdna.json'].value}`,
|
|
1075
1111
|
payload_digest: `sha256:${entries['payload.kdnab'].value}`,
|
|
1076
1112
|
entry_set_digest: entrySetDigest,
|
|
1077
|
-
asset_digest: entrySetDigest,
|
|
1078
1113
|
entries,
|
|
1079
1114
|
};
|
|
1080
1115
|
}
|
|
@@ -1082,17 +1117,19 @@ function buildChecksumsV1(sourceDir) {
|
|
|
1082
1117
|
// ─── pack ──────────────────────────────────────────────────────────────
|
|
1083
1118
|
|
|
1084
1119
|
/**
|
|
1085
|
-
* Pack an authoring source directory into a .kdna asset.
|
|
1086
|
-
*
|
|
1087
|
-
*
|
|
1088
|
-
*
|
|
1120
|
+
* Pack an authoring source directory into a .kdna asset. With one pinned
|
|
1121
|
+
* packer toolchain and compressor, output is byte-reproducible (fixed DOS
|
|
1122
|
+
* timestamps, fixed entry order, mimetype first). DEFLATE output may differ
|
|
1123
|
+
* across compressors, zlib versions, or systems. entry_set_digest and
|
|
1124
|
+
* source_fingerprint identify the logical entries; A binds the exact immutable
|
|
1125
|
+
* package bytes.
|
|
1089
1126
|
*/
|
|
1090
1127
|
function pack(sourceDir, outputPath) {
|
|
1091
1128
|
const absSrc = path.resolve(sourceDir);
|
|
1092
1129
|
if (!fs.existsSync(absSrc) || !fs.statSync(absSrc).isDirectory()) {
|
|
1093
1130
|
throw new Error(`not a directory: ${absSrc}`);
|
|
1094
1131
|
}
|
|
1095
|
-
for (const f of
|
|
1132
|
+
for (const f of REQUIRED_DIR_ENTRIES) {
|
|
1096
1133
|
if (!fs.existsSync(path.join(absSrc, f))) {
|
|
1097
1134
|
throw new Error(`cannot pack: missing required entry ${f}`);
|
|
1098
1135
|
}
|
|
@@ -1171,7 +1208,7 @@ function pack(sourceDir, outputPath) {
|
|
|
1171
1208
|
// ─── unpack ────────────────────────────────────────────────────────────
|
|
1172
1209
|
|
|
1173
1210
|
/**
|
|
1174
|
-
* Unpack a
|
|
1211
|
+
* Unpack a layout .kdna container to a directory. Refuses path traversal.
|
|
1175
1212
|
* Does not auto-execute any entry.
|
|
1176
1213
|
*/
|
|
1177
1214
|
function unpack(inputPath, outputDir) {
|
|
@@ -1212,23 +1249,20 @@ function unpack(inputPath, outputDir) {
|
|
|
1212
1249
|
// ─── Public router entry points ────────────────────────────────────────
|
|
1213
1250
|
|
|
1214
1251
|
function inspect(inputPath, _opts = {}) {
|
|
1215
|
-
const
|
|
1216
|
-
const out = buildInspectOutput(
|
|
1252
|
+
const layout = readInputLayout(inputPath);
|
|
1253
|
+
const out = buildInspectOutput(layout);
|
|
1217
1254
|
// Guard against accidental forbidden wording in any future field additions.
|
|
1218
1255
|
assertNoForbiddenTerms(out);
|
|
1219
1256
|
return out;
|
|
1220
1257
|
}
|
|
1221
1258
|
|
|
1222
1259
|
function validate(inputPath, _opts = {}) {
|
|
1223
|
-
const
|
|
1224
|
-
return runValidate(
|
|
1260
|
+
const layout = readInputLayout(inputPath);
|
|
1261
|
+
return runValidate(layout);
|
|
1225
1262
|
}
|
|
1226
1263
|
|
|
1227
1264
|
function normalizeAccess(access) {
|
|
1228
1265
|
const value = access || 'public';
|
|
1229
|
-
if (value === 'open') return { access: 'public', alias: value };
|
|
1230
|
-
if (value === 'protected') return { access: 'licensed', alias: value };
|
|
1231
|
-
if (value === 'runtime') return { access: 'remote', alias: value };
|
|
1232
1266
|
return { access: value, alias: null };
|
|
1233
1267
|
}
|
|
1234
1268
|
|
|
@@ -1236,13 +1270,12 @@ function inferEntitlementProfile(manifest) {
|
|
|
1236
1270
|
if (manifest.entitlement && typeof manifest.entitlement.profile === 'string') {
|
|
1237
1271
|
return manifest.entitlement.profile;
|
|
1238
1272
|
}
|
|
1239
|
-
if (manifest.encryption && manifest.encryption.profile === 'kdna
|
|
1273
|
+
if (manifest.encryption && manifest.encryption.profile === 'kdna.encryption.password') {
|
|
1240
1274
|
return 'password';
|
|
1241
1275
|
}
|
|
1242
|
-
if (manifest.encryption && manifest.encryption.profile === 'kdna
|
|
1276
|
+
if (manifest.encryption && manifest.encryption.profile === 'kdna.encryption.password.scrypt') {
|
|
1243
1277
|
return 'password';
|
|
1244
1278
|
}
|
|
1245
|
-
if (manifest.access === 'protected') return 'password';
|
|
1246
1279
|
return null;
|
|
1247
1280
|
}
|
|
1248
1281
|
|
|
@@ -1264,14 +1297,14 @@ function finalizeLoadPlan(plan) {
|
|
|
1264
1297
|
return plan;
|
|
1265
1298
|
}
|
|
1266
1299
|
|
|
1267
|
-
function computeSourceFingerprint(_inputPath,
|
|
1300
|
+
function computeSourceFingerprint(_inputPath, layout) {
|
|
1268
1301
|
// Fingerprint the logical entries, not the transport ZIP bytes. DEFLATE
|
|
1269
1302
|
// output can differ between zlib versions and operating systems even when
|
|
1270
1303
|
// every uncompressed KDNA entry is identical. LoadPlan conformance must be
|
|
1271
1304
|
// stable across JS, Swift, CI runners, and repackaging transports.
|
|
1272
1305
|
const hash = crypto.createHash('sha256');
|
|
1273
|
-
for (const name of Object.keys(
|
|
1274
|
-
const bytes =
|
|
1306
|
+
for (const name of Object.keys(layout.map).sort()) {
|
|
1307
|
+
const bytes = layout.map[name];
|
|
1275
1308
|
hash.update(name);
|
|
1276
1309
|
hash.update('\0');
|
|
1277
1310
|
hash.update(String(bytes.length));
|
|
@@ -1282,7 +1315,7 @@ function computeSourceFingerprint(_inputPath, v1) {
|
|
|
1282
1315
|
return `sha256:${hash.digest('hex')}`;
|
|
1283
1316
|
}
|
|
1284
1317
|
|
|
1285
|
-
function inputFingerprint(inputPath,
|
|
1318
|
+
function inputFingerprint(inputPath, layout, opts = {}) {
|
|
1286
1319
|
const allowedEntitlement = new Set(['active', 'expired', 'revoked', 'offline_grace']);
|
|
1287
1320
|
const entitlementInput = (
|
|
1288
1321
|
opts.entitlement && allowedEntitlement.has(opts.entitlement.status)
|
|
@@ -1291,12 +1324,12 @@ function inputFingerprint(inputPath, v1, opts = {}) {
|
|
|
1291
1324
|
return {
|
|
1292
1325
|
has_password_input: opts.hasPassword === true,
|
|
1293
1326
|
entitlement_input: entitlementInput,
|
|
1294
|
-
source_fingerprint: computeSourceFingerprint(inputPath,
|
|
1327
|
+
source_fingerprint: computeSourceFingerprint(inputPath, layout),
|
|
1295
1328
|
};
|
|
1296
1329
|
}
|
|
1297
1330
|
|
|
1298
|
-
function baseLoadPlan(inputPath,
|
|
1299
|
-
const manifest =
|
|
1331
|
+
function baseLoadPlan(inputPath, layout, validation, opts = {}) {
|
|
1332
|
+
const manifest = layout.manifest;
|
|
1300
1333
|
const accessInfo = normalizeAccess(manifest.access);
|
|
1301
1334
|
const entitlementProfile = inferEntitlementProfile(manifest);
|
|
1302
1335
|
const asset = {
|
|
@@ -1308,7 +1341,7 @@ function baseLoadPlan(inputPath, v1, validation, opts = {}) {
|
|
|
1308
1341
|
};
|
|
1309
1342
|
|
|
1310
1343
|
const plan = {
|
|
1311
|
-
|
|
1344
|
+
format_version: manifest.format_version || null,
|
|
1312
1345
|
asset,
|
|
1313
1346
|
access: accessInfo.access,
|
|
1314
1347
|
access_alias: accessInfo.alias,
|
|
@@ -1317,7 +1350,7 @@ function baseLoadPlan(inputPath, v1, validation, opts = {}) {
|
|
|
1317
1350
|
required_action: 'block',
|
|
1318
1351
|
can_load_now: false,
|
|
1319
1352
|
projection_policy: 'none',
|
|
1320
|
-
input_fingerprint: inputFingerprint(inputPath,
|
|
1353
|
+
input_fingerprint: inputFingerprint(inputPath, layout, opts),
|
|
1321
1354
|
checks: {
|
|
1322
1355
|
format_valid: validation.format_valid,
|
|
1323
1356
|
schema_valid: validation.schema_valid,
|
|
@@ -1328,7 +1361,7 @@ function baseLoadPlan(inputPath, v1, validation, opts = {}) {
|
|
|
1328
1361
|
},
|
|
1329
1362
|
issues: [],
|
|
1330
1363
|
source: {
|
|
1331
|
-
kind:
|
|
1364
|
+
kind: layout.kind,
|
|
1332
1365
|
path: typeof inputPath === 'string' ? path.resolve(inputPath) : null,
|
|
1333
1366
|
},
|
|
1334
1367
|
};
|
|
@@ -1344,9 +1377,9 @@ function baseLoadPlan(inputPath, v1, validation, opts = {}) {
|
|
|
1344
1377
|
return plan;
|
|
1345
1378
|
}
|
|
1346
1379
|
|
|
1347
|
-
// ─── B1: Unified container model → legacy
|
|
1380
|
+
// ─── B1: Unified container model → legacy layout layout ─────────────────────
|
|
1348
1381
|
|
|
1349
|
-
function
|
|
1382
|
+
function canonicalToContainerLayout(asset) {
|
|
1350
1383
|
const map = {};
|
|
1351
1384
|
if (asset.entries) {
|
|
1352
1385
|
for (const [name, buf] of asset.entries) {
|
|
@@ -1379,14 +1412,14 @@ function canonicalToV1Layout(asset) {
|
|
|
1379
1412
|
* result instead of parsing manifest fields directly.
|
|
1380
1413
|
*/
|
|
1381
1414
|
function planLoad(inputPath, opts = {}) {
|
|
1382
|
-
let
|
|
1415
|
+
let layout;
|
|
1383
1416
|
|
|
1384
1417
|
if (Buffer.isBuffer(inputPath) || inputPath instanceof Uint8Array) {
|
|
1385
1418
|
try {
|
|
1386
|
-
|
|
1419
|
+
layout = readInputLayout(inputPath);
|
|
1387
1420
|
} catch (e) {
|
|
1388
1421
|
return finalizeLoadPlan({
|
|
1389
|
-
|
|
1422
|
+
format_version: null,
|
|
1390
1423
|
asset: { asset_id: null, asset_uid: null, title: null, version: null, judgment_version: null },
|
|
1391
1424
|
access: null, access_alias: null, entitlement_profile: null,
|
|
1392
1425
|
state: 'invalid', required_action: 'block', can_load_now: false, projection_policy: 'none',
|
|
@@ -1400,17 +1433,17 @@ function planLoad(inputPath, opts = {}) {
|
|
|
1400
1433
|
|
|
1401
1434
|
// Try the unified container dispatcher first; the internal compatibility
|
|
1402
1435
|
// reader below remains available only for authoring and migration tooling.
|
|
1403
|
-
if (!
|
|
1436
|
+
if (!layout && readAsset !== null) {
|
|
1404
1437
|
try {
|
|
1405
1438
|
const asset = readAsset(path.resolve(inputPath));
|
|
1406
|
-
|
|
1439
|
+
layout = canonicalToContainerLayout(asset);
|
|
1407
1440
|
} catch (e) {
|
|
1408
|
-
// If dispatcher fails, fall back to legacy
|
|
1441
|
+
// If dispatcher fails, fall back to legacy readLayout for backward compat
|
|
1409
1442
|
if (e.code === 'KDNA_FORMAT_UNKNOWN' || e.code === 'KDNA_PATH_NOT_FOUND') {
|
|
1410
1443
|
// let the legacy path handle it
|
|
1411
1444
|
} else {
|
|
1412
1445
|
return finalizeLoadPlan({
|
|
1413
|
-
|
|
1446
|
+
format_version: null,
|
|
1414
1447
|
asset: { asset_id: null, asset_uid: null, title: null, version: null, judgment_version: null },
|
|
1415
1448
|
access: null, access_alias: null, entitlement_profile: null,
|
|
1416
1449
|
state: 'invalid', required_action: 'block', can_load_now: false, projection_policy: 'none',
|
|
@@ -1426,12 +1459,12 @@ function planLoad(inputPath, opts = {}) {
|
|
|
1426
1459
|
}
|
|
1427
1460
|
}
|
|
1428
1461
|
|
|
1429
|
-
if (!
|
|
1462
|
+
if (!layout) {
|
|
1430
1463
|
try {
|
|
1431
|
-
|
|
1464
|
+
layout = readInputLayout(inputPath);
|
|
1432
1465
|
} catch (e) {
|
|
1433
1466
|
return finalizeLoadPlan({
|
|
1434
|
-
|
|
1467
|
+
format_version: null,
|
|
1435
1468
|
asset: { asset_id: null, asset_uid: null, title: null, version: null, judgment_version: null },
|
|
1436
1469
|
access: null, access_alias: null, entitlement_profile: null,
|
|
1437
1470
|
state: 'invalid', required_action: 'block', can_load_now: false, projection_policy: 'none',
|
|
@@ -1443,10 +1476,14 @@ function planLoad(inputPath, opts = {}) {
|
|
|
1443
1476
|
}
|
|
1444
1477
|
}
|
|
1445
1478
|
|
|
1446
|
-
const validation = runValidate(
|
|
1447
|
-
const plan = baseLoadPlan(inputPath,
|
|
1479
|
+
const validation = runValidate(layout);
|
|
1480
|
+
const plan = baseLoadPlan(inputPath, layout, validation, opts);
|
|
1448
1481
|
|
|
1449
1482
|
if (!validation.overall_valid) {
|
|
1483
|
+
if (!['public', 'licensed', 'remote'].includes(plan.access)) {
|
|
1484
|
+
plan.access = null;
|
|
1485
|
+
plan.access_alias = null;
|
|
1486
|
+
}
|
|
1450
1487
|
plan.state = 'invalid';
|
|
1451
1488
|
plan.required_action = 'block';
|
|
1452
1489
|
plan.can_load_now = false;
|
|
@@ -1457,22 +1494,27 @@ function planLoad(inputPath, opts = {}) {
|
|
|
1457
1494
|
return finalizeLoadPlan(plan);
|
|
1458
1495
|
}
|
|
1459
1496
|
|
|
1460
|
-
const manifest =
|
|
1497
|
+
const manifest = layout.manifest;
|
|
1498
|
+
|
|
1499
|
+
if (validation.loader_compatible === false) {
|
|
1500
|
+
plan.state = 'invalid';
|
|
1501
|
+
plan.required_action = 'block';
|
|
1502
|
+
plan.can_load_now = false;
|
|
1503
|
+
plan.projection_policy = 'none';
|
|
1504
|
+
plan.issues.push(buildLoadPlanIssue(
|
|
1505
|
+
'KDNA_LOADER_VERSION_UNSUPPORTED',
|
|
1506
|
+
'blocking',
|
|
1507
|
+
loaderVersionUnsupportedMessage(validation.min_loader_version),
|
|
1508
|
+
));
|
|
1509
|
+
return finalizeLoadPlan(plan);
|
|
1510
|
+
}
|
|
1461
1511
|
|
|
1462
1512
|
function externalEntitlementMatchesCurrentAsset(entitlement) {
|
|
1463
1513
|
if (!isVerifiedExternalEntitlement(entitlement) || !entitlement.asset) return false;
|
|
1464
|
-
|
|
1465
|
-
try {
|
|
1466
|
-
checksums = v1.map['checksums.json']
|
|
1467
|
-
? parseJsonEntry('checksums.json', v1.map['checksums.json'])
|
|
1468
|
-
: null;
|
|
1469
|
-
} catch {
|
|
1470
|
-
return false;
|
|
1471
|
-
}
|
|
1472
|
-
return entitlement.asset.asset_id === (manifest.asset_id || manifest.name)
|
|
1514
|
+
return entitlement.asset.asset_id === manifest.asset_id
|
|
1473
1515
|
&& entitlement.asset.asset_uid === manifest.asset_uid
|
|
1474
1516
|
&& entitlement.asset.version === manifest.version
|
|
1475
|
-
&& entitlement.asset.digest ===
|
|
1517
|
+
&& entitlement.asset.digest === layout.containerDigest
|
|
1476
1518
|
&& entitlement.asset.entry_path === manifest.payload?.path;
|
|
1477
1519
|
}
|
|
1478
1520
|
|
|
@@ -1867,35 +1909,46 @@ function assertNoForbiddenTerms(obj) {
|
|
|
1867
1909
|
walk(obj);
|
|
1868
1910
|
if (seen.size > 0) {
|
|
1869
1911
|
throw new Error(
|
|
1870
|
-
`internal:
|
|
1912
|
+
`internal: layout inspect output contains forbidden terms: ${[...seen].join(', ')}`,
|
|
1871
1913
|
);
|
|
1872
1914
|
}
|
|
1873
1915
|
}
|
|
1874
1916
|
|
|
1875
1917
|
module.exports = {
|
|
1876
1918
|
MIMETYPE,
|
|
1919
|
+
KDNA_LOADER_VERSION,
|
|
1877
1920
|
REQUIRED_DIR_ENTRIES,
|
|
1878
1921
|
isKdnaSourceDir,
|
|
1879
1922
|
detectContainerFormat,
|
|
1880
|
-
readLayout:
|
|
1923
|
+
readLayout: readLayout,
|
|
1881
1924
|
inspect,
|
|
1882
1925
|
validate,
|
|
1883
1926
|
planLoad,
|
|
1884
1927
|
loadAuthorized,
|
|
1885
|
-
buildChecksums:
|
|
1928
|
+
buildChecksums: buildChecksums,
|
|
1886
1929
|
computeRuntimeEntrySetDigest,
|
|
1887
1930
|
pack,
|
|
1888
1931
|
unpack,
|
|
1889
|
-
load:
|
|
1890
|
-
loadAsset:
|
|
1891
|
-
buildCapsule,
|
|
1932
|
+
load: loadAsset,
|
|
1933
|
+
loadAsset: loadAsset,
|
|
1892
1934
|
FORBIDDEN_OUTPUT_TERMS,
|
|
1893
1935
|
parseSemver,
|
|
1894
1936
|
compareSemver,
|
|
1895
1937
|
satisfies,
|
|
1896
1938
|
};
|
|
1897
1939
|
|
|
1898
|
-
//
|
|
1940
|
+
// The remote Runtime package subpath uses this non-enumerable bridge. Keeping
|
|
1941
|
+
// it non-enumerable prevents the package-root object spread from turning the
|
|
1942
|
+
// deployer-only contract into an ordinary consumer API. Package exports also
|
|
1943
|
+
// block direct access to this internal container module after installation.
|
|
1944
|
+
Object.defineProperty(module.exports, 'loadRemoteRuntimeAssetForServer', {
|
|
1945
|
+
value: loadRemoteRuntimeAssetForServer,
|
|
1946
|
+
enumerable: false,
|
|
1947
|
+
configurable: false,
|
|
1948
|
+
writable: false,
|
|
1949
|
+
});
|
|
1950
|
+
|
|
1951
|
+
// ─── loadAsset — layout runtime loading / load contract ──────────────────────
|
|
1899
1952
|
|
|
1900
1953
|
function renderPromptItem(item) {
|
|
1901
1954
|
if (item === undefined || item === null) return '';
|
|
@@ -1916,7 +1969,7 @@ function renderPromptItem(item) {
|
|
|
1916
1969
|
// Boundary card: kdna-studio writes { id, scope, out_of_scope,
|
|
1917
1970
|
// acceptable_exceptions }. Render the scope/out-of-scope rule
|
|
1918
1971
|
// instead of falling through to the UUID `id` (which was the
|
|
1919
|
-
//
|
|
1972
|
+
// 0.15.0 behavior and made boundaries unreadable in
|
|
1920
1973
|
// `kdna load --as=prompt`).
|
|
1921
1974
|
if (
|
|
1922
1975
|
item.type === 'boundary' ||
|
|
@@ -1979,6 +2032,7 @@ function loadAuthorized(inputPath, opts = {}) {
|
|
|
1979
2032
|
// A packaged path is read exactly once. Authorization and projection must
|
|
1980
2033
|
// operate on the same immutable byte snapshot even if the path changes
|
|
1981
2034
|
// while this synchronous call is in progress.
|
|
2035
|
+
const inputKind = typeof inputPath === 'string' ? 'packaged_file' : 'packaged_bytes';
|
|
1982
2036
|
const inputSnapshot = snapshotPackagedInput(inputPath);
|
|
1983
2037
|
const plan = planLoad(inputSnapshot, opts);
|
|
1984
2038
|
if (plan.can_load_now !== true) {
|
|
@@ -2000,11 +2054,155 @@ function loadAuthorized(inputPath, opts = {}) {
|
|
|
2000
2054
|
schema_valid: plan.checks && plan.checks.overall_valid === true,
|
|
2001
2055
|
signature_valid: plan.signature_valid === true,
|
|
2002
2056
|
},
|
|
2057
|
+
_runtimeAssetBytes: inputSnapshot,
|
|
2058
|
+
_runtimeInputKind: inputKind,
|
|
2003
2059
|
};
|
|
2004
|
-
return
|
|
2060
|
+
return loadAssetUnsafe(inputSnapshot, mergedOpts);
|
|
2061
|
+
}
|
|
2062
|
+
|
|
2063
|
+
function remoteRuntimeDeniedPlan(plan, code, message) {
|
|
2064
|
+
return finalizeLoadPlan({
|
|
2065
|
+
...plan,
|
|
2066
|
+
state: 'invalid',
|
|
2067
|
+
required_action: 'block',
|
|
2068
|
+
can_load_now: false,
|
|
2069
|
+
projection_policy: 'none',
|
|
2070
|
+
issues: [buildLoadPlanIssue(code, 'blocking', message)],
|
|
2071
|
+
});
|
|
2005
2072
|
}
|
|
2006
2073
|
|
|
2007
|
-
function
|
|
2074
|
+
function packagedAssetRequiredPlan(inputPath) {
|
|
2075
|
+
return finalizeLoadPlan({
|
|
2076
|
+
format_version: null,
|
|
2077
|
+
asset: {
|
|
2078
|
+
asset_id: null,
|
|
2079
|
+
asset_uid: null,
|
|
2080
|
+
title: null,
|
|
2081
|
+
version: null,
|
|
2082
|
+
judgment_version: null,
|
|
2083
|
+
},
|
|
2084
|
+
access: null,
|
|
2085
|
+
access_alias: null,
|
|
2086
|
+
entitlement_profile: null,
|
|
2087
|
+
state: 'invalid',
|
|
2088
|
+
required_action: 'block',
|
|
2089
|
+
can_load_now: false,
|
|
2090
|
+
projection_policy: 'none',
|
|
2091
|
+
input_fingerprint: null,
|
|
2092
|
+
checks: {
|
|
2093
|
+
format_valid: false,
|
|
2094
|
+
schema_valid: false,
|
|
2095
|
+
payload_valid: false,
|
|
2096
|
+
checksums_valid: false,
|
|
2097
|
+
load_contract_valid: false,
|
|
2098
|
+
overall_valid: false,
|
|
2099
|
+
},
|
|
2100
|
+
issues: [buildLoadPlanIssue(
|
|
2101
|
+
'KDNA_ASSET_FILE_REQUIRED',
|
|
2102
|
+
'blocking',
|
|
2103
|
+
'Remote Runtime loading requires a packaged .kdna asset file. Source directories are authoring inputs only.',
|
|
2104
|
+
)],
|
|
2105
|
+
source: {
|
|
2106
|
+
kind: 'dir',
|
|
2107
|
+
path: typeof inputPath === 'string' ? path.resolve(inputPath) : null,
|
|
2108
|
+
},
|
|
2109
|
+
});
|
|
2110
|
+
}
|
|
2111
|
+
|
|
2112
|
+
function loadPlanDeniedError(plan, fallbackCode = 'KDNA_LOAD_NOT_AUTHORIZED') {
|
|
2113
|
+
const issueCodes = Array.isArray(plan && plan.issues)
|
|
2114
|
+
? plan.issues.map((issue) => issue.code).filter(Boolean)
|
|
2115
|
+
: [];
|
|
2116
|
+
const error = new Error(
|
|
2117
|
+
`LoadPlan denied loading: state=${plan?.state || 'invalid'} required_action=${plan?.required_action || 'block'}`,
|
|
2118
|
+
);
|
|
2119
|
+
error.code = issueCodes[0] || fallbackCode;
|
|
2120
|
+
error.plan = plan;
|
|
2121
|
+
return error;
|
|
2122
|
+
}
|
|
2123
|
+
|
|
2124
|
+
/**
|
|
2125
|
+
* Deployer-only remote Runtime loading contract.
|
|
2126
|
+
*
|
|
2127
|
+
* The caller must already control the packaged remote asset. This function
|
|
2128
|
+
* snapshots a packaged path exactly once, validates and plans that immutable
|
|
2129
|
+
* byte snapshot, accepts only the remote access mode and the frozen
|
|
2130
|
+
* single-asset shape, then emits the full JSON Runtime Capsule required by a
|
|
2131
|
+
* server-side projection engine.
|
|
2132
|
+
*/
|
|
2133
|
+
function loadRemoteRuntimeAssetForServer(inputPath) {
|
|
2134
|
+
const inputKind = typeof inputPath === 'string' ? 'packaged_file' : 'packaged_bytes';
|
|
2135
|
+
const inputSnapshot = snapshotPackagedInput(inputPath);
|
|
2136
|
+
|
|
2137
|
+
if (!Buffer.isBuffer(inputSnapshot)) {
|
|
2138
|
+
if (typeof inputPath === 'string') {
|
|
2139
|
+
try {
|
|
2140
|
+
if (fs.statSync(path.resolve(inputPath)).isDirectory()) {
|
|
2141
|
+
throw loadPlanDeniedError(packagedAssetRequiredPlan(inputPath));
|
|
2142
|
+
}
|
|
2143
|
+
} catch (error) {
|
|
2144
|
+
if (error && error.code === 'KDNA_ASSET_FILE_REQUIRED') throw error;
|
|
2145
|
+
}
|
|
2146
|
+
throw loadPlanDeniedError(planLoad(inputPath));
|
|
2147
|
+
}
|
|
2148
|
+
const plan = remoteRuntimeDeniedPlan(
|
|
2149
|
+
packagedAssetRequiredPlan(null),
|
|
2150
|
+
'KDNA_FORMAT_INVALID',
|
|
2151
|
+
'Remote Runtime input must be a packaged .kdna file path or packaged bytes.',
|
|
2152
|
+
);
|
|
2153
|
+
throw loadPlanDeniedError(plan);
|
|
2154
|
+
}
|
|
2155
|
+
|
|
2156
|
+
const plan = planLoad(inputSnapshot);
|
|
2157
|
+
if (!plan.checks || plan.checks.overall_valid !== true) {
|
|
2158
|
+
throw loadPlanDeniedError(plan);
|
|
2159
|
+
}
|
|
2160
|
+
|
|
2161
|
+
if (plan.access !== 'remote') {
|
|
2162
|
+
throw loadPlanDeniedError(remoteRuntimeDeniedPlan(
|
|
2163
|
+
plan,
|
|
2164
|
+
'KDNA_REMOTE_RUNTIME_ACCESS_REQUIRED',
|
|
2165
|
+
'The Remote Runtime loader accepts only packaged assets whose access mode is remote.',
|
|
2166
|
+
));
|
|
2167
|
+
}
|
|
2168
|
+
|
|
2169
|
+
const layout = readInputLayout(inputSnapshot);
|
|
2170
|
+
const manifest = layout.manifest;
|
|
2171
|
+
const hasDependencies = manifest.dependencies
|
|
2172
|
+
&& typeof manifest.dependencies === 'object'
|
|
2173
|
+
&& Object.keys(manifest.dependencies).length > 0;
|
|
2174
|
+
if (hasDependencies || manifest.extends !== undefined) {
|
|
2175
|
+
throw loadPlanDeniedError(remoteRuntimeDeniedPlan(
|
|
2176
|
+
plan,
|
|
2177
|
+
'KDNA_REMOTE_RUNTIME_COMPOSITION_UNSUPPORTED',
|
|
2178
|
+
'Remote Runtime loading currently accepts one asset only; dependencies and extends declarations are not authorized.',
|
|
2179
|
+
));
|
|
2180
|
+
}
|
|
2181
|
+
|
|
2182
|
+
if (
|
|
2183
|
+
plan.state !== 'needs_runtime'
|
|
2184
|
+
|| plan.required_action !== 'connect_runtime'
|
|
2185
|
+
|| plan.can_load_now !== false
|
|
2186
|
+
|| plan.projection_policy !== 'remote'
|
|
2187
|
+
) {
|
|
2188
|
+
throw loadPlanDeniedError(plan);
|
|
2189
|
+
}
|
|
2190
|
+
|
|
2191
|
+
return loadAssetUnsafe(inputSnapshot, {
|
|
2192
|
+
profile: 'full',
|
|
2193
|
+
as: 'json',
|
|
2194
|
+
resolvedDependencies: [],
|
|
2195
|
+
extendsChain: [],
|
|
2196
|
+
_validation: {
|
|
2197
|
+
schema_valid: plan.checks.overall_valid === true,
|
|
2198
|
+
signature_valid: plan.signature_valid === true,
|
|
2199
|
+
},
|
|
2200
|
+
_runtimeAssetBytes: inputSnapshot,
|
|
2201
|
+
_runtimeInputKind: inputKind,
|
|
2202
|
+
});
|
|
2203
|
+
}
|
|
2204
|
+
|
|
2205
|
+
function loadAsset(inputPath, opts = {}) {
|
|
2008
2206
|
return loadAuthorized(inputPath, opts);
|
|
2009
2207
|
}
|
|
2010
2208
|
|
|
@@ -2068,15 +2266,15 @@ function hasCompactPromptContent(content) {
|
|
|
2068
2266
|
);
|
|
2069
2267
|
}
|
|
2070
2268
|
|
|
2071
|
-
function
|
|
2072
|
-
const
|
|
2073
|
-
const m =
|
|
2269
|
+
function loadAssetUnsafe(inputPath, opts = {}) {
|
|
2270
|
+
const layout = readInputLayout(inputPath);
|
|
2271
|
+
const m = layout.manifest;
|
|
2074
2272
|
const profile = opts.profile || (m.load_contract ? m.load_contract.default_profile : 'compact') || 'compact';
|
|
2075
2273
|
const as = opts.as || 'json';
|
|
2076
2274
|
|
|
2077
2275
|
let payload;
|
|
2078
2276
|
try {
|
|
2079
|
-
const rawPayload =
|
|
2277
|
+
const rawPayload = layout.map['payload.kdnab'];
|
|
2080
2278
|
payload = parsePayloadEntry("payload.kdnab", rawPayload);
|
|
2081
2279
|
} catch (e) {
|
|
2082
2280
|
throw new Error(`payload.kdnab is not valid KDNA payload: ${e.message}`, { cause: e });
|
|
@@ -2088,10 +2286,10 @@ function loadV1Unsafe(inputPath, opts = {}) {
|
|
|
2088
2286
|
// Consumer-provided decrypt hook (e.g., licensed entry key)
|
|
2089
2287
|
try {
|
|
2090
2288
|
const decrypted = opts.decryptEntry({
|
|
2091
|
-
asset: { entries:
|
|
2289
|
+
asset: { entries: layout.map, manifest: m },
|
|
2092
2290
|
manifest: m,
|
|
2093
2291
|
entryName: 'payload.kdnab',
|
|
2094
|
-
ciphertext:
|
|
2292
|
+
ciphertext: layout.map['payload.kdnab'],
|
|
2095
2293
|
});
|
|
2096
2294
|
const decryptedBuf = typeof decrypted === 'string'
|
|
2097
2295
|
? Buffer.from(decrypted, 'utf8')
|
|
@@ -2103,7 +2301,7 @@ function loadV1Unsafe(inputPath, opts = {}) {
|
|
|
2103
2301
|
throw err;
|
|
2104
2302
|
}
|
|
2105
2303
|
} else if (opts.password) {
|
|
2106
|
-
// Password-based decryption via kdna
|
|
2304
|
+
// Password-based decryption via kdna.encryption.password*
|
|
2107
2305
|
// profiles. Detects Argon2id vs scrypt from the envelope profile.
|
|
2108
2306
|
try {
|
|
2109
2307
|
const {
|
|
@@ -2111,10 +2309,10 @@ function loadV1Unsafe(inputPath, opts = {}) {
|
|
|
2111
2309
|
decryptProtectedEntryScrypt,
|
|
2112
2310
|
PASSWORD_PROTECTED_SCRYPT_PROFILE,
|
|
2113
2311
|
} = require('../crypto-profile.js');
|
|
2114
|
-
const encryptedEnvelope =
|
|
2312
|
+
const encryptedEnvelope = layout.map['payload.kdnab'];
|
|
2115
2313
|
let envelope;
|
|
2116
2314
|
try { envelope = cbor.decode(Buffer.isBuffer(encryptedEnvelope) ? encryptedEnvelope : Buffer.from(encryptedEnvelope)); }
|
|
2117
|
-
catch { throw new Error('Cannot decode encrypted envelope: payload.kdnab must be CBOR-encoded (
|
|
2315
|
+
catch { throw new Error('Cannot decode encrypted envelope: payload.kdnab must be CBOR-encoded (format 0.1.0)'); }
|
|
2118
2316
|
const decryptFn = (envelope.profile === PASSWORD_PROTECTED_SCRYPT_PROFILE)
|
|
2119
2317
|
? decryptProtectedEntryScrypt
|
|
2120
2318
|
: decryptProtectedEntry;
|
|
@@ -2142,11 +2340,11 @@ function loadV1Unsafe(inputPath, opts = {}) {
|
|
|
2142
2340
|
}
|
|
2143
2341
|
|
|
2144
2342
|
// Digest verification — refuse to load if checksums.json is present and digests mismatch.
|
|
2145
|
-
if (
|
|
2343
|
+
if (layout.map['checksums.json']) {
|
|
2146
2344
|
try {
|
|
2147
|
-
const checks = parseJsonEntry('checksums.json',
|
|
2345
|
+
const checks = parseJsonEntry('checksums.json', layout.map['checksums.json']);
|
|
2148
2346
|
const problems = [];
|
|
2149
|
-
const ok = verifyDigests(checks,
|
|
2347
|
+
const ok = verifyDigests(checks, layout.map, problems, {});
|
|
2150
2348
|
if (!ok) {
|
|
2151
2349
|
const err = new Error(`checksum verification failed: ${problems.join('; ')}`);
|
|
2152
2350
|
err.code = 'checksum_mismatch';
|
|
@@ -2194,7 +2392,8 @@ function loadV1Unsafe(inputPath, opts = {}) {
|
|
|
2194
2392
|
if (m.load_contract && m.load_contract.profiles && m.load_contract.profiles.compact && m.load_contract.profiles.compact.max_tokens_hint) {
|
|
2195
2393
|
maxTokensHint = m.load_contract.profiles.compact.max_tokens_hint;
|
|
2196
2394
|
}
|
|
2197
|
-
result.content = { asset_id: m.asset_id, asset_uid: m.asset_uid, title: m.title, version: m.version, judgment_version: m.judgment_version, asset_type: m.asset_type, summary: m.summary || null, language: m.language || null, keywords: m.keywords || [], profiles_available: m.load_contract ? Object.keys(m.load_contract.profiles || {}) : []
|
|
2395
|
+
result.content = { asset_id: m.asset_id, asset_uid: m.asset_uid, title: m.title, version: m.version, judgment_version: m.judgment_version, asset_type: m.asset_type, summary: m.summary || null, language: m.language || null, keywords: m.keywords || [], profiles_available: m.load_contract ? Object.keys(m.load_contract.profiles || {}) : [] };
|
|
2396
|
+
if (maxTokensHint !== undefined) result.content.max_tokens_hint = maxTokensHint;
|
|
2198
2397
|
} else if (profile === 'compact') {
|
|
2199
2398
|
const core = payload.core || {};
|
|
2200
2399
|
const normalizeList = (items) => (items || []).map((item) => {
|
|
@@ -2202,6 +2401,13 @@ function loadV1Unsafe(inputPath, opts = {}) {
|
|
|
2202
2401
|
if (item && typeof item === 'object') return item;
|
|
2203
2402
|
return null;
|
|
2204
2403
|
}).filter(Boolean);
|
|
2404
|
+
const preserveDeclaredList = (items) => (
|
|
2405
|
+
Array.isArray(items)
|
|
2406
|
+
? items.map((item) => (
|
|
2407
|
+
typeof item === 'string' ? item : globalThis.structuredClone(item)
|
|
2408
|
+
))
|
|
2409
|
+
: []
|
|
2410
|
+
);
|
|
2205
2411
|
result.content = {
|
|
2206
2412
|
highest_question: core.highest_question || null,
|
|
2207
2413
|
// These scoped domain-level values are already validated by the payload
|
|
@@ -2217,11 +2423,10 @@ function loadV1Unsafe(inputPath, opts = {}) {
|
|
|
2217
2423
|
: null,
|
|
2218
2424
|
axioms: (core.axioms || []).map(normalizeCompactAxiom).filter(Boolean),
|
|
2219
2425
|
boundaries: normalizeList(core.boundaries),
|
|
2220
|
-
//
|
|
2221
|
-
//
|
|
2222
|
-
//
|
|
2223
|
-
|
|
2224
|
-
self_checks: normalizeList(payload.reasoning && payload.reasoning.self_check),
|
|
2426
|
+
// Payload uses the singular field name while Runtime Capsule uses the
|
|
2427
|
+
// plural collection name. Preserve every validated item and its shape;
|
|
2428
|
+
// strings remain strings and structured questions remain objects.
|
|
2429
|
+
self_checks: preserveDeclaredList(payload.reasoning && payload.reasoning.self_check),
|
|
2225
2430
|
failure_modes: normalizeList(payload.reasoning && payload.reasoning.failure_modes),
|
|
2226
2431
|
patterns: normalizeList(payload.patterns).slice(0, 3),
|
|
2227
2432
|
};
|
|
@@ -2243,7 +2448,7 @@ function loadV1Unsafe(inputPath, opts = {}) {
|
|
|
2243
2448
|
if (opts.extendsChain && opts.extendsChain.length > 0 && result.content) {
|
|
2244
2449
|
try {
|
|
2245
2450
|
const base = opts.extendsChain[0];
|
|
2246
|
-
const baseLoaded =
|
|
2451
|
+
const baseLoaded = loadAssetUnsafe(base.path, {
|
|
2247
2452
|
...opts,
|
|
2248
2453
|
resolvedDependencies: [],
|
|
2249
2454
|
extendsChain: [],
|
|
@@ -2287,7 +2492,7 @@ function loadV1Unsafe(inputPath, opts = {}) {
|
|
|
2287
2492
|
|
|
2288
2493
|
if (opts.resolvedDependencies && opts.resolvedDependencies.length > 0) {
|
|
2289
2494
|
result.resolved_dependencies = opts.resolvedDependencies.map(dep => {
|
|
2290
|
-
const depLoaded =
|
|
2495
|
+
const depLoaded = loadAssetUnsafe(dep.path, { ...opts, resolvedDependencies: [] });
|
|
2291
2496
|
// RAG namespace (Story 11): scoped identifier for namespace isolation.
|
|
2292
2497
|
// Format: name@version (or just name when version is absent).
|
|
2293
2498
|
const ragNamespace = dep.name
|
|
@@ -2373,7 +2578,7 @@ function loadV1Unsafe(inputPath, opts = {}) {
|
|
|
2373
2578
|
let textOut = text.trim();
|
|
2374
2579
|
if (opts.resolvedDependencies && opts.resolvedDependencies.length > 0) {
|
|
2375
2580
|
for (const dep of opts.resolvedDependencies) {
|
|
2376
|
-
const depLoaded =
|
|
2581
|
+
const depLoaded = loadAssetUnsafe(dep.path, { ...opts, resolvedDependencies: [] });
|
|
2377
2582
|
if (depLoaded.text) {
|
|
2378
2583
|
// Namespace header (Story 11): each component section is prefixed
|
|
2379
2584
|
// with [NAMESPACE: id] so consumers can isolate RAG content per source.
|
|
@@ -2392,55 +2597,37 @@ function loadV1Unsafe(inputPath, opts = {}) {
|
|
|
2392
2597
|
return result;
|
|
2393
2598
|
}
|
|
2394
2599
|
|
|
2395
|
-
return wrapAsCapsule(result,
|
|
2600
|
+
return wrapAsCapsule(result, layout, profile, { ...opts, as });
|
|
2396
2601
|
}
|
|
2397
2602
|
|
|
2398
|
-
function wrapAsCapsule(result,
|
|
2603
|
+
function wrapAsCapsule(result, layout, profile, opts) {
|
|
2399
2604
|
if (opts.as === 'json') {
|
|
2400
|
-
return
|
|
2605
|
+
return buildRuntimeCapsuleProjection(result, layout, profile, opts);
|
|
2401
2606
|
}
|
|
2402
2607
|
return result;
|
|
2403
2608
|
}
|
|
2404
2609
|
|
|
2405
|
-
function
|
|
2406
|
-
const m =
|
|
2610
|
+
function buildRuntimeCapsuleProjection(loadResult, layout, profile, opts = {}) {
|
|
2611
|
+
const m = layout.manifest;
|
|
2407
2612
|
const val = opts._validation || {};
|
|
2408
|
-
const
|
|
2409
|
-
v1.map && v1.map['kdna.json'],
|
|
2410
|
-
v1.map && v1.map['payload.kdnab'],
|
|
2411
|
-
);
|
|
2412
|
-
|
|
2413
|
-
const pubkey = (m.creator && m.creator.pubkey) || (m.author && m.author.pubkey) || null;
|
|
2613
|
+
const pubkey = (m.creator && m.creator.pubkey) || null;
|
|
2414
2614
|
const sigVerified = val.signature_valid === true;
|
|
2415
|
-
const
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2615
|
+
const runtimeCapsule = require('../runtime-capsule');
|
|
2616
|
+
const digests = runtimeCapsule.computeDigestEvidence(opts._runtimeAssetBytes, {
|
|
2617
|
+
expectedDigests: opts.expectedDigests,
|
|
2618
|
+
});
|
|
2619
|
+
return runtimeCapsule.buildRuntimeCapsule({
|
|
2620
|
+
projection: loadResult,
|
|
2621
|
+
manifest: m,
|
|
2622
|
+
digests,
|
|
2421
2623
|
signature: pubkey
|
|
2422
2624
|
? { state: sigVerified ? 'verified' : 'not_checked', issuer: pubkey }
|
|
2423
2625
|
: { state: 'absent' },
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
payload_encoding: (m.payload && m.payload.encoding) || 'cbor',
|
|
2430
|
-
loaded_by: 'kdna-core',
|
|
2431
|
-
loaded_at: new Date().toISOString(),
|
|
2432
|
-
schema_valid: val.schema_valid === true,
|
|
2433
|
-
signature_state: sigVerified ? 'verified' : (pubkey ? 'not_checked' : 'absent'),
|
|
2434
|
-
profile: profile,
|
|
2435
|
-
},
|
|
2436
|
-
};
|
|
2437
|
-
|
|
2438
|
-
if (loadResult.extends_chain) capsule.extends_chain = loadResult.extends_chain;
|
|
2439
|
-
if (loadResult.inheritance_applied !== undefined) capsule.inheritance_applied = loadResult.inheritance_applied;
|
|
2440
|
-
if (loadResult.resolved_dependencies) capsule.resolved_dependencies = loadResult.resolved_dependencies;
|
|
2441
|
-
if (loadResult.rag_isolation_policy) capsule.rag_isolation_policy = loadResult.rag_isolation_policy;
|
|
2442
|
-
|
|
2443
|
-
return capsule;
|
|
2626
|
+
inputKind: opts._runtimeInputKind,
|
|
2627
|
+
loadedAt: opts.loadedAt,
|
|
2628
|
+
schemaValid: val.schema_valid === true,
|
|
2629
|
+
profile,
|
|
2630
|
+
});
|
|
2444
2631
|
}
|
|
2445
2632
|
|
|
2446
2633
|
// ─── Semver & Dependency Resolution (Story 6) ─────────────────────────
|
|
@@ -2456,9 +2643,9 @@ function parseSemver(v) {
|
|
|
2456
2643
|
};
|
|
2457
2644
|
}
|
|
2458
2645
|
|
|
2459
|
-
function compareSemver(
|
|
2460
|
-
const p1 = parseSemver(
|
|
2461
|
-
const p2 = parseSemver(
|
|
2646
|
+
function compareSemver(leftVersion, rightVersion) {
|
|
2647
|
+
const p1 = parseSemver(leftVersion);
|
|
2648
|
+
const p2 = parseSemver(rightVersion);
|
|
2462
2649
|
if (!p1 || !p2) return 0;
|
|
2463
2650
|
if (p1.major !== p2.major) return p1.major - p2.major;
|
|
2464
2651
|
if (p1.minor !== p2.minor) return p1.minor - p2.minor;
|