@aikdna/kdna-core 0.18.0 → 0.20.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 +136 -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 +78 -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 -33
- 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} +95 -254
- package/src/{execution-contract-v1.js → runtime-contract.js} +184 -152
- package/src/types.d.ts +200 -278
- 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
|
@@ -3,18 +3,20 @@
|
|
|
3
3
|
const crypto = require('node:crypto');
|
|
4
4
|
const fs = require('node:fs');
|
|
5
5
|
|
|
6
|
-
const
|
|
6
|
+
const JsonSchema2020 = require('ajv/dist/2020');
|
|
7
7
|
const addFormats = require('ajv-formats');
|
|
8
8
|
|
|
9
9
|
const { createKdnaAssetReader } = require('./asset-reader');
|
|
10
|
-
const
|
|
10
|
+
const container = require('./container');
|
|
11
11
|
const digestEvidenceSchema = require('../schema/digest-evidence.schema.json');
|
|
12
|
-
const
|
|
13
|
-
const capsule2Schema = require('../schema/runtime-capsule-2.schema.json');
|
|
12
|
+
const runtimeCapsuleSchema = require('../schema/runtime-capsule.schema.json');
|
|
14
13
|
|
|
15
14
|
const SHA256_RE = /^sha256:[0-9a-f]{64}$/;
|
|
16
|
-
const DIGEST_PROFILE = 'kdna-
|
|
17
|
-
const
|
|
15
|
+
const DIGEST_PROFILE = 'kdna.digest-evidence';
|
|
16
|
+
const DIGEST_PROFILE_VERSION = '0.1.0';
|
|
17
|
+
const CAPSULE_DIGEST_PROFILE = 'kdna.canonicalization.runtime-capsule-jcs';
|
|
18
|
+
const CANONICALIZATION_PROFILE_VERSION = '0.1.0';
|
|
19
|
+
const RUNTIME_CAPSULE_CONTRACT_VERSION = '0.1.0';
|
|
18
20
|
const COMPARISON_SOURCES = new Set([
|
|
19
21
|
'caller',
|
|
20
22
|
'registry',
|
|
@@ -23,7 +25,6 @@ const COMPARISON_SOURCES = new Set([
|
|
|
23
25
|
'kdna.json.content_digest',
|
|
24
26
|
'kdna.json.authoring.content_digest',
|
|
25
27
|
'checksums.json.entry_set_digest',
|
|
26
|
-
'checksums.json.asset_digest',
|
|
27
28
|
]);
|
|
28
29
|
const EXTERNAL_COMPARISON_SOURCES = new Set([
|
|
29
30
|
'caller',
|
|
@@ -37,32 +38,19 @@ const COMPARISON_TARGETS = new Set([
|
|
|
37
38
|
'checksum_declaration',
|
|
38
39
|
]);
|
|
39
40
|
const BASIS = Object.freeze({
|
|
40
|
-
asset: 'kdna-container-bytes
|
|
41
|
-
content: 'kdna-content-tree
|
|
42
|
-
runtime_entry_set: 'kdna-runtime-entry-set
|
|
41
|
+
asset: 'kdna.digest-basis.container-bytes',
|
|
42
|
+
content: 'kdna.digest-basis.content-tree',
|
|
43
|
+
runtime_entry_set: 'kdna.digest-basis.runtime-entry-set',
|
|
43
44
|
});
|
|
44
|
-
const CAPSULE_1_EXTENSION_KEYS = Object.freeze([
|
|
45
|
-
'extends_chain',
|
|
46
|
-
'inheritance_applied',
|
|
47
|
-
'resolved_dependencies',
|
|
48
|
-
'rag_isolation_policy',
|
|
49
|
-
]);
|
|
50
|
-
const CAPSULE_1_ACCESS_MAP = Object.freeze({
|
|
51
|
-
open: 'public',
|
|
52
|
-
protected: 'licensed',
|
|
53
|
-
runtime: 'remote',
|
|
54
|
-
});
|
|
55
|
-
|
|
56
45
|
let validators;
|
|
57
46
|
|
|
58
47
|
function schemaValidators() {
|
|
59
48
|
if (validators) return validators;
|
|
60
|
-
const ajv = new
|
|
49
|
+
const ajv = new JsonSchema2020({ allErrors: true, strict: false });
|
|
61
50
|
addFormats(ajv);
|
|
62
51
|
ajv.addSchema(digestEvidenceSchema);
|
|
63
52
|
validators = {
|
|
64
|
-
|
|
65
|
-
capsule2: ajv.compile(capsule2Schema),
|
|
53
|
+
runtimeCapsule: ajv.compile(runtimeCapsuleSchema),
|
|
66
54
|
};
|
|
67
55
|
return validators;
|
|
68
56
|
}
|
|
@@ -85,8 +73,8 @@ function snapshotPackagedBytes(input) {
|
|
|
85
73
|
}
|
|
86
74
|
if (typeof input !== 'string') {
|
|
87
75
|
fail(
|
|
88
|
-
'
|
|
89
|
-
'Capsule
|
|
76
|
+
'KDNA_RUNTIME_CAPSULE_INPUT_INVALID',
|
|
77
|
+
'Runtime Capsule requires a packaged .kdna path, Buffer, or Uint8Array.',
|
|
90
78
|
);
|
|
91
79
|
}
|
|
92
80
|
|
|
@@ -94,16 +82,16 @@ function snapshotPackagedBytes(input) {
|
|
|
94
82
|
try {
|
|
95
83
|
stat = fs.statSync(input);
|
|
96
84
|
} catch (error) {
|
|
97
|
-
fail('
|
|
85
|
+
fail('KDNA_RUNTIME_CAPSULE_INPUT_INVALID', `Cannot read packaged KDNA asset: ${error.message}`);
|
|
98
86
|
}
|
|
99
87
|
if (stat.isDirectory()) {
|
|
100
88
|
fail(
|
|
101
89
|
'KDNA_ASSET_FILE_REQUIRED',
|
|
102
|
-
'Runtime Capsule
|
|
90
|
+
'Runtime Capsule requires final packaged .kdna bytes; authoring directories are not runtime assets.',
|
|
103
91
|
);
|
|
104
92
|
}
|
|
105
93
|
if (!stat.isFile()) {
|
|
106
|
-
fail('
|
|
94
|
+
fail('KDNA_RUNTIME_CAPSULE_INPUT_INVALID', 'Runtime Capsule input is not a regular file.');
|
|
107
95
|
}
|
|
108
96
|
return { bytes: fs.readFileSync(input), inputKind: 'packaged_file' };
|
|
109
97
|
}
|
|
@@ -189,23 +177,12 @@ function declaredContentDigest(manifest) {
|
|
|
189
177
|
|
|
190
178
|
function declaredEntrySetDigest(checksums) {
|
|
191
179
|
if (!checksums) return null;
|
|
192
|
-
|
|
180
|
+
return checksums.entry_set_digest !== undefined
|
|
193
181
|
? normalizeDeclaredDigest(
|
|
194
182
|
checksums.entry_set_digest,
|
|
195
183
|
'checksums.json.entry_set_digest',
|
|
196
184
|
)
|
|
197
185
|
: null;
|
|
198
|
-
const legacy = checksums.asset_digest !== undefined
|
|
199
|
-
? normalizeDeclaredDigest(checksums.asset_digest, 'checksums.json.asset_digest')
|
|
200
|
-
: null;
|
|
201
|
-
if (canonical && legacy && canonical.value !== legacy.value) {
|
|
202
|
-
fail(
|
|
203
|
-
'KDNA_RUNTIME_ENTRY_SET_DIGEST_DECLARATION_CONFLICT',
|
|
204
|
-
'checksums.json entry_set_digest conflicts with deprecated asset_digest.',
|
|
205
|
-
{ entry_set_digest: canonical.value, asset_digest: legacy.value },
|
|
206
|
-
);
|
|
207
|
-
}
|
|
208
|
-
return canonical || legacy;
|
|
209
186
|
}
|
|
210
187
|
|
|
211
188
|
function comparisonWithDeclarationPriority(observed, declared, external, declarationTarget) {
|
|
@@ -232,13 +209,13 @@ function computeDigestEvidenceFromBytes(assetBytes, options = {}) {
|
|
|
232
209
|
const checksums = readJsonEntry(asset, 'checksums.json');
|
|
233
210
|
const assetDigest = computeAssetDigest(bytes);
|
|
234
211
|
const contentDigest = reader.contentDigestSync(asset);
|
|
235
|
-
const entrySetDigest =
|
|
212
|
+
const entrySetDigest = container.computeRuntimeEntrySetDigest(
|
|
236
213
|
asset.readEntry('kdna.json'),
|
|
237
214
|
asset.readEntry('payload.kdnab'),
|
|
238
215
|
);
|
|
239
216
|
const externalExpected = options.expectedDigests || {};
|
|
240
217
|
const expectedAsset = normalizeExpectedDigest(
|
|
241
|
-
externalExpected.asset
|
|
218
|
+
externalExpected.asset,
|
|
242
219
|
'caller',
|
|
243
220
|
);
|
|
244
221
|
const externalContent = normalizeExpectedDigest(externalExpected.content, 'caller');
|
|
@@ -248,6 +225,7 @@ function computeDigestEvidenceFromBytes(assetBytes, options = {}) {
|
|
|
248
225
|
|
|
249
226
|
return {
|
|
250
227
|
profile: DIGEST_PROFILE,
|
|
228
|
+
profile_version: DIGEST_PROFILE_VERSION,
|
|
251
229
|
asset: {
|
|
252
230
|
value: assetDigest,
|
|
253
231
|
basis: BASIS.asset,
|
|
@@ -377,63 +355,42 @@ function assertJsonValue(value, code, label) {
|
|
|
377
355
|
}
|
|
378
356
|
}
|
|
379
357
|
|
|
380
|
-
function
|
|
381
|
-
assertJsonValue(capsule, code, 'Capsule
|
|
382
|
-
const validator = schemaValidators().
|
|
383
|
-
if (!validator(capsule)) {
|
|
384
|
-
fail(code, 'Capsule 1 does not satisfy the frozen schema.', schemaErrorDetails(validator));
|
|
385
|
-
}
|
|
386
|
-
if (capsule.trace.schema_valid !== true) {
|
|
387
|
-
fail(code, 'Capsule 1 schema_valid must be true before building Capsule 2.');
|
|
388
|
-
}
|
|
389
|
-
if (capsule.signature.state !== capsule.trace.signature_state) {
|
|
390
|
-
fail(code, 'Capsule 1 signature state conflicts with its trace.');
|
|
391
|
-
}
|
|
392
|
-
if (capsule.profile !== capsule.trace.profile) {
|
|
393
|
-
fail(code, 'Capsule 1 profile conflicts with its trace.');
|
|
394
|
-
}
|
|
395
|
-
if (!['public', 'licensed', 'remote'].includes(capsule2Access(capsule.access))) {
|
|
396
|
-
fail(code, `Capsule 1 access is not a supported Runtime value: ${capsule.access}.`);
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
function assertCapsule2Success(capsule, code = 'KDNA_CAPSULE_ADAPTER_INPUT_INVALID') {
|
|
401
|
-
assertJsonValue(capsule, code, 'Capsule 2');
|
|
402
|
-
const validator = schemaValidators().capsule2;
|
|
358
|
+
function assertRuntimeCapsuleSuccess(capsule, code = 'KDNA_RUNTIME_CAPSULE_INVALID') {
|
|
359
|
+
assertJsonValue(capsule, code, 'Runtime Capsule');
|
|
360
|
+
const validator = schemaValidators().runtimeCapsule;
|
|
403
361
|
if (!validator(capsule)) {
|
|
404
|
-
fail(
|
|
362
|
+
fail(
|
|
363
|
+
code,
|
|
364
|
+
'Runtime Capsule does not satisfy the current contract.',
|
|
365
|
+
schemaErrorDetails(validator),
|
|
366
|
+
);
|
|
405
367
|
}
|
|
406
368
|
assertSuccessfulDigestEvidence(capsule.digests);
|
|
407
369
|
if (capsule.signature.state !== capsule.trace.signature_state) {
|
|
408
|
-
fail(code, 'Capsule
|
|
370
|
+
fail(code, 'Runtime Capsule signature state conflicts with its trace.');
|
|
409
371
|
}
|
|
410
372
|
if (capsule.profile !== capsule.trace.profile) {
|
|
411
|
-
fail(code, 'Capsule
|
|
412
|
-
}
|
|
413
|
-
const capsule1Access = capsule.compatibility?.capsule_1_access;
|
|
414
|
-
if (capsule1Access && CAPSULE_1_ACCESS_MAP[capsule1Access] !== capsule.access) {
|
|
415
|
-
fail(
|
|
416
|
-
code,
|
|
417
|
-
'Capsule 1 compatibility access does not map to Capsule 2 access.',
|
|
418
|
-
{
|
|
419
|
-
capsule_1_access: capsule1Access,
|
|
420
|
-
expected_capsule_2_access: CAPSULE_1_ACCESS_MAP[capsule1Access],
|
|
421
|
-
capsule_2_access: capsule.access,
|
|
422
|
-
},
|
|
423
|
-
);
|
|
373
|
+
fail(code, 'Runtime Capsule profile conflicts with its trace.');
|
|
424
374
|
}
|
|
425
375
|
}
|
|
426
376
|
|
|
427
377
|
function assertSuccessfulDigestEvidence(digests) {
|
|
428
|
-
if (
|
|
429
|
-
|
|
378
|
+
if (
|
|
379
|
+
!digests ||
|
|
380
|
+
digests.profile !== DIGEST_PROFILE ||
|
|
381
|
+
digests.profile_version !== DIGEST_PROFILE_VERSION
|
|
382
|
+
) {
|
|
383
|
+
fail(
|
|
384
|
+
'KDNA_RUNTIME_CAPSULE_DIGEST_EVIDENCE_INVALID',
|
|
385
|
+
'Runtime Capsule digest evidence is missing or uses the wrong profile coordinate.',
|
|
386
|
+
);
|
|
430
387
|
}
|
|
431
388
|
for (const name of ['asset', 'content', 'runtime_entry_set']) {
|
|
432
389
|
const item = digests[name];
|
|
433
390
|
if (!item || !SHA256_RE.test(item.value || '') || item.basis !== BASIS[name]) {
|
|
434
391
|
fail(
|
|
435
|
-
'
|
|
436
|
-
`Capsule
|
|
392
|
+
'KDNA_RUNTIME_CAPSULE_DIGEST_EVIDENCE_INVALID',
|
|
393
|
+
`Runtime Capsule ${name} digest evidence is invalid.`,
|
|
437
394
|
);
|
|
438
395
|
}
|
|
439
396
|
const comparisonState = item.comparison && item.comparison.state;
|
|
@@ -446,8 +403,8 @@ function assertSuccessfulDigestEvidence(digests) {
|
|
|
446
403
|
fail(
|
|
447
404
|
comparisonState === 'mismatched'
|
|
448
405
|
? mismatchCodes[name]
|
|
449
|
-
: '
|
|
450
|
-
`Capsule
|
|
406
|
+
: 'KDNA_RUNTIME_CAPSULE_DIGEST_EVIDENCE_INVALID',
|
|
407
|
+
`Runtime Capsule cannot be emitted with ${name} comparison ${comparisonState || 'missing'}.`,
|
|
451
408
|
{ digest: name, evidence: item },
|
|
452
409
|
);
|
|
453
410
|
}
|
|
@@ -458,8 +415,8 @@ function assertSuccessfulDigestEvidence(digests) {
|
|
|
458
415
|
!COMPARISON_SOURCES.has(item.comparison.source))
|
|
459
416
|
) {
|
|
460
417
|
fail(
|
|
461
|
-
'
|
|
462
|
-
`Capsule
|
|
418
|
+
'KDNA_RUNTIME_CAPSULE_DIGEST_EVIDENCE_INVALID',
|
|
419
|
+
`Runtime Capsule ${name} matched comparison is internally inconsistent.`,
|
|
463
420
|
);
|
|
464
421
|
}
|
|
465
422
|
if (
|
|
@@ -469,80 +426,60 @@ function assertSuccessfulDigestEvidence(digests) {
|
|
|
469
426
|
item.comparison.source !== null)
|
|
470
427
|
) {
|
|
471
428
|
fail(
|
|
472
|
-
'
|
|
473
|
-
`Capsule
|
|
429
|
+
'KDNA_RUNTIME_CAPSULE_DIGEST_EVIDENCE_INVALID',
|
|
430
|
+
`Runtime Capsule ${name} not_compared evidence must not claim an expectation.`,
|
|
474
431
|
);
|
|
475
432
|
}
|
|
476
433
|
}
|
|
477
434
|
}
|
|
478
435
|
|
|
479
|
-
function
|
|
480
|
-
|
|
481
|
-
if (access === 'protected') return 'licensed';
|
|
482
|
-
if (access === 'runtime') return 'remote';
|
|
483
|
-
return access;
|
|
436
|
+
function normalizeRuntimeAccess(access) {
|
|
437
|
+
return access === undefined ? 'public' : access;
|
|
484
438
|
}
|
|
485
439
|
|
|
486
|
-
function
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
440
|
+
function buildRuntimeCapsule({
|
|
441
|
+
projection,
|
|
442
|
+
manifest,
|
|
443
|
+
digests,
|
|
444
|
+
signature,
|
|
445
|
+
inputKind,
|
|
446
|
+
loadedAt,
|
|
447
|
+
schemaValid,
|
|
448
|
+
} = {}) {
|
|
449
|
+
if (!projection || typeof projection !== 'object' || Array.isArray(projection)) {
|
|
450
|
+
fail('KDNA_RUNTIME_CAPSULE_BUILD_INVALID', 'Runtime Capsule builder requires a projection.');
|
|
492
451
|
}
|
|
493
|
-
assertCapsule1Success(capsule1);
|
|
494
452
|
if (!manifest || typeof manifest !== 'object') {
|
|
495
|
-
fail('
|
|
453
|
+
fail('KDNA_RUNTIME_CAPSULE_BUILD_INVALID', 'Runtime Capsule builder requires the manifest.');
|
|
496
454
|
}
|
|
497
455
|
for (const field of ['asset_id', 'asset_uid', 'version', 'judgment_version']) {
|
|
498
456
|
if (typeof manifest[field] !== 'string' || manifest[field].length === 0) {
|
|
499
|
-
fail('
|
|
457
|
+
fail('KDNA_RUNTIME_CAPSULE_BUILD_INVALID', `Runtime manifest ${field} is required.`);
|
|
500
458
|
}
|
|
501
459
|
}
|
|
502
460
|
if (!['packaged_file', 'packaged_bytes'].includes(inputKind)) {
|
|
503
|
-
fail('KDNA_CAPSULE_2_BUILD_INVALID', 'Capsule 2 input_kind must identify packaged bytes.');
|
|
504
|
-
}
|
|
505
|
-
assertSuccessfulDigestEvidence(digests);
|
|
506
|
-
|
|
507
|
-
const expectedDomain = manifest.name || manifest.asset_id;
|
|
508
|
-
if (capsule1.domain !== expectedDomain) {
|
|
509
|
-
fail(
|
|
510
|
-
'KDNA_CAPSULE_2_BUILD_INVALID',
|
|
511
|
-
'Capsule 1 domain does not match the Runtime manifest identity.',
|
|
512
|
-
{ capsule_1: capsule1.domain, manifest: expectedDomain },
|
|
513
|
-
);
|
|
514
|
-
}
|
|
515
|
-
if (capsule1.judgment_version !== manifest.judgment_version) {
|
|
516
461
|
fail(
|
|
517
|
-
'
|
|
518
|
-
'Capsule
|
|
519
|
-
{ capsule_1: capsule1.judgment_version, manifest: manifest.judgment_version },
|
|
462
|
+
'KDNA_RUNTIME_CAPSULE_BUILD_INVALID',
|
|
463
|
+
'Runtime Capsule input_kind must identify packaged bytes.',
|
|
520
464
|
);
|
|
521
465
|
}
|
|
522
|
-
|
|
523
|
-
|
|
466
|
+
assertSuccessfulDigestEvidence(digests);
|
|
467
|
+
const timestamp = loadedAt || new Date().toISOString();
|
|
468
|
+
if (typeof timestamp !== 'string' || Number.isNaN(Date.parse(timestamp))) {
|
|
524
469
|
fail(
|
|
525
|
-
'
|
|
526
|
-
'Capsule
|
|
527
|
-
{ capsule_1: capsule1.access, manifest: expectedAccess },
|
|
470
|
+
'KDNA_RUNTIME_CAPSULE_BUILD_INVALID',
|
|
471
|
+
'Runtime Capsule loaded_at must be an ISO date-time string.',
|
|
528
472
|
);
|
|
529
473
|
}
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
'Capsule 1 asset_digest does not match Runtime entry-set digest E.',
|
|
534
|
-
{ capsule_1: capsule1.asset_digest, runtime_entry_set: digests.runtime_entry_set.value },
|
|
535
|
-
);
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
const timestamp = loadedAt || capsule1.trace?.loaded_at;
|
|
539
|
-
if (typeof timestamp !== 'string' || Number.isNaN(Date.parse(timestamp))) {
|
|
540
|
-
fail('KDNA_CAPSULE_2_BUILD_INVALID', 'Capsule 2 loaded_at must be an ISO date-time string.');
|
|
474
|
+
const runtimeAccess = normalizeRuntimeAccess(manifest.access);
|
|
475
|
+
if (!['public', 'licensed', 'remote'].includes(runtimeAccess)) {
|
|
476
|
+
fail('KDNA_RUNTIME_CAPSULE_BUILD_INVALID', `Unsupported Runtime access: ${runtimeAccess}.`);
|
|
541
477
|
}
|
|
478
|
+
const signatureEvidence = signature || { state: 'absent' };
|
|
542
479
|
|
|
543
480
|
const capsule = {
|
|
544
|
-
type: 'kdna.
|
|
545
|
-
|
|
481
|
+
type: 'kdna.runtime-capsule',
|
|
482
|
+
contract_version: RUNTIME_CAPSULE_CONTRACT_VERSION,
|
|
546
483
|
asset: {
|
|
547
484
|
asset_id: manifest.asset_id,
|
|
548
485
|
asset_uid: manifest.asset_uid,
|
|
@@ -550,138 +487,42 @@ function buildCapsuleV2({ capsule1, manifest, digests, inputKind, loadedAt } = {
|
|
|
550
487
|
judgment_version: manifest.judgment_version,
|
|
551
488
|
},
|
|
552
489
|
digests,
|
|
553
|
-
signature:
|
|
554
|
-
access:
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
context: capsule1.context,
|
|
490
|
+
signature: signatureEvidence,
|
|
491
|
+
access: runtimeAccess,
|
|
492
|
+
profile: projection.profile,
|
|
493
|
+
context: projection.content || {},
|
|
558
494
|
trace: {
|
|
559
|
-
payload_encoding:
|
|
495
|
+
payload_encoding: manifest.payload?.encoding || 'cbor',
|
|
560
496
|
loaded_by: 'kdna-core',
|
|
561
497
|
loaded_at: timestamp,
|
|
562
498
|
input_kind: inputKind,
|
|
563
499
|
runtime_eligible: true,
|
|
564
|
-
schema_valid:
|
|
565
|
-
signature_state:
|
|
566
|
-
profile:
|
|
500
|
+
schema_valid: schemaValid === true,
|
|
501
|
+
signature_state: signatureEvidence.state,
|
|
502
|
+
profile: projection.profile,
|
|
567
503
|
},
|
|
568
504
|
};
|
|
569
|
-
|
|
570
|
-
// Capsule 2 identity is always manifest.asset_id. A distinct legacy name is
|
|
571
|
-
// carried only so the one-way v2 -> v1 adapter can reproduce the frozen v1
|
|
572
|
-
// `domain` value. It has no routing or identity authority in Capsule 2.
|
|
573
|
-
const compatibility = {};
|
|
574
|
-
if (capsule1.domain && capsule1.domain !== manifest.asset_id) {
|
|
575
|
-
compatibility.capsule_1_domain = capsule1.domain;
|
|
576
|
-
}
|
|
577
|
-
if (CAPSULE_1_ACCESS_MAP[capsule1.access]) {
|
|
578
|
-
compatibility.capsule_1_access = capsule1.access;
|
|
579
|
-
}
|
|
580
|
-
const capsule1Extensions = {};
|
|
581
|
-
for (const key of CAPSULE_1_EXTENSION_KEYS) {
|
|
582
|
-
if (Object.prototype.hasOwnProperty.call(capsule1, key)) {
|
|
583
|
-
capsule1Extensions[key] = capsule1[key];
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
if (Object.keys(capsule1Extensions).length > 0) {
|
|
587
|
-
compatibility.capsule_1_extensions = capsule1Extensions;
|
|
588
|
-
}
|
|
589
|
-
if (Object.keys(compatibility).length > 0) {
|
|
590
|
-
capsule.compatibility = compatibility;
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
assertCapsule2Success(capsule, 'KDNA_CAPSULE_2_BUILD_INVALID');
|
|
505
|
+
assertRuntimeCapsuleSuccess(capsule, 'KDNA_RUNTIME_CAPSULE_BUILD_INVALID');
|
|
594
506
|
return capsule;
|
|
595
507
|
}
|
|
596
508
|
|
|
597
|
-
function
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
}
|
|
601
|
-
assertCapsule2Success(capsule);
|
|
602
|
-
const domain = capsule.compatibility?.capsule_1_domain || capsule.asset?.asset_id;
|
|
603
|
-
if (typeof domain !== 'string' || domain.length === 0) {
|
|
604
|
-
fail('KDNA_CAPSULE_ADAPTER_INPUT_INVALID', 'Capsule 2 has no Capsule 1 domain mapping.');
|
|
605
|
-
}
|
|
606
|
-
const capsule1 = {
|
|
607
|
-
type: 'kdna.context.capsule',
|
|
608
|
-
version: '1.0',
|
|
609
|
-
domain,
|
|
610
|
-
judgment_version: capsule.asset.judgment_version,
|
|
611
|
-
asset_digest: capsule.digests.runtime_entry_set.value,
|
|
612
|
-
signature: capsule.signature,
|
|
613
|
-
access: capsule.compatibility?.capsule_1_access || capsule.access,
|
|
614
|
-
risk_level: capsule.risk_level,
|
|
615
|
-
profile: capsule.profile,
|
|
616
|
-
context: capsule.context,
|
|
617
|
-
trace: {
|
|
618
|
-
payload_encoding: capsule.trace.payload_encoding,
|
|
619
|
-
loaded_by: capsule.trace.loaded_by,
|
|
620
|
-
loaded_at: capsule.trace.loaded_at,
|
|
621
|
-
schema_valid: capsule.trace.schema_valid,
|
|
622
|
-
signature_state: capsule.trace.signature_state,
|
|
623
|
-
profile: capsule.trace.profile,
|
|
624
|
-
},
|
|
625
|
-
};
|
|
626
|
-
const capsule1Extensions = capsule.compatibility?.capsule_1_extensions;
|
|
627
|
-
if (capsule1Extensions) {
|
|
628
|
-
for (const key of CAPSULE_1_EXTENSION_KEYS) {
|
|
629
|
-
if (Object.prototype.hasOwnProperty.call(capsule1Extensions, key)) {
|
|
630
|
-
capsule1[key] = capsule1Extensions[key];
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
assertCapsule1Success(capsule1, 'KDNA_CAPSULE_ADAPTER_OUTPUT_INVALID');
|
|
635
|
-
return capsule1;
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
function loadCapsuleV2(input, options = {}) {
|
|
639
|
-
const { bytes, inputKind } = snapshotPackagedBytes(input);
|
|
640
|
-
let validation;
|
|
641
|
-
try {
|
|
642
|
-
validation = v1.validate(bytes);
|
|
643
|
-
} catch (error) {
|
|
644
|
-
fail('KDNA_CAPSULE_2_ASSET_INVALID', error.message);
|
|
645
|
-
}
|
|
646
|
-
if (validation.overall_valid !== true) {
|
|
647
|
-
fail(
|
|
648
|
-
'KDNA_CAPSULE_2_ASSET_INVALID',
|
|
649
|
-
`Cannot emit Capsule 2 from an invalid asset: ${validation.problems.join('; ')}`,
|
|
650
|
-
validation,
|
|
651
|
-
);
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
const digests = computeDigestEvidenceFromBytes(bytes, {
|
|
655
|
-
expectedDigests: options.expectedDigests,
|
|
656
|
-
expectedAssetDigest: options.expectedAssetDigest,
|
|
657
|
-
});
|
|
658
|
-
assertSuccessfulDigestEvidence(digests);
|
|
659
|
-
const reader = createKdnaAssetReader();
|
|
660
|
-
const asset = reader.openSync(bytes);
|
|
661
|
-
const manifest = reader.readManifestSync(asset);
|
|
662
|
-
const capsule1 = v1.loadAuthorized(bytes, {
|
|
663
|
-
...options,
|
|
664
|
-
as: 'json',
|
|
665
|
-
});
|
|
666
|
-
return buildCapsuleV2({
|
|
667
|
-
capsule1,
|
|
668
|
-
manifest,
|
|
669
|
-
digests,
|
|
670
|
-
inputKind,
|
|
671
|
-
loadedAt: options.loadedAt,
|
|
672
|
-
});
|
|
509
|
+
function loadRuntimeCapsule(input, options = {}) {
|
|
510
|
+
snapshotPackagedBytes(input);
|
|
511
|
+
return container.loadAuthorized(input, { ...options, as: 'json' });
|
|
673
512
|
}
|
|
674
513
|
|
|
675
514
|
module.exports = {
|
|
676
515
|
BASIS,
|
|
677
516
|
CAPSULE_DIGEST_PROFILE,
|
|
517
|
+
CANONICALIZATION_PROFILE_VERSION,
|
|
678
518
|
DIGEST_PROFILE,
|
|
679
|
-
|
|
680
|
-
|
|
519
|
+
DIGEST_PROFILE_VERSION,
|
|
520
|
+
RUNTIME_CAPSULE_CONTRACT_VERSION,
|
|
521
|
+
buildRuntimeCapsule,
|
|
681
522
|
canonicalizeJcs,
|
|
682
523
|
computeAssetDigest,
|
|
683
524
|
computeCapsuleDeliveryDigest,
|
|
684
525
|
computeDigestEvidence,
|
|
685
|
-
computeRuntimeEntrySetDigest:
|
|
686
|
-
|
|
526
|
+
computeRuntimeEntrySetDigest: container.computeRuntimeEntrySetDigest,
|
|
527
|
+
loadRuntimeCapsule,
|
|
687
528
|
};
|