@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.
@@ -0,0 +1,1850 @@
1
+ 'use strict';
2
+
3
+ const crypto = require('node:crypto');
4
+
5
+ const Ajv2020 = require('ajv/dist/2020');
6
+ const addFormats = require('ajv-formats');
7
+
8
+ const { canonicalizeJcs, computeCapsuleDeliveryDigest } = require('./capsule-v2');
9
+ const digestEvidenceSchema = require('../schema/digest-evidence.schema.json');
10
+ const capsule2Schema = require('../schema/runtime-capsule-2.schema.json');
11
+ const planSchema = require('../schema/consumption-plan-1.schema.json');
12
+ const capabilitiesSchema = require('../schema/agent-host-capabilities-1.schema.json');
13
+ const requestSchema = require('../schema/agent-host-2-request.schema.json');
14
+ const receiptSchema = require('../schema/agent-host-2-receipt.schema.json');
15
+ const traceSchema = require('../schema/judgment-trace-1.schema.json');
16
+
17
+ const PLAN_DIGEST_PROFILE = 'kdna-consumption-plan-jcs-v1';
18
+ const CAPSULE_DELIVERY_PROFILE = 'kdna-capsule-jcs-v1';
19
+ const CAPSULE_DIGEST_PROFILE = 'kdna-capsule-digests-v1';
20
+ const HOST_PROTOCOL = 'kdna.agent-host/2';
21
+ const CAPSULE_VERSION = '2.0';
22
+ const DEFAULT_CORE_CAPSULE_VERSIONS = Object.freeze(['2.0', '1.0']);
23
+ const JSON_MAX_BYTES = 2 * 1024 * 1024;
24
+ const JSON_MAX_DEPTH = 64;
25
+ const PRE_HOST_INTERNAL_REQUEST_ID = 'host_000000000000000000000000';
26
+ const PRE_HOST_BUDGET_ERROR = Object.freeze({
27
+ code: 'KDNA_HOST_BUDGET_LIMIT_EXCEEDED',
28
+ message: 'Pre-Host projection or task budget exceeded.',
29
+ phase: 'budget',
30
+ });
31
+
32
+ let validators;
33
+
34
+ class KDNAExecutionContractError extends Error {
35
+ constructor(code, message, details) {
36
+ super(message);
37
+ this.name = 'KDNAExecutionContractError';
38
+ this.code = code;
39
+ if (details !== undefined) this.details = details;
40
+ }
41
+ }
42
+
43
+ function schemaValidators() {
44
+ if (validators) return validators;
45
+ const ajv = new Ajv2020({ allErrors: true, strict: false });
46
+ addFormats(ajv);
47
+ for (const schema of [
48
+ digestEvidenceSchema,
49
+ capsule2Schema,
50
+ planSchema,
51
+ capabilitiesSchema,
52
+ requestSchema,
53
+ receiptSchema,
54
+ traceSchema,
55
+ ]) {
56
+ ajv.addSchema(schema);
57
+ }
58
+ validators = {
59
+ plan: ajv.getSchema(planSchema.$id),
60
+ capabilities: ajv.getSchema(capabilitiesSchema.$id),
61
+ request: ajv.getSchema(requestSchema.$id),
62
+ receipt: ajv.getSchema(receiptSchema.$id),
63
+ trace: ajv.getSchema(traceSchema.$id),
64
+ };
65
+ return validators;
66
+ }
67
+
68
+ function ok(value) {
69
+ return { valid: true, code: null, errors: [], ...(value === undefined ? {} : { value }) };
70
+ }
71
+
72
+ function invalid(code, message, details) {
73
+ return {
74
+ valid: false,
75
+ code,
76
+ errors: [{ code, message, ...(details === undefined ? {} : { details }) }],
77
+ };
78
+ }
79
+
80
+ function throwInvalid(result) {
81
+ if (result.valid) return;
82
+ const first = result.errors[0] || {};
83
+ throw new KDNAExecutionContractError(
84
+ result.code || 'KDNA_EXECUTION_CONTRACT_INVALID',
85
+ first.message || 'KDNA execution contract is invalid.',
86
+ first.details,
87
+ );
88
+ }
89
+
90
+ function clone(value) {
91
+ return globalThis.structuredClone(value);
92
+ }
93
+
94
+ function equalJson(left, right) {
95
+ try {
96
+ return canonicalizeJcs(left) === canonicalizeJcs(right);
97
+ } catch {
98
+ return false;
99
+ }
100
+ }
101
+
102
+ function digestJcs(value) {
103
+ return `sha256:${crypto
104
+ .createHash('sha256')
105
+ .update(Buffer.from(canonicalizeJcs(value), 'utf8'))
106
+ .digest('hex')}`;
107
+ }
108
+
109
+ function jcsCharacterCount(value) {
110
+ return [...canonicalizeJcs(value)].length;
111
+ }
112
+
113
+ function snapshotJsonValue(value) {
114
+ return parseExecutionContractJsonV1(canonicalizeJcs(value));
115
+ }
116
+
117
+ function validateSchema(kind, value) {
118
+ const validator = schemaValidators()[kind];
119
+ try {
120
+ const snapshot = snapshotJsonValue(value);
121
+ if (validator(snapshot)) return ok(snapshot);
122
+ const details = clone(validator.errors || []);
123
+ if (
124
+ kind === 'plan' &&
125
+ details.some(
126
+ (error) =>
127
+ error.keyword === 'const' &&
128
+ error.instancePath === '/projection_request/require_packaged_asset',
129
+ )
130
+ ) {
131
+ return invalid(
132
+ 'KDNA_ASSET_FILE_REQUIRED',
133
+ 'ConsumptionPlan 1 requires a packaged .kdna Runtime asset.',
134
+ details,
135
+ );
136
+ }
137
+ if (
138
+ kind === 'trace' &&
139
+ snapshot &&
140
+ snapshot.overall_status === 'execution_completed' &&
141
+ snapshot.budget?.comparison?.overall === 'exceeded'
142
+ ) {
143
+ return invalid(
144
+ 'KDNA_TRACE_BUDGET_LIMIT_VIOLATION',
145
+ 'A completed JudgmentTrace cannot cross an enforced budget limit.',
146
+ details,
147
+ );
148
+ }
149
+ return invalid('SCHEMA_INVALID', `${kind} does not match its packaged JSON Schema.`, details);
150
+ } catch (error) {
151
+ return invalid('KDNA_INPUT_INVALID', `${kind} is not a safe JSON value.`, {
152
+ cause: error && typeof error.message === 'string' ? error.message : String(error),
153
+ });
154
+ }
155
+ }
156
+
157
+ function snapshotContext(context, keys) {
158
+ try {
159
+ if (
160
+ context === null ||
161
+ typeof context !== 'object' ||
162
+ Array.isArray(context) ||
163
+ Object.getPrototypeOf(context) !== Object.prototype
164
+ ) {
165
+ return invalid(
166
+ 'KDNA_VALIDATION_CONTEXT_INVALID',
167
+ 'Validation context must be a plain object.',
168
+ );
169
+ }
170
+ const descriptors = Object.getOwnPropertyDescriptors(context);
171
+ if (
172
+ Reflect.ownKeys(descriptors).some(
173
+ (key) =>
174
+ typeof key !== 'string' ||
175
+ !descriptors[key].enumerable ||
176
+ !Object.prototype.hasOwnProperty.call(descriptors[key], 'value'),
177
+ )
178
+ ) {
179
+ return invalid(
180
+ 'KDNA_VALIDATION_CONTEXT_INVALID',
181
+ 'Validation context must contain only enumerable data properties.',
182
+ );
183
+ }
184
+
185
+ const snapshot = {};
186
+ for (const key of keys) {
187
+ const descriptor = descriptors[key];
188
+ if (!descriptor) {
189
+ return invalid(
190
+ 'KDNA_VALIDATION_CONTEXT_INVALID',
191
+ `Validation context must explicitly provide ${key}.`,
192
+ );
193
+ }
194
+ snapshot[key] = snapshotJsonValue(descriptor.value);
195
+ }
196
+ return ok(snapshot);
197
+ } catch (error) {
198
+ return invalid(
199
+ 'KDNA_VALIDATION_CONTEXT_INVALID',
200
+ 'Validation context could not be safely snapshotted.',
201
+ { cause: error && typeof error.message === 'string' ? error.message : String(error) },
202
+ );
203
+ }
204
+ }
205
+
206
+ function snapshotPublicInput(input, requiredKeys, optionalKeys = []) {
207
+ try {
208
+ if (
209
+ input === null ||
210
+ typeof input !== 'object' ||
211
+ Array.isArray(input) ||
212
+ Object.getPrototypeOf(input) !== Object.prototype
213
+ ) {
214
+ return invalid('KDNA_INPUT_INVALID', 'Builder input must be a plain object.');
215
+ }
216
+ const descriptors = Object.getOwnPropertyDescriptors(input);
217
+ const allowedKeys = new Set([...requiredKeys, ...optionalKeys]);
218
+ if (
219
+ Reflect.ownKeys(descriptors).some(
220
+ (key) =>
221
+ typeof key !== 'string' ||
222
+ !allowedKeys.has(key) ||
223
+ !descriptors[key].enumerable ||
224
+ !Object.prototype.hasOwnProperty.call(descriptors[key], 'value'),
225
+ )
226
+ ) {
227
+ return invalid(
228
+ 'KDNA_INPUT_INVALID',
229
+ 'Builder input must contain only declared enumerable data properties.',
230
+ );
231
+ }
232
+ const snapshot = {};
233
+ for (const key of requiredKeys) {
234
+ if (!descriptors[key]) {
235
+ return invalid('KDNA_INPUT_INVALID', `Builder input must explicitly provide ${key}.`);
236
+ }
237
+ snapshot[key] = snapshotJsonValue(descriptors[key].value);
238
+ }
239
+ for (const key of optionalKeys) {
240
+ if (descriptors[key]) snapshot[key] = snapshotJsonValue(descriptors[key].value);
241
+ }
242
+ return ok(snapshot);
243
+ } catch (error) {
244
+ return invalid('KDNA_INPUT_INVALID', 'Builder input could not be safely snapshotted.', {
245
+ cause: error && typeof error.message === 'string' ? error.message : String(error),
246
+ });
247
+ }
248
+ }
249
+
250
+ /**
251
+ * Parse an execution-contract JSON value without JSON.parse semantics.
252
+ * Duplicate member names are rejected after JSON escape decoding, so
253
+ * {"a": 1, "\\u0061": 2} is also rejected.
254
+ */
255
+ function parseExecutionContractJsonV1(input, options = {}) {
256
+ if (
257
+ options === null ||
258
+ typeof options !== 'object' ||
259
+ Array.isArray(options) ||
260
+ Object.getPrototypeOf(options) !== Object.prototype
261
+ ) {
262
+ throw new KDNAExecutionContractError(
263
+ 'KDNA_JSON_OPTIONS_INVALID',
264
+ 'Execution-contract JSON parser options must be a plain object.',
265
+ );
266
+ }
267
+ const descriptors = Object.getOwnPropertyDescriptors(options);
268
+ const optionKeys = Reflect.ownKeys(descriptors);
269
+ if (
270
+ optionKeys.some(
271
+ (key) =>
272
+ typeof key !== 'string' ||
273
+ !['maxBytes', 'maxDepth'].includes(key) ||
274
+ !Object.prototype.hasOwnProperty.call(descriptors[key], 'value'),
275
+ )
276
+ ) {
277
+ throw new KDNAExecutionContractError(
278
+ 'KDNA_JSON_OPTIONS_INVALID',
279
+ 'Execution-contract JSON parser options contain an unknown or accessor property.',
280
+ );
281
+ }
282
+ const maxBytes = options.maxBytes ?? JSON_MAX_BYTES;
283
+ const maxDepth = options.maxDepth ?? JSON_MAX_DEPTH;
284
+ if (
285
+ !Number.isSafeInteger(maxBytes) ||
286
+ maxBytes < 1 ||
287
+ maxBytes > JSON_MAX_BYTES ||
288
+ !Number.isSafeInteger(maxDepth) ||
289
+ maxDepth < 1 ||
290
+ maxDepth > JSON_MAX_DEPTH
291
+ ) {
292
+ throw new KDNAExecutionContractError(
293
+ 'KDNA_JSON_OPTIONS_INVALID',
294
+ `maxBytes and maxDepth must be positive safe integers no greater than ${JSON_MAX_BYTES} and ${JSON_MAX_DEPTH}.`,
295
+ );
296
+ }
297
+ let text;
298
+ if (typeof input === 'string') {
299
+ text = input;
300
+ } else if (Buffer.isBuffer(input) || input instanceof Uint8Array) {
301
+ if (input.byteLength > maxBytes) {
302
+ throw new KDNAExecutionContractError(
303
+ 'KDNA_JSON_LIMIT_EXCEEDED',
304
+ 'Execution-contract JSON exceeds the configured byte limit.',
305
+ );
306
+ }
307
+ if (input.byteLength >= 3 && input[0] === 0xef && input[1] === 0xbb && input[2] === 0xbf) {
308
+ throw new KDNAExecutionContractError(
309
+ 'KDNA_JSON_BOM_FORBIDDEN',
310
+ 'Execution-contract JSON must not begin with a UTF-8 BOM.',
311
+ );
312
+ }
313
+ try {
314
+ text = new TextDecoder('utf-8', { fatal: true }).decode(input);
315
+ } catch {
316
+ throw new KDNAExecutionContractError(
317
+ 'KDNA_JSON_INVALID_UNICODE',
318
+ 'Execution-contract JSON is not valid UTF-8.',
319
+ );
320
+ }
321
+ } else {
322
+ throw new KDNAExecutionContractError(
323
+ 'KDNA_JSON_INPUT_INVALID',
324
+ 'Execution-contract JSON input must be a string, Buffer, or Uint8Array.',
325
+ );
326
+ }
327
+
328
+ if (text.charCodeAt(0) === 0xfeff) {
329
+ throw new KDNAExecutionContractError(
330
+ 'KDNA_JSON_BOM_FORBIDDEN',
331
+ 'Execution-contract JSON must not begin with a BOM.',
332
+ );
333
+ }
334
+
335
+ if (Buffer.byteLength(text, 'utf8') > maxBytes) {
336
+ throw new KDNAExecutionContractError(
337
+ 'KDNA_JSON_LIMIT_EXCEEDED',
338
+ 'Execution-contract JSON exceeds the configured byte limit.',
339
+ );
340
+ }
341
+
342
+ const parser = new StrictJsonParser(text, maxDepth);
343
+ return parser.parse();
344
+ }
345
+
346
+ class StrictJsonParser {
347
+ constructor(text, maxDepth) {
348
+ this.text = text;
349
+ this.maxDepth = maxDepth;
350
+ this.index = 0;
351
+ }
352
+
353
+ parse() {
354
+ this.skipWhitespace();
355
+ const value = this.parseValue(0);
356
+ this.skipWhitespace();
357
+ if (this.index !== this.text.length) this.syntax('Unexpected trailing JSON content.');
358
+ return value;
359
+ }
360
+
361
+ syntax(message) {
362
+ throw new KDNAExecutionContractError('KDNA_JSON_SYNTAX_INVALID', message, {
363
+ code_unit_offset: this.index,
364
+ });
365
+ }
366
+
367
+ skipWhitespace() {
368
+ while (
369
+ this.index < this.text.length &&
370
+ (this.text[this.index] === ' ' ||
371
+ this.text[this.index] === '\n' ||
372
+ this.text[this.index] === '\r' ||
373
+ this.text[this.index] === '\t')
374
+ ) {
375
+ this.index += 1;
376
+ }
377
+ }
378
+
379
+ parseValue(depth) {
380
+ this.skipWhitespace();
381
+ const char = this.text[this.index];
382
+ if (char === '{' || char === '[') {
383
+ if (depth >= this.maxDepth) {
384
+ throw new KDNAExecutionContractError(
385
+ 'KDNA_JSON_LIMIT_EXCEEDED',
386
+ 'Execution-contract JSON exceeds the configured nesting limit.',
387
+ );
388
+ }
389
+ return char === '{' ? this.parseObject(depth + 1) : this.parseArray(depth + 1);
390
+ }
391
+ if (char === '"') return this.parseString();
392
+ if (char === '-' || (char >= '0' && char <= '9')) return this.parseNumber();
393
+ if (this.text.startsWith('true', this.index)) {
394
+ this.index += 4;
395
+ return true;
396
+ }
397
+ if (this.text.startsWith('false', this.index)) {
398
+ this.index += 5;
399
+ return false;
400
+ }
401
+ if (this.text.startsWith('null', this.index)) {
402
+ this.index += 4;
403
+ return null;
404
+ }
405
+ this.syntax('Expected a JSON value.');
406
+ }
407
+
408
+ parseObject(depth) {
409
+ const output = {};
410
+ const keys = new Set();
411
+ this.index += 1;
412
+ this.skipWhitespace();
413
+ if (this.text[this.index] === '}') {
414
+ this.index += 1;
415
+ return output;
416
+ }
417
+ while (this.index < this.text.length) {
418
+ if (this.text[this.index] !== '"') this.syntax('Expected a quoted JSON member name.');
419
+ const key = this.parseString();
420
+ if (keys.has(key)) {
421
+ throw new KDNAExecutionContractError(
422
+ 'KDNA_JSON_DUPLICATE_KEY',
423
+ `Execution-contract JSON contains duplicate member name ${JSON.stringify(key)}.`,
424
+ { key, code_unit_offset: this.index },
425
+ );
426
+ }
427
+ keys.add(key);
428
+ this.skipWhitespace();
429
+ if (this.text[this.index] !== ':') this.syntax('Expected a colon after a JSON member name.');
430
+ this.index += 1;
431
+ Object.defineProperty(output, key, {
432
+ value: this.parseValue(depth),
433
+ enumerable: true,
434
+ writable: true,
435
+ configurable: true,
436
+ });
437
+ this.skipWhitespace();
438
+ const next = this.text[this.index];
439
+ if (next === '}') {
440
+ this.index += 1;
441
+ return output;
442
+ }
443
+ if (next !== ',') this.syntax('Expected a comma or closing brace.');
444
+ this.index += 1;
445
+ this.skipWhitespace();
446
+ }
447
+ this.syntax('Unterminated JSON object.');
448
+ }
449
+
450
+ parseArray(depth) {
451
+ const output = [];
452
+ this.index += 1;
453
+ this.skipWhitespace();
454
+ if (this.text[this.index] === ']') {
455
+ this.index += 1;
456
+ return output;
457
+ }
458
+ while (this.index < this.text.length) {
459
+ output.push(this.parseValue(depth));
460
+ this.skipWhitespace();
461
+ const next = this.text[this.index];
462
+ if (next === ']') {
463
+ this.index += 1;
464
+ return output;
465
+ }
466
+ if (next !== ',') this.syntax('Expected a comma or closing bracket.');
467
+ this.index += 1;
468
+ this.skipWhitespace();
469
+ }
470
+ this.syntax('Unterminated JSON array.');
471
+ }
472
+
473
+ parseString() {
474
+ this.index += 1;
475
+ let output = '';
476
+ while (this.index < this.text.length) {
477
+ const char = this.text[this.index];
478
+ if (char === '"') {
479
+ this.index += 1;
480
+ return output;
481
+ }
482
+ if (char === '\\') {
483
+ this.index += 1;
484
+ output += this.parseEscape();
485
+ continue;
486
+ }
487
+ const code = this.text.charCodeAt(this.index);
488
+ if (code <= 0x1f) this.syntax('Unescaped control character in JSON string.');
489
+ if (code >= 0xd800 && code <= 0xdbff) {
490
+ const low = this.text.charCodeAt(this.index + 1);
491
+ if (low < 0xdc00 || low > 0xdfff) this.invalidUnicode();
492
+ output += this.text.slice(this.index, this.index + 2);
493
+ this.index += 2;
494
+ continue;
495
+ }
496
+ if (code >= 0xdc00 && code <= 0xdfff) this.invalidUnicode();
497
+ output += char;
498
+ this.index += 1;
499
+ }
500
+ this.syntax('Unterminated JSON string.');
501
+ }
502
+
503
+ parseEscape() {
504
+ const char = this.text[this.index];
505
+ const simple = { '"': '"', '\\': '\\', '/': '/', b: '\b', f: '\f', n: '\n', r: '\r', t: '\t' };
506
+ if (Object.prototype.hasOwnProperty.call(simple, char)) {
507
+ this.index += 1;
508
+ return simple[char];
509
+ }
510
+ if (char !== 'u') this.syntax('Invalid JSON string escape.');
511
+ this.index += 1;
512
+ const first = this.parseHexCodeUnit();
513
+ if (first >= 0xd800 && first <= 0xdbff) {
514
+ if (this.text.slice(this.index, this.index + 2) !== '\\u') this.invalidUnicode();
515
+ this.index += 2;
516
+ const second = this.parseHexCodeUnit();
517
+ if (second < 0xdc00 || second > 0xdfff) this.invalidUnicode();
518
+ return String.fromCodePoint(0x10000 + ((first - 0xd800) << 10) + (second - 0xdc00));
519
+ }
520
+ if (first >= 0xdc00 && first <= 0xdfff) this.invalidUnicode();
521
+ return String.fromCharCode(first);
522
+ }
523
+
524
+ parseHexCodeUnit() {
525
+ const hex = this.text.slice(this.index, this.index + 4);
526
+ if (!/^[0-9a-fA-F]{4}$/.test(hex)) this.syntax('Invalid Unicode escape in JSON string.');
527
+ this.index += 4;
528
+ return Number.parseInt(hex, 16);
529
+ }
530
+
531
+ invalidUnicode() {
532
+ throw new KDNAExecutionContractError(
533
+ 'KDNA_JSON_INVALID_UNICODE',
534
+ 'Execution-contract JSON contains an unpaired UTF-16 surrogate.',
535
+ { code_unit_offset: this.index },
536
+ );
537
+ }
538
+
539
+ parseNumber() {
540
+ const source = this.text.slice(this.index);
541
+ const match = /^-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?/.exec(source);
542
+ if (!match) this.syntax('Invalid JSON number.');
543
+ const token = match[0];
544
+ this.index += token.length;
545
+ const number = Number(token);
546
+ if (!Number.isFinite(number)) {
547
+ throw new KDNAExecutionContractError(
548
+ 'KDNA_JSON_NUMBER_INVALID',
549
+ 'Execution-contract JSON number is outside the finite IEEE-754 range.',
550
+ );
551
+ }
552
+ return number;
553
+ }
554
+ }
555
+
556
+ function computeConsumptionPlanDigestV1(plan) {
557
+ try {
558
+ if (plan === null || typeof plan !== 'object' || Array.isArray(plan)) {
559
+ throw new TypeError('plan must be an object');
560
+ }
561
+ const projection = clone(plan);
562
+ delete projection.integrity;
563
+ return digestJcs(projection);
564
+ } catch (error) {
565
+ if (error instanceof KDNAExecutionContractError) throw error;
566
+ throw new KDNAExecutionContractError(
567
+ 'KDNA_PLAN_INPUT_INVALID',
568
+ 'ConsumptionPlan digest input must be a finite, acyclic JSON object.',
569
+ { cause: error && typeof error.message === 'string' ? error.message : String(error) },
570
+ );
571
+ }
572
+ }
573
+
574
+ function validatePlanSemantics(plan, trustedPlanDigest) {
575
+ let computed;
576
+ try {
577
+ computed = computeConsumptionPlanDigestV1(plan);
578
+ } catch {
579
+ return 'KDNA_PLAN_INPUT_INVALID';
580
+ }
581
+ if (computed !== plan.integrity.plan_digest) return 'KDNA_PLAN_DIGEST_MISMATCH';
582
+ if (trustedPlanDigest !== null && computed !== trustedPlanDigest) {
583
+ return 'KDNA_TRUSTED_PLAN_DIGEST_MISMATCH';
584
+ }
585
+ return null;
586
+ }
587
+
588
+ function validateConsumptionPlanV1(plan, context) {
589
+ const contextResult = snapshotContext(context, ['trustedPlanDigest']);
590
+ if (!contextResult.valid) return contextResult;
591
+ const safeContext = contextResult.value;
592
+ const schemaResult = validateSchema('plan', plan);
593
+ if (!schemaResult.valid) return schemaResult;
594
+ const safePlan = schemaResult.value;
595
+ if (
596
+ typeof safeContext.trustedPlanDigest !== 'string' ||
597
+ !/^sha256:[0-9a-f]{64}$/.test(safeContext.trustedPlanDigest)
598
+ ) {
599
+ return invalid(
600
+ 'KDNA_VALIDATION_CONTEXT_INVALID',
601
+ 'trustedPlanDigest must be a non-null lowercase sha256 digest.',
602
+ );
603
+ }
604
+ const code = validatePlanSemantics(safePlan, safeContext.trustedPlanDigest);
605
+ return code ? invalid(code, 'ConsumptionPlan integrity correlation failed.') : ok(safePlan);
606
+ }
607
+
608
+ function buildConsumptionPlanV1(input) {
609
+ let plan;
610
+ try {
611
+ plan = {
612
+ type: 'kdna.consumption.plan',
613
+ plan_version: '1.0.0',
614
+ plan_id: input.plan_id,
615
+ created_at: input.created_at,
616
+ mode: 'single',
617
+ task: clone(input.task),
618
+ asset_ref: clone(input.asset_ref),
619
+ projection_request: {
620
+ profile: input.projection_profile,
621
+ accepted_capsule_versions: [CAPSULE_VERSION],
622
+ required_digest_profile: CAPSULE_DIGEST_PROFILE,
623
+ require_packaged_asset: true,
624
+ },
625
+ host_request: { accepted_protocols: [HOST_PROTOCOL] },
626
+ result_request: { shape: 'structured_judgment' },
627
+ budget: clone(input.budget),
628
+ trace_policy: clone(input.trace_policy),
629
+ integrity: { profile: PLAN_DIGEST_PROFILE, plan_digest: '' },
630
+ constraints: clone(input.constraints),
631
+ };
632
+ plan.integrity.plan_digest = computeConsumptionPlanDigestV1(plan);
633
+ } catch (error) {
634
+ if (error instanceof KDNAExecutionContractError) throw error;
635
+ throw new KDNAExecutionContractError(
636
+ 'KDNA_PLAN_INPUT_INVALID',
637
+ 'Cannot build ConsumptionPlan 1 from the supplied input.',
638
+ );
639
+ }
640
+ const result = validateConsumptionPlanV1(plan, {
641
+ trustedPlanDigest: plan.integrity.plan_digest,
642
+ });
643
+ throwInvalid(result);
644
+ return plan;
645
+ }
646
+
647
+ function blocked(issueCode) {
648
+ return {
649
+ state: 'blocked',
650
+ capsule_version: null,
651
+ host_protocol: null,
652
+ issue_code: issueCode,
653
+ };
654
+ }
655
+
656
+ function negotiateExecutionPairV1(plan, context) {
657
+ const contextResult = snapshotContext(context, [
658
+ 'trustedPlanDigest',
659
+ 'capabilities',
660
+ 'coreCapsuleVersions',
661
+ ]);
662
+ if (!contextResult.valid) return blocked(contextResult.code);
663
+ const safeContext = contextResult.value;
664
+ const planResult = validateConsumptionPlanV1(plan, {
665
+ trustedPlanDigest: safeContext.trustedPlanDigest,
666
+ });
667
+ if (!planResult.valid) return blocked(planResult.code);
668
+ const safePlan = planResult.value;
669
+ const capabilitiesResult = validateSchema('capabilities', safeContext.capabilities);
670
+ if (!capabilitiesResult.valid) return blocked(capabilitiesResult.code);
671
+ const safeCapabilities = capabilitiesResult.value;
672
+ if (
673
+ !Array.isArray(safeContext.coreCapsuleVersions) ||
674
+ safeContext.coreCapsuleVersions.some((value) => typeof value !== 'string')
675
+ ) {
676
+ return blocked('KDNA_VALIDATION_CONTEXT_INVALID');
677
+ }
678
+
679
+ const hasCapsule =
680
+ safePlan.projection_request.accepted_capsule_versions.includes(CAPSULE_VERSION) &&
681
+ safeContext.coreCapsuleVersions.includes(CAPSULE_VERSION) &&
682
+ safeCapabilities.capsule_versions.includes(CAPSULE_VERSION);
683
+ if (!hasCapsule) return blocked('KDNA_CAPSULE_VERSION_UNSUPPORTED');
684
+
685
+ const hasProtocol =
686
+ safePlan.host_request.accepted_protocols.includes(HOST_PROTOCOL) &&
687
+ safeCapabilities.host_protocols.includes(HOST_PROTOCOL);
688
+ if (!hasProtocol) return blocked('KDNA_HOST_PROTOCOL_UNSUPPORTED');
689
+
690
+ const hasVerifiablePair =
691
+ safeCapabilities.capability_basis === 'registered_descriptor' &&
692
+ safeCapabilities.capsule_digest_profiles.includes(CAPSULE_DELIVERY_PROFILE);
693
+ if (!hasVerifiablePair) return blocked('KDNA_HOST_CAPSULE_PAIR_UNSUPPORTED');
694
+
695
+ return {
696
+ state: 'selected',
697
+ capsule_version: CAPSULE_VERSION,
698
+ host_protocol: HOST_PROTOCOL,
699
+ issue_code: null,
700
+ };
701
+ }
702
+
703
+ function expectedAgainst(source) {
704
+ if (source === 'kdna.json.content_digest') return 'manifest_declaration';
705
+ if (source === 'checksums.json.entry_set_digest' || source === 'checksums.json.asset_digest') {
706
+ return 'checksum_declaration';
707
+ }
708
+ return 'external_expected';
709
+ }
710
+
711
+ function validateExpectedDigests(expectedDigests, capsuleDigests) {
712
+ for (const name of ['asset', 'content', 'runtime_entry_set']) {
713
+ const expected = expectedDigests[name];
714
+ if (expected === null) continue;
715
+ const observed = capsuleDigests[name];
716
+ if (observed.value !== expected.value || observed.basis !== expected.basis) {
717
+ return 'KDNA_HOST_DIGEST_EXPECTATION_MISMATCH';
718
+ }
719
+ if (observed.comparison.state !== expected.comparison) {
720
+ return 'KDNA_HOST_DIGEST_EXPECTATION_MISMATCH';
721
+ }
722
+ if (expected.comparison === 'matched') {
723
+ if (
724
+ observed.comparison.expected !== expected.value ||
725
+ observed.comparison.source !== expected.source ||
726
+ observed.comparison.against !== expectedAgainst(expected.source)
727
+ ) {
728
+ return 'KDNA_HOST_DIGEST_EXPECTATION_MISMATCH';
729
+ }
730
+ } else if (
731
+ observed.comparison.expected !== null ||
732
+ observed.comparison.source !== null ||
733
+ observed.comparison.against !== null
734
+ ) {
735
+ return 'KDNA_HOST_DIGEST_EXPECTATION_MISMATCH';
736
+ }
737
+ }
738
+ return null;
739
+ }
740
+
741
+ function validateRequestSemantics(request, plan, enforceBudget = true) {
742
+ if (
743
+ request.plan_ref.plan_id !== plan.plan_id ||
744
+ request.plan_ref.plan_digest_profile !== plan.integrity.profile ||
745
+ request.plan_ref.plan_digest !== plan.integrity.plan_digest ||
746
+ computeConsumptionPlanDigestV1(plan) !== plan.integrity.plan_digest
747
+ ) {
748
+ return 'KDNA_HOST_PLAN_REF_MISMATCH';
749
+ }
750
+ if (!equalJson(request.task, plan.task)) return 'KDNA_HOST_TASK_MISMATCH';
751
+ if (request.authority.asset_id !== plan.asset_ref.asset_id) {
752
+ return 'KDNA_HOST_ASSET_ID_MISMATCH';
753
+ }
754
+
755
+ const expectedRequestAsset = { ...plan.asset_ref, role: 'primary' };
756
+ if (!equalJson(request.asset, expectedRequestAsset)) return 'KDNA_HOST_ASSET_REF_MISMATCH';
757
+
758
+ const capsuleAsset = request.capsule.asset;
759
+ for (const field of ['asset_id', 'asset_uid', 'version', 'judgment_version']) {
760
+ if (capsuleAsset[field] !== plan.asset_ref[field]) return 'KDNA_HOST_ASSET_REF_MISMATCH';
761
+ }
762
+ if (request.capsule.access !== plan.asset_ref.access) return 'KDNA_HOST_ASSET_REF_MISMATCH';
763
+
764
+ if (
765
+ request.runtime_contract.capsule_version !== request.capsule.version ||
766
+ request.capsule.version !== CAPSULE_VERSION
767
+ ) {
768
+ return 'KDNA_HOST_CAPSULE_VERSION_MISMATCH';
769
+ }
770
+ if (
771
+ request.runtime_contract.capsule_digest_profile !== CAPSULE_DELIVERY_PROFILE ||
772
+ request.capsule.digests.profile !== plan.projection_request.required_digest_profile
773
+ ) {
774
+ return 'KDNA_HOST_PROJECTION_CONTRACT_MISMATCH';
775
+ }
776
+
777
+ const expectedProjectionContract = {
778
+ profile: plan.projection_request.profile,
779
+ required_digest_profile: plan.projection_request.required_digest_profile,
780
+ require_packaged_asset: plan.projection_request.require_packaged_asset,
781
+ };
782
+ if (
783
+ !equalJson(request.projection_contract, expectedProjectionContract) ||
784
+ request.capsule.profile !== request.projection_contract.profile
785
+ ) {
786
+ return 'KDNA_HOST_PROJECTION_CONTRACT_MISMATCH';
787
+ }
788
+ if (!equalJson(request.result_contract, plan.result_request)) {
789
+ return 'KDNA_HOST_RESULT_CONTRACT_MISMATCH';
790
+ }
791
+ if (!equalJson(request.budget, plan.budget)) return 'KDNA_HOST_BUDGET_MISMATCH';
792
+ if (!equalJson(request.constraints, plan.constraints)) return 'KDNA_HOST_CONSTRAINTS_MISMATCH';
793
+ if (
794
+ enforceBudget &&
795
+ (jcsCharacterCount(request.capsule) > request.budget.max_projection_chars ||
796
+ jcsCharacterCount(request.task) > request.budget.max_task_chars)
797
+ ) {
798
+ return 'KDNA_HOST_BUDGET_LIMIT_EXCEEDED';
799
+ }
800
+
801
+ const digestError = validateExpectedDigests(
802
+ plan.asset_ref.expected_digests,
803
+ request.capsule.digests,
804
+ );
805
+ if (digestError) return digestError;
806
+
807
+ if (
808
+ computeCapsuleDeliveryDigest(request.capsule) !==
809
+ request.runtime_contract.capsule_delivery_digest
810
+ ) {
811
+ return 'KDNA_CAPSULE_DELIVERY_DIGEST_MISMATCH';
812
+ }
813
+ return null;
814
+ }
815
+
816
+ function validateAgentHost2RequestV1(request, context) {
817
+ const contextResult = snapshotContext(context, [
818
+ 'plan',
819
+ 'trustedPlanDigest',
820
+ 'capabilities',
821
+ 'coreCapsuleVersions',
822
+ ]);
823
+ if (!contextResult.valid) return contextResult;
824
+ const safeContext = contextResult.value;
825
+ const schemaResult = validateSchema('request', request);
826
+ if (!schemaResult.valid) return schemaResult;
827
+ const safeRequest = schemaResult.value;
828
+ const planResult = validateConsumptionPlanV1(safeContext.plan, {
829
+ trustedPlanDigest: safeContext.trustedPlanDigest,
830
+ });
831
+ if (!planResult.valid) return planResult;
832
+ const safePlan = planResult.value;
833
+ const negotiation = negotiateExecutionPairV1(safePlan, safeContext);
834
+ if (negotiation.state !== 'selected') {
835
+ return invalid(negotiation.issue_code, 'No strict ConsumptionPlan 1 / Host 2 pair was selected.');
836
+ }
837
+ try {
838
+ const code = validateRequestSemantics(safeRequest, safePlan, true);
839
+ return code ? invalid(code, 'Agent Host 2 request correlation failed.') : ok(safeRequest);
840
+ } catch (error) {
841
+ return invalid('KDNA_INPUT_INVALID', 'Agent Host 2 request is not a safe JSON value.', {
842
+ cause: error && typeof error.message === 'string' ? error.message : String(error),
843
+ });
844
+ }
845
+ }
846
+
847
+ function assembleAgentHost2RequestV1(input, plan) {
848
+ return {
849
+ protocol: HOST_PROTOCOL,
850
+ request_id: input.request_id,
851
+ plan_ref: {
852
+ plan_id: plan.plan_id,
853
+ plan_digest_profile: plan.integrity.profile,
854
+ plan_digest: plan.integrity.plan_digest,
855
+ },
856
+ runtime_contract: {
857
+ capsule_version: CAPSULE_VERSION,
858
+ capsule_digest_profile: CAPSULE_DELIVERY_PROFILE,
859
+ capsule_delivery_digest: computeCapsuleDeliveryDigest(input.capsule),
860
+ },
861
+ projection_contract: {
862
+ profile: plan.projection_request.profile,
863
+ required_digest_profile: plan.projection_request.required_digest_profile,
864
+ require_packaged_asset: plan.projection_request.require_packaged_asset,
865
+ },
866
+ result_contract: clone(plan.result_request),
867
+ budget: clone(plan.budget),
868
+ constraints: clone(plan.constraints),
869
+ phase: 'single_judgment',
870
+ task: clone(plan.task),
871
+ authority: { asset_id: plan.asset_ref.asset_id, role: 'primary', final_decision: true },
872
+ asset: { ...clone(plan.asset_ref), role: 'primary' },
873
+ capsule: clone(input.capsule),
874
+ };
875
+ }
876
+
877
+ function buildAgentHost2RequestV1(input, context) {
878
+ const contextResult = snapshotContext(context, [
879
+ 'plan',
880
+ 'trustedPlanDigest',
881
+ 'capabilities',
882
+ 'coreCapsuleVersions',
883
+ ]);
884
+ throwInvalid(contextResult);
885
+ const inputResult = snapshotPublicInput(input, ['request_id', 'capsule']);
886
+ throwInvalid(inputResult);
887
+ const safeContext = contextResult.value;
888
+ const planResult = validateConsumptionPlanV1(safeContext.plan, {
889
+ trustedPlanDigest: safeContext.trustedPlanDigest,
890
+ });
891
+ throwInvalid(planResult);
892
+ const plan = planResult.value;
893
+ const negotiation = negotiateExecutionPairV1(plan, safeContext);
894
+ if (negotiation.state !== 'selected') {
895
+ throw new KDNAExecutionContractError(
896
+ negotiation.issue_code,
897
+ 'Cannot build Host 2 request without a selected strict execution pair.',
898
+ );
899
+ }
900
+ let request;
901
+ try {
902
+ request = assembleAgentHost2RequestV1(inputResult.value, plan);
903
+ } catch {
904
+ throw new KDNAExecutionContractError(
905
+ 'KDNA_HOST_REQUEST_INPUT_INVALID',
906
+ 'Cannot build Agent Host 2 request from the supplied input.',
907
+ );
908
+ }
909
+ const result = validateAgentHost2RequestV1(request, { ...safeContext, plan });
910
+ throwInvalid(result);
911
+ return request;
912
+ }
913
+
914
+ function validateReceiptSemantics(receipt, request) {
915
+ if (receipt.protocol !== request.protocol || receipt.request_id !== request.request_id) {
916
+ return 'KDNA_HOST_REQUEST_ID_MISMATCH';
917
+ }
918
+ const runtimeReceipt = receipt.runtime_receipt;
919
+ const senderP = request.runtime_contract.capsule_delivery_digest;
920
+ const recomputedP = computeCapsuleDeliveryDigest(request.capsule);
921
+ if (
922
+ runtimeReceipt.sender_capsule_delivery_digest !== senderP ||
923
+ runtimeReceipt.echoed_capsule_delivery_digest !==
924
+ runtimeReceipt.host_recomputed_capsule_delivery_digest
925
+ ) {
926
+ return 'KDNA_HOST_CAPSULE_DELIVERY_DIGEST_MISMATCH';
927
+ }
928
+
929
+ if (runtimeReceipt.capsule_delivery_comparison === 'mismatched') {
930
+ if (
931
+ runtimeReceipt.host_recomputed_capsule_delivery_digest === senderP ||
932
+ runtimeReceipt.provider_execution_status !== 'not_started' ||
933
+ receipt.outcome !== null
934
+ ) {
935
+ return 'KDNA_HOST_CAPSULE_DELIVERY_DIGEST_MISMATCH';
936
+ }
937
+ } else if (
938
+ runtimeReceipt.capsule_delivery_comparison !== 'matched' ||
939
+ runtimeReceipt.provider_execution_status === 'not_started' ||
940
+ senderP !== recomputedP ||
941
+ runtimeReceipt.host_recomputed_capsule_delivery_digest !== senderP ||
942
+ runtimeReceipt.host_recomputed_capsule_delivery_digest !== recomputedP
943
+ ) {
944
+ return 'KDNA_HOST_CAPSULE_DELIVERY_DIGEST_MISMATCH';
945
+ }
946
+ if (
947
+ runtimeReceipt.capsule_version !== request.runtime_contract.capsule_version ||
948
+ runtimeReceipt.capsule_digest_profile !== request.runtime_contract.capsule_digest_profile
949
+ ) {
950
+ return 'KDNA_HOST_CAPSULE_VERSION_MISMATCH';
951
+ }
952
+
953
+ const outcomeUsage = receipt.outcome?.usage ?? null;
954
+ const observed = runtimeReceipt.usage;
955
+ if (receipt.outcome === null) return null;
956
+ if (outcomeUsage === null) {
957
+ if (
958
+ observed.basis !== 'not_observed' ||
959
+ observed.tokens_used !== null ||
960
+ observed.model_calls !== null
961
+ ) {
962
+ return 'KDNA_HOST_USAGE_MISMATCH';
963
+ }
964
+ } else if (
965
+ observed.basis !== 'host_reported' ||
966
+ observed.tokens_used !== outcomeUsage.tokens_used ||
967
+ observed.model_calls !== outcomeUsage.model_calls
968
+ ) {
969
+ return 'KDNA_HOST_USAGE_MISMATCH';
970
+ }
971
+ return null;
972
+ }
973
+
974
+ function validateAgentHost2ReceiptV1(receipt, context) {
975
+ const contextResult = snapshotContext(context, ['request']);
976
+ if (!contextResult.valid) return contextResult;
977
+ const safeContext = contextResult.value;
978
+ const schemaResult = validateSchema('receipt', receipt);
979
+ if (!schemaResult.valid) return schemaResult;
980
+ const safeReceipt = schemaResult.value;
981
+ const requestSchemaResult = validateSchema('request', safeContext.request);
982
+ if (!requestSchemaResult.valid) {
983
+ return invalid(
984
+ 'KDNA_VALIDATION_CONTEXT_INVALID',
985
+ 'Receipt validation requires a schema-valid independent Host 2 request.',
986
+ requestSchemaResult.errors,
987
+ );
988
+ }
989
+ const safeRequest = requestSchemaResult.value;
990
+ try {
991
+ const code = validateReceiptSemantics(safeReceipt, safeRequest);
992
+ return code ? invalid(code, 'Agent Host 2 receipt correlation failed.') : ok(safeReceipt);
993
+ } catch (error) {
994
+ return invalid('KDNA_INPUT_INVALID', 'Agent Host 2 receipt is not a safe JSON value.', {
995
+ cause: error && typeof error.message === 'string' ? error.message : String(error),
996
+ });
997
+ }
998
+ }
999
+
1000
+ function compareObserved(value, limit, unobserved = 'not_observed') {
1001
+ if (limit === null) return 'not_limited';
1002
+ if (value === null) return unobserved;
1003
+ return value <= limit ? 'within_limit' : 'exceeded';
1004
+ }
1005
+
1006
+ function expectedBudgetEvidence(plan, request, receipt) {
1007
+ const projectionChars = request === null ? null : jcsCharacterCount(request.capsule);
1008
+ const taskChars = jcsCharacterCount(plan.task);
1009
+ const usage = receipt?.runtime_receipt.usage ?? {
1010
+ elapsed_ms: null,
1011
+ elapsed_basis: 'not_observed',
1012
+ tokens_used: null,
1013
+ model_calls: null,
1014
+ basis: 'not_observed',
1015
+ };
1016
+ const actual = {
1017
+ projection_chars: projectionChars,
1018
+ task_chars: taskChars,
1019
+ elapsed_ms: usage.elapsed_ms,
1020
+ elapsed_basis: usage.elapsed_basis,
1021
+ tokens_used: usage.tokens_used,
1022
+ model_calls: usage.model_calls,
1023
+ usage_basis: usage.basis,
1024
+ };
1025
+ const comparison = {
1026
+ projection_chars: compareObserved(projectionChars, plan.budget.max_projection_chars),
1027
+ task_chars: compareObserved(taskChars, plan.budget.max_task_chars),
1028
+ elapsed_ms: compareObserved(actual.elapsed_ms, plan.budget.deadline_ms),
1029
+ tokens_used: compareObserved(usage.tokens_used, plan.budget.max_tokens),
1030
+ model_calls: compareObserved(usage.model_calls, plan.budget.max_model_calls),
1031
+ };
1032
+ const dimensionStates = Object.values(comparison);
1033
+ comparison.overall = dimensionStates.includes('exceeded')
1034
+ ? 'exceeded'
1035
+ : dimensionStates.includes('not_observed')
1036
+ ? 'not_observed'
1037
+ : 'within_limit';
1038
+ return { limits: clone(plan.budget), actual, comparison };
1039
+ }
1040
+
1041
+ function deriveBudgetEvidenceV1(plan, context) {
1042
+ const contextResult = snapshotContext(context, ['trustedPlanDigest', 'request', 'receipt']);
1043
+ throwInvalid(contextResult);
1044
+ const safeContext = contextResult.value;
1045
+ const planResult = validateConsumptionPlanV1(plan, {
1046
+ trustedPlanDigest: safeContext.trustedPlanDigest,
1047
+ });
1048
+ throwInvalid(planResult);
1049
+ const safePlan = planResult.value;
1050
+ if (safeContext.request !== null) {
1051
+ const requestResult = validateSchema('request', safeContext.request);
1052
+ if (!requestResult.valid) {
1053
+ throw new KDNAExecutionContractError(
1054
+ 'KDNA_VALIDATION_CONTEXT_INVALID',
1055
+ 'Budget derivation requires a schema-valid independent Host 2 request.',
1056
+ requestResult.errors,
1057
+ );
1058
+ }
1059
+ safeContext.request = requestResult.value;
1060
+ const requestCode = validateRequestSemantics(safeContext.request, safePlan, false);
1061
+ if (requestCode !== null) {
1062
+ throw new KDNAExecutionContractError(
1063
+ requestCode,
1064
+ 'Budget derivation requires an independently correlated Host 2 request.',
1065
+ );
1066
+ }
1067
+ }
1068
+ if (safeContext.receipt !== null) {
1069
+ if (safeContext.request === null) {
1070
+ throw new KDNAExecutionContractError(
1071
+ 'KDNA_VALIDATION_CONTEXT_INVALID',
1072
+ 'Budget derivation cannot use a receipt without its independent request.',
1073
+ );
1074
+ }
1075
+ const receiptResult = validateAgentHost2ReceiptV1(safeContext.receipt, {
1076
+ request: safeContext.request,
1077
+ });
1078
+ throwInvalid(receiptResult);
1079
+ safeContext.receipt = receiptResult.value;
1080
+ }
1081
+ return expectedBudgetEvidence(safePlan, safeContext.request, safeContext.receipt);
1082
+ }
1083
+
1084
+ function preparePreHostBudgetBlockedV1(capsule, safeContext) {
1085
+ const planResult = validateConsumptionPlanV1(safeContext.plan, {
1086
+ trustedPlanDigest: safeContext.trustedPlanDigest,
1087
+ });
1088
+ if (!planResult.valid) return planResult;
1089
+ const plan = planResult.value;
1090
+ const negotiation = negotiateExecutionPairV1(plan, safeContext);
1091
+ if (negotiation.state !== 'selected') {
1092
+ return invalid(
1093
+ negotiation.issue_code,
1094
+ 'Pre-Host budget evidence requires a selected strict execution pair.',
1095
+ );
1096
+ }
1097
+
1098
+ let projectedCandidate;
1099
+ try {
1100
+ projectedCandidate = assembleAgentHost2RequestV1(
1101
+ { request_id: PRE_HOST_INTERNAL_REQUEST_ID, capsule },
1102
+ plan,
1103
+ );
1104
+ } catch (error) {
1105
+ return invalid('KDNA_HOST_REQUEST_INPUT_INVALID', 'Projected Capsule input is invalid.', {
1106
+ cause: error && typeof error.message === 'string' ? error.message : String(error),
1107
+ });
1108
+ }
1109
+ const schemaResult = validateSchema('request', projectedCandidate);
1110
+ if (!schemaResult.valid) return schemaResult;
1111
+ projectedCandidate = schemaResult.value;
1112
+ const requestCode = validateRequestSemantics(projectedCandidate, plan, false);
1113
+ if (requestCode !== null) {
1114
+ return invalid(
1115
+ requestCode,
1116
+ 'Projected Capsule does not correlate with the independently validated Plan.',
1117
+ );
1118
+ }
1119
+ const budget = expectedBudgetEvidence(plan, projectedCandidate, null);
1120
+ if (
1121
+ budget.comparison.projection_chars !== 'exceeded' &&
1122
+ budget.comparison.task_chars !== 'exceeded'
1123
+ ) {
1124
+ return invalid(
1125
+ 'KDNA_PRE_HOST_BUDGET_NOT_EXCEEDED',
1126
+ 'Pre-Host blocked Trace construction requires an exceeded projection or task limit.',
1127
+ );
1128
+ }
1129
+ return ok({ plan, projectedCandidate, budget });
1130
+ }
1131
+
1132
+ /**
1133
+ * Build the only supported Trace 1 path for a successfully projected Capsule
1134
+ * that exceeds an enforce-before-Host projection or task limit. This function
1135
+ * never exposes the internal request-shaped correlation candidate and cannot
1136
+ * produce a deliverable Agent Host 2 request.
1137
+ */
1138
+ function buildPreHostBudgetBlockedTraceV1(input, context) {
1139
+ const inputResult = snapshotPublicInput(
1140
+ input,
1141
+ ['trace_id', 'timestamp', 'capsule'],
1142
+ ['parent_trace_id', 'warnings'],
1143
+ );
1144
+ throwInvalid(inputResult);
1145
+ const contextResult = snapshotContext(context, [
1146
+ 'plan',
1147
+ 'trustedPlanDigest',
1148
+ 'capabilities',
1149
+ 'coreCapsuleVersions',
1150
+ ]);
1151
+ throwInvalid(contextResult);
1152
+ const safeInput = inputResult.value;
1153
+ const safeContext = contextResult.value;
1154
+ const prepared = preparePreHostBudgetBlockedV1(safeInput.capsule, safeContext);
1155
+ throwInvalid(prepared);
1156
+
1157
+ return buildJudgmentTraceV1(
1158
+ {
1159
+ trace_id: safeInput.trace_id,
1160
+ timestamp: safeInput.timestamp,
1161
+ parent_trace_id: safeInput.parent_trace_id ?? null,
1162
+ overall_status: 'blocked',
1163
+ errors: [clone(PRE_HOST_BUDGET_ERROR)],
1164
+ warnings: safeInput.warnings || [],
1165
+ },
1166
+ {
1167
+ ...safeContext,
1168
+ plan: prepared.value.plan,
1169
+ request: prepared.value.projectedCandidate,
1170
+ receipt: null,
1171
+ trustedDeliveryObservation: 'not_delivered',
1172
+ },
1173
+ );
1174
+ }
1175
+
1176
+ /**
1177
+ * Validate a pre-Host budget-blocked Trace against the original Capsule and
1178
+ * independent Plan/Host capability facts without requiring callers to
1179
+ * construct or retain an over-budget request object.
1180
+ */
1181
+ function validatePreHostBudgetBlockedTraceV1(trace, context) {
1182
+ const contextResult = snapshotContext(context, [
1183
+ 'plan',
1184
+ 'trustedPlanDigest',
1185
+ 'capabilities',
1186
+ 'coreCapsuleVersions',
1187
+ 'capsule',
1188
+ ]);
1189
+ if (!contextResult.valid) return contextResult;
1190
+ const safeContext = contextResult.value;
1191
+ const prepared = preparePreHostBudgetBlockedV1(safeContext.capsule, safeContext);
1192
+ if (!prepared.valid) return prepared;
1193
+ const traceResult = validateJudgmentTraceV1(trace, {
1194
+ ...safeContext,
1195
+ plan: prepared.value.plan,
1196
+ request: prepared.value.projectedCandidate,
1197
+ receipt: null,
1198
+ trustedDeliveryObservation: 'not_delivered',
1199
+ });
1200
+ if (!traceResult.valid) return traceResult;
1201
+ const safeTrace = traceResult.value;
1202
+ if (
1203
+ safeTrace.overall_status !== 'blocked' ||
1204
+ safeTrace.host_receipt !== null ||
1205
+ safeTrace.execution.delivery_status !== 'not_delivered' ||
1206
+ safeTrace.execution.execution_status !== 'not_started' ||
1207
+ !equalJson(safeTrace.execution.semantic_consumption, {
1208
+ state: 'not_observed',
1209
+ basis: null,
1210
+ }) ||
1211
+ !equalJson(safeTrace.execution.model_identity, {
1212
+ value: null,
1213
+ basis: 'not_observed',
1214
+ }) ||
1215
+ safeTrace.budget.comparison.overall !== 'exceeded' ||
1216
+ (safeTrace.budget.comparison.projection_chars !== 'exceeded' &&
1217
+ safeTrace.budget.comparison.task_chars !== 'exceeded') ||
1218
+ !equalJson(safeTrace.errors, [PRE_HOST_BUDGET_ERROR])
1219
+ ) {
1220
+ return invalid(
1221
+ 'KDNA_TRACE_PRE_HOST_BUDGET_MISMATCH',
1222
+ 'Trace is not an authentic pre-Host budget-blocked terminal.',
1223
+ );
1224
+ }
1225
+ return ok(safeTrace);
1226
+ }
1227
+
1228
+ function withoutExpectedDigests(asset) {
1229
+ const value = clone(asset);
1230
+ delete value.expected_digests;
1231
+ delete value.role;
1232
+ return value;
1233
+ }
1234
+
1235
+ function unavailableDigestEvidence() {
1236
+ const item = (basis) => ({
1237
+ value: null,
1238
+ basis,
1239
+ comparison: { state: 'unavailable', against: null, expected: null, source: null },
1240
+ });
1241
+ return {
1242
+ profile: CAPSULE_DIGEST_PROFILE,
1243
+ asset: item('kdna-container-bytes-v1'),
1244
+ content: item('kdna-content-tree-v1'),
1245
+ runtime_entry_set: item('kdna-runtime-entry-set-v1'),
1246
+ };
1247
+ }
1248
+
1249
+ function validateTerminalState(trace, receipt) {
1250
+ const table = {
1251
+ execution_completed: ['completed', 'completed', true, 0],
1252
+ execution_failed: ['failed', 'failed', false, 1],
1253
+ cancelled: ['cancelled', 'cancelled', false, 1],
1254
+ timed_out: ['timed_out', 'timed_out', false, 1],
1255
+ };
1256
+
1257
+ if (trace.overall_status === 'blocked') {
1258
+ if (
1259
+ trace.result_ref !== null ||
1260
+ trace.execution.execution_status !== 'not_started' ||
1261
+ trace.errors.length < 1
1262
+ ) {
1263
+ return 'KDNA_TRACE_TERMINAL_STATE_MISMATCH';
1264
+ }
1265
+ return null;
1266
+ }
1267
+
1268
+ const expected = table[trace.overall_status];
1269
+ if (!expected || receipt === null) return 'KDNA_TRACE_TERMINAL_STATE_MISMATCH';
1270
+ const [providerStatus, executionStatus, hasResult, minimumErrors] = expected;
1271
+ if (
1272
+ receipt.runtime_receipt.provider_execution_status !== providerStatus ||
1273
+ trace.execution.execution_status !== executionStatus ||
1274
+ trace.execution.delivery_status !== 'correlated_response' ||
1275
+ (trace.result_ref !== null) !== hasResult ||
1276
+ trace.errors.length < minimumErrors
1277
+ ) {
1278
+ return 'KDNA_TRACE_TERMINAL_STATE_MISMATCH';
1279
+ }
1280
+ if (trace.overall_status === 'execution_completed' && trace.errors.length !== 0) {
1281
+ return 'KDNA_TRACE_TERMINAL_STATE_MISMATCH';
1282
+ }
1283
+ return null;
1284
+ }
1285
+
1286
+ function validateTrustedDeliveryObservation(context) {
1287
+ const observation = context.trustedDeliveryObservation;
1288
+ if (!['host_receipt', 'not_delivered', 'not_observed'].includes(observation)) {
1289
+ return invalid(
1290
+ 'KDNA_VALIDATION_CONTEXT_INVALID',
1291
+ 'trustedDeliveryObservation must be host_receipt, not_delivered, or not_observed.',
1292
+ );
1293
+ }
1294
+ if (context.receipt !== null && observation !== 'host_receipt') {
1295
+ return invalid(
1296
+ 'KDNA_VALIDATION_CONTEXT_INVALID',
1297
+ 'A correlated Host receipt requires trustedDeliveryObservation=host_receipt.',
1298
+ );
1299
+ }
1300
+ if (context.receipt === null && observation === 'host_receipt') {
1301
+ return invalid(
1302
+ 'KDNA_VALIDATION_CONTEXT_INVALID',
1303
+ 'trustedDeliveryObservation=host_receipt requires an independent receipt.',
1304
+ );
1305
+ }
1306
+ if (context.request === null && observation !== 'not_delivered') {
1307
+ return invalid(
1308
+ 'KDNA_VALIDATION_CONTEXT_INVALID',
1309
+ 'Without a Host request the trusted delivery observation must be not_delivered.',
1310
+ );
1311
+ }
1312
+ return ok(observation);
1313
+ }
1314
+
1315
+ function validateTraceSemantics(
1316
+ trace,
1317
+ plan,
1318
+ request,
1319
+ receipt,
1320
+ capabilities,
1321
+ coreCapsuleVersions,
1322
+ trustedDeliveryObservation,
1323
+ ) {
1324
+ if (
1325
+ trace.plan_ref.plan_id !== plan.plan_id ||
1326
+ trace.plan_ref.plan_digest_profile !== plan.integrity.profile ||
1327
+ trace.plan_ref.plan_digest !== plan.integrity.plan_digest ||
1328
+ trace.plan_ref.plan_digest !== computeConsumptionPlanDigestV1(plan)
1329
+ ) {
1330
+ return 'KDNA_TRACE_PLAN_REF_MISMATCH';
1331
+ }
1332
+ const runtime = trace.runtime_contract;
1333
+ if (
1334
+ !equalJson(runtime.plan_capsule_versions, plan.projection_request.accepted_capsule_versions) ||
1335
+ !equalJson(runtime.plan_host_protocols, plan.host_request.accepted_protocols) ||
1336
+ !equalJson(runtime.core_capsule_versions, coreCapsuleVersions) ||
1337
+ !equalJson(runtime.host_capabilities, capabilities)
1338
+ ) {
1339
+ return 'KDNA_TRACE_NEGOTIATION_EVIDENCE_MISMATCH';
1340
+ }
1341
+
1342
+ if (!equalJson(trace.asset_identity, withoutExpectedDigests(plan.asset_ref))) {
1343
+ return 'KDNA_TRACE_ASSET_IDENTITY_MISMATCH';
1344
+ }
1345
+ if (!equalJson(trace.budget.limits, plan.budget)) return 'KDNA_TRACE_BUDGET_MISMATCH';
1346
+
1347
+ if (trace.overall_status === 'blocked') {
1348
+ if (!equalJson(trace.host_receipt, receipt)) return 'KDNA_TRACE_TERMINAL_STATE_MISMATCH';
1349
+ if (request !== null) {
1350
+ if (validateRequestSemantics(request, plan, false) !== null) {
1351
+ return 'KDNA_TRACE_REQUEST_MISMATCH';
1352
+ }
1353
+ const negotiation = negotiateExecutionPairV1(plan, {
1354
+ trustedPlanDigest: plan.integrity.plan_digest,
1355
+ capabilities,
1356
+ coreCapsuleVersions,
1357
+ });
1358
+ if (
1359
+ negotiation.state !== 'selected' ||
1360
+ runtime.negotiation_state !== 'selected' ||
1361
+ runtime.selected_capsule_version !== negotiation.capsule_version ||
1362
+ runtime.selected_host_protocol !== negotiation.host_protocol ||
1363
+ runtime.issue_code !== null
1364
+ ) {
1365
+ return 'KDNA_TRACE_NEGOTIATION_EVIDENCE_MISMATCH';
1366
+ }
1367
+ if (
1368
+ !equalJson(trace.asset_identity, withoutExpectedDigests(request.asset)) ||
1369
+ !equalJson(trace.digest_evidence, request.capsule.digests)
1370
+ ) {
1371
+ return 'KDNA_TRACE_DIGEST_EVIDENCE_MISMATCH';
1372
+ }
1373
+ const p = request.runtime_contract.capsule_delivery_digest;
1374
+ const delivery = trace.capsule_delivery_evidence;
1375
+ const projectionMismatch =
1376
+ trace.projection_actual.profile !== request.projection_contract.profile ||
1377
+ trace.projection_actual.capsule_delivery_digest !== p ||
1378
+ trace.projection_actual.profile_deviated_from_plan !== false;
1379
+ if (delivery.observed !== p || delivery.sender_computed !== true || projectionMismatch) {
1380
+ return 'KDNA_TRACE_UNDELIVERED_HOST_EVIDENCE';
1381
+ }
1382
+
1383
+ if (delivery.host_boundary_comparison === 'mismatched') {
1384
+ if (
1385
+ trustedDeliveryObservation !== 'host_receipt' ||
1386
+ receipt === null ||
1387
+ validateReceiptSemantics(receipt, request) !== null ||
1388
+ receipt.runtime_receipt.capsule_delivery_comparison !== 'mismatched' ||
1389
+ delivery.host_recomputed !==
1390
+ receipt.runtime_receipt.host_recomputed_capsule_delivery_digest ||
1391
+ delivery.host_echoed !== receipt.runtime_receipt.echoed_capsule_delivery_digest ||
1392
+ delivery.host_recomputed === p ||
1393
+ delivery.delivered_capsule_version !== CAPSULE_VERSION ||
1394
+ delivery.request_id !== request.request_id ||
1395
+ trace.execution.delivery_status !== 'rejected_before_execution'
1396
+ ) {
1397
+ return 'KDNA_TRACE_CAPSULE_DELIVERY_DIGEST_MISMATCH';
1398
+ }
1399
+ } else if (delivery.host_boundary_comparison === 'not_observed') {
1400
+ if (
1401
+ trustedDeliveryObservation !== 'not_observed' ||
1402
+ receipt !== null ||
1403
+ delivery.host_recomputed !== null ||
1404
+ delivery.host_echoed !== null ||
1405
+ delivery.delivered_capsule_version !== CAPSULE_VERSION ||
1406
+ delivery.request_id !== request.request_id ||
1407
+ trace.execution.delivery_status !== 'rejected_before_execution'
1408
+ ) {
1409
+ return 'KDNA_TRACE_UNDELIVERED_HOST_EVIDENCE';
1410
+ }
1411
+ } else {
1412
+ if (trustedDeliveryObservation !== 'not_delivered') {
1413
+ return 'KDNA_TRACE_DELIVERY_OBSERVATION_MISMATCH';
1414
+ }
1415
+ if (
1416
+ receipt !== null ||
1417
+ delivery.host_recomputed !== null ||
1418
+ delivery.host_echoed !== null ||
1419
+ delivery.delivered_capsule_version !== null ||
1420
+ delivery.host_boundary_comparison !== 'not_delivered' ||
1421
+ delivery.request_id !== null ||
1422
+ trace.execution.delivery_status !== 'not_delivered'
1423
+ ) {
1424
+ return 'KDNA_TRACE_UNDELIVERED_HOST_EVIDENCE';
1425
+ }
1426
+ }
1427
+
1428
+ const expectedBudget = expectedBudgetEvidence(plan, request, receipt);
1429
+ if (!equalJson(trace.budget, expectedBudget)) return 'KDNA_TRACE_BUDGET_MISMATCH';
1430
+ return validateTerminalState(trace, null);
1431
+ }
1432
+
1433
+ if (
1434
+ trustedDeliveryObservation !== 'not_delivered' ||
1435
+ runtime.selected_capsule_version !== null ||
1436
+ runtime.selected_host_protocol !== null ||
1437
+ !['not_started', 'blocked'].includes(runtime.negotiation_state) ||
1438
+ trace.capsule_delivery_evidence.host_recomputed !== null ||
1439
+ trace.capsule_delivery_evidence.host_echoed !== null ||
1440
+ trace.capsule_delivery_evidence.host_boundary_comparison !== 'unavailable' ||
1441
+ trace.projection_actual.profile !== null ||
1442
+ trace.projection_actual.capsule_delivery_digest !== null ||
1443
+ trace.projection_actual.profile_deviated_from_plan !== null
1444
+ ) {
1445
+ return 'KDNA_TRACE_UNDELIVERED_HOST_EVIDENCE';
1446
+ }
1447
+ for (const name of ['asset', 'content', 'runtime_entry_set']) {
1448
+ if (
1449
+ trace.digest_evidence[name].value !== null ||
1450
+ trace.digest_evidence[name].comparison.state !== 'unavailable'
1451
+ ) {
1452
+ return 'KDNA_TRACE_UNDELIVERED_HOST_EVIDENCE';
1453
+ }
1454
+ }
1455
+ if (runtime.negotiation_state === 'not_started' && runtime.issue_code !== null) {
1456
+ return 'KDNA_TRACE_NEGOTIATION_EVIDENCE_MISMATCH';
1457
+ }
1458
+ if (runtime.negotiation_state === 'blocked') {
1459
+ const negotiation = negotiateExecutionPairV1(plan, {
1460
+ trustedPlanDigest: plan.integrity.plan_digest,
1461
+ capabilities,
1462
+ coreCapsuleVersions,
1463
+ });
1464
+ if (negotiation.state !== 'blocked' || runtime.issue_code !== negotiation.issue_code) {
1465
+ return 'KDNA_TRACE_NEGOTIATION_EVIDENCE_MISMATCH';
1466
+ }
1467
+ }
1468
+ if (receipt !== null || trace.execution.delivery_status !== 'not_delivered') {
1469
+ return 'KDNA_TRACE_UNDELIVERED_HOST_EVIDENCE';
1470
+ }
1471
+ const expectedBudget = expectedBudgetEvidence(plan, null, null);
1472
+ if (!equalJson(trace.budget, expectedBudget)) return 'KDNA_TRACE_BUDGET_MISMATCH';
1473
+ return validateTerminalState(trace, null);
1474
+ }
1475
+
1476
+ if (
1477
+ request === null ||
1478
+ receipt === null ||
1479
+ trustedDeliveryObservation !== 'host_receipt'
1480
+ ) {
1481
+ return 'KDNA_TRACE_TERMINAL_STATE_MISMATCH';
1482
+ }
1483
+
1484
+ const requestError = validateRequestSemantics(request, plan);
1485
+ if (requestError === 'KDNA_HOST_BUDGET_LIMIT_EXCEEDED') {
1486
+ return 'KDNA_TRACE_BUDGET_LIMIT_VIOLATION';
1487
+ }
1488
+ if (requestError !== null) return 'KDNA_TRACE_REQUEST_MISMATCH';
1489
+ if (validateReceiptSemantics(receipt, request) !== null) {
1490
+ return 'KDNA_TRACE_HOST_RECEIPT_MISMATCH';
1491
+ }
1492
+
1493
+ const negotiation = negotiateExecutionPairV1(plan, {
1494
+ trustedPlanDigest: plan.integrity.plan_digest,
1495
+ capabilities,
1496
+ coreCapsuleVersions,
1497
+ });
1498
+ if (
1499
+ negotiation.state !== 'selected' ||
1500
+ runtime.negotiation_state !== negotiation.state ||
1501
+ runtime.selected_capsule_version !== negotiation.capsule_version ||
1502
+ runtime.selected_host_protocol !== negotiation.host_protocol ||
1503
+ runtime.issue_code !== null
1504
+ ) {
1505
+ return 'KDNA_TRACE_NEGOTIATION_EVIDENCE_MISMATCH';
1506
+ }
1507
+ if (!runtime.core_capsule_versions.includes(runtime.selected_capsule_version)) {
1508
+ return 'KDNA_TRACE_NEGOTIATION_EVIDENCE_MISMATCH';
1509
+ }
1510
+ if (!equalJson(trace.asset_identity, withoutExpectedDigests(request.asset))) {
1511
+ return 'KDNA_TRACE_ASSET_IDENTITY_MISMATCH';
1512
+ }
1513
+ if (!equalJson(trace.digest_evidence, request.capsule.digests)) {
1514
+ return 'KDNA_TRACE_DIGEST_EVIDENCE_MISMATCH';
1515
+ }
1516
+
1517
+ const p = request.runtime_contract.capsule_delivery_digest;
1518
+ const delivery = trace.capsule_delivery_evidence;
1519
+ const runtimeReceipt = receipt.runtime_receipt;
1520
+ if (
1521
+ delivery.observed !== p ||
1522
+ delivery.host_boundary_comparison !== 'matched' ||
1523
+ delivery.host_recomputed !== runtimeReceipt.host_recomputed_capsule_delivery_digest ||
1524
+ delivery.host_recomputed !== p ||
1525
+ delivery.host_echoed !== runtimeReceipt.echoed_capsule_delivery_digest ||
1526
+ delivery.host_echoed !== p ||
1527
+ delivery.delivered_capsule_version !== request.runtime_contract.capsule_version ||
1528
+ delivery.request_id !== request.request_id ||
1529
+ trace.projection_actual.profile !== request.projection_contract.profile ||
1530
+ trace.projection_actual.capsule_delivery_digest !== p ||
1531
+ trace.projection_actual.profile_deviated_from_plan !== false
1532
+ ) {
1533
+ return 'KDNA_TRACE_CAPSULE_DELIVERY_DIGEST_MISMATCH';
1534
+ }
1535
+ if (!equalJson(trace.host_receipt, receipt)) return 'KDNA_TRACE_HOST_RECEIPT_MISMATCH';
1536
+ if (
1537
+ !equalJson(trace.execution.semantic_consumption, runtimeReceipt.semantic_consumption) ||
1538
+ !equalJson(trace.execution.model_identity, runtimeReceipt.model_identity)
1539
+ ) {
1540
+ return 'KDNA_TRACE_EXECUTION_EVIDENCE_MISMATCH';
1541
+ }
1542
+
1543
+ const expectedBudget = expectedBudgetEvidence(plan, request, receipt);
1544
+ if (!equalJson(trace.budget, expectedBudget)) return 'KDNA_TRACE_BUDGET_MISMATCH';
1545
+ if (trace.overall_status !== 'timed_out' && expectedBudget.comparison.overall === 'exceeded') {
1546
+ return 'KDNA_TRACE_BUDGET_LIMIT_VIOLATION';
1547
+ }
1548
+ if (trace.overall_status === 'timed_out' && expectedBudget.comparison.elapsed_ms !== 'exceeded') {
1549
+ return 'KDNA_TRACE_BUDGET_MISMATCH';
1550
+ }
1551
+
1552
+ if (trace.result_ref !== null) {
1553
+ if (
1554
+ trace.result_ref.shape !== request.result_contract.shape ||
1555
+ receipt.outcome === null ||
1556
+ trace.result_ref.result_digest !== digestJcs(receipt.outcome)
1557
+ ) {
1558
+ return 'KDNA_TRACE_RESULT_DIGEST_MISMATCH';
1559
+ }
1560
+ } else if (receipt.outcome !== null) {
1561
+ return 'KDNA_TRACE_RESULT_DIGEST_MISMATCH';
1562
+ }
1563
+ return validateTerminalState(trace, receipt);
1564
+ }
1565
+
1566
+ function validateJudgmentTraceV1(trace, context) {
1567
+ const contextResult = snapshotContext(context, [
1568
+ 'plan',
1569
+ 'trustedPlanDigest',
1570
+ 'capabilities',
1571
+ 'coreCapsuleVersions',
1572
+ 'request',
1573
+ 'receipt',
1574
+ 'trustedDeliveryObservation',
1575
+ ]);
1576
+ if (!contextResult.valid) return contextResult;
1577
+ const safeContext = contextResult.value;
1578
+ const observationResult = validateTrustedDeliveryObservation(safeContext);
1579
+ if (!observationResult.valid) return observationResult;
1580
+ const schemaResult = validateSchema('trace', trace);
1581
+ if (!schemaResult.valid) return schemaResult;
1582
+ const safeTrace = schemaResult.value;
1583
+ const planResult = validateConsumptionPlanV1(safeContext.plan, {
1584
+ trustedPlanDigest: safeContext.trustedPlanDigest,
1585
+ });
1586
+ if (!planResult.valid) return planResult;
1587
+ safeContext.plan = planResult.value;
1588
+ const capabilitiesResult = validateSchema('capabilities', safeContext.capabilities);
1589
+ if (!capabilitiesResult.valid) {
1590
+ return invalid(
1591
+ 'KDNA_VALIDATION_CONTEXT_INVALID',
1592
+ 'Trace validation requires schema-valid independent Host capabilities.',
1593
+ capabilitiesResult.errors,
1594
+ );
1595
+ }
1596
+ safeContext.capabilities = capabilitiesResult.value;
1597
+ if (safeContext.request !== null) {
1598
+ const requestSchemaResult = validateSchema('request', safeContext.request);
1599
+ if (!requestSchemaResult.valid) {
1600
+ return invalid(
1601
+ 'KDNA_VALIDATION_CONTEXT_INVALID',
1602
+ 'Trace validation requires a schema-valid independent Host request.',
1603
+ requestSchemaResult.errors,
1604
+ );
1605
+ }
1606
+ safeContext.request = requestSchemaResult.value;
1607
+ }
1608
+ if (safeContext.receipt !== null) {
1609
+ if (safeContext.request === null) {
1610
+ return invalid(
1611
+ 'KDNA_VALIDATION_CONTEXT_INVALID',
1612
+ 'Trace validation cannot correlate a receipt without its independent request.',
1613
+ );
1614
+ }
1615
+ const receiptResult = validateAgentHost2ReceiptV1(safeContext.receipt, {
1616
+ request: safeContext.request,
1617
+ });
1618
+ if (!receiptResult.valid) {
1619
+ if (receiptResult.code === 'KDNA_HOST_CAPSULE_DELIVERY_DIGEST_MISMATCH') {
1620
+ return invalid(
1621
+ 'KDNA_TRACE_CAPSULE_DELIVERY_DIGEST_MISMATCH',
1622
+ 'JudgmentTrace receipt context contains inconsistent Capsule delivery evidence.',
1623
+ receiptResult.errors,
1624
+ );
1625
+ }
1626
+ return invalid(
1627
+ 'KDNA_VALIDATION_CONTEXT_INVALID',
1628
+ 'Trace validation requires a valid independently correlated Host receipt.',
1629
+ receiptResult.errors,
1630
+ );
1631
+ }
1632
+ safeContext.receipt = receiptResult.value;
1633
+ }
1634
+ try {
1635
+ const code = validateTraceSemantics(
1636
+ safeTrace,
1637
+ safeContext.plan,
1638
+ safeContext.request,
1639
+ safeContext.receipt,
1640
+ safeContext.capabilities,
1641
+ safeContext.coreCapsuleVersions,
1642
+ safeContext.trustedDeliveryObservation,
1643
+ );
1644
+ return code ? invalid(code, 'JudgmentTrace correlation failed.') : ok(safeTrace);
1645
+ } catch (error) {
1646
+ return invalid('KDNA_INPUT_INVALID', 'JudgmentTrace is not a safe JSON value.', {
1647
+ cause: error && typeof error.message === 'string' ? error.message : String(error),
1648
+ });
1649
+ }
1650
+ }
1651
+
1652
+ function buildJudgmentTraceV1(input, context) {
1653
+ const contextResult = snapshotContext(context, [
1654
+ 'plan',
1655
+ 'trustedPlanDigest',
1656
+ 'capabilities',
1657
+ 'coreCapsuleVersions',
1658
+ 'request',
1659
+ 'receipt',
1660
+ 'trustedDeliveryObservation',
1661
+ ]);
1662
+ throwInvalid(contextResult);
1663
+ const safeContext = contextResult.value;
1664
+ throwInvalid(validateTrustedDeliveryObservation(safeContext));
1665
+ const planResult = validateConsumptionPlanV1(safeContext.plan, {
1666
+ trustedPlanDigest: safeContext.trustedPlanDigest,
1667
+ });
1668
+ throwInvalid(planResult);
1669
+ safeContext.plan = planResult.value;
1670
+
1671
+ const capabilitiesResult = validateSchema('capabilities', safeContext.capabilities);
1672
+ if (!capabilitiesResult.valid) {
1673
+ throw new KDNAExecutionContractError(
1674
+ 'KDNA_VALIDATION_CONTEXT_INVALID',
1675
+ 'Trace construction requires schema-valid independent Host capabilities.',
1676
+ capabilitiesResult.errors,
1677
+ );
1678
+ }
1679
+ safeContext.capabilities = capabilitiesResult.value;
1680
+ if (safeContext.request !== null) {
1681
+ const requestResult = validateSchema('request', safeContext.request);
1682
+ if (!requestResult.valid) {
1683
+ throw new KDNAExecutionContractError(
1684
+ 'KDNA_VALIDATION_CONTEXT_INVALID',
1685
+ 'Trace construction requires a schema-valid independent Host request.',
1686
+ requestResult.errors,
1687
+ );
1688
+ }
1689
+ safeContext.request = requestResult.value;
1690
+ const requestCode = validateRequestSemantics(
1691
+ safeContext.request,
1692
+ safeContext.plan,
1693
+ false,
1694
+ );
1695
+ if (requestCode !== null) {
1696
+ throw new KDNAExecutionContractError(
1697
+ requestCode,
1698
+ 'Trace construction requires an independently correlated Host request.',
1699
+ );
1700
+ }
1701
+ const negotiation = negotiateExecutionPairV1(safeContext.plan, safeContext);
1702
+ if (negotiation.state !== 'selected') {
1703
+ throw new KDNAExecutionContractError(
1704
+ negotiation.issue_code,
1705
+ 'Trace construction requires a selected strict execution pair.',
1706
+ );
1707
+ }
1708
+ }
1709
+ if (safeContext.receipt !== null) {
1710
+ if (safeContext.request === null) {
1711
+ throw new KDNAExecutionContractError(
1712
+ 'KDNA_VALIDATION_CONTEXT_INVALID',
1713
+ 'Trace construction cannot use a receipt without its independent request.',
1714
+ );
1715
+ }
1716
+ const receiptResult = validateAgentHost2ReceiptV1(safeContext.receipt, {
1717
+ request: safeContext.request,
1718
+ });
1719
+ throwInvalid(receiptResult);
1720
+ safeContext.receipt = receiptResult.value;
1721
+ }
1722
+
1723
+ const plan = safeContext.plan;
1724
+ const request = safeContext.request;
1725
+ const receipt = safeContext.receipt;
1726
+ const observation = safeContext.trustedDeliveryObservation;
1727
+ const negotiation = negotiateExecutionPairV1(plan, safeContext);
1728
+ const hasRequest = request !== null;
1729
+ const hasReceipt = receipt !== null;
1730
+ const comparison = hasReceipt
1731
+ ? receipt.runtime_receipt.capsule_delivery_comparison
1732
+ : hasRequest
1733
+ ? observation
1734
+ : 'unavailable';
1735
+ const p = hasRequest ? request.runtime_contract.capsule_delivery_digest : null;
1736
+ const matched = comparison === 'matched';
1737
+
1738
+ let trace;
1739
+ try {
1740
+ trace = {
1741
+ type: 'kdna.judgment.trace',
1742
+ trace_version: '1.0.0',
1743
+ trace_id: input.trace_id,
1744
+ plan_ref: {
1745
+ plan_id: plan.plan_id,
1746
+ plan_digest_profile: plan.integrity.profile,
1747
+ plan_digest: plan.integrity.plan_digest,
1748
+ comparison: 'matched',
1749
+ },
1750
+ parent_trace_id: input.parent_trace_id ?? null,
1751
+ timestamp: input.timestamp,
1752
+ overall_status: input.overall_status,
1753
+ runtime_contract: {
1754
+ plan_capsule_versions: clone(plan.projection_request.accepted_capsule_versions),
1755
+ core_capsule_versions: clone(safeContext.coreCapsuleVersions),
1756
+ plan_host_protocols: clone(plan.host_request.accepted_protocols),
1757
+ host_capabilities: clone(safeContext.capabilities),
1758
+ negotiation_state: hasRequest
1759
+ ? 'selected'
1760
+ : negotiation.state === 'selected'
1761
+ ? 'not_started'
1762
+ : 'blocked',
1763
+ selected_capsule_version: hasRequest ? negotiation.capsule_version : null,
1764
+ selected_host_protocol: hasRequest ? negotiation.host_protocol : null,
1765
+ issue_code: hasRequest || negotiation.state === 'selected' ? null : negotiation.issue_code,
1766
+ },
1767
+ asset_identity: withoutExpectedDigests(plan.asset_ref),
1768
+ digest_evidence: hasRequest ? clone(request.capsule.digests) : unavailableDigestEvidence(),
1769
+ capsule_delivery_evidence: {
1770
+ basis: CAPSULE_DELIVERY_PROFILE,
1771
+ observed: p,
1772
+ sender_computed: hasRequest,
1773
+ host_recomputed: hasReceipt
1774
+ ? receipt.runtime_receipt.host_recomputed_capsule_delivery_digest
1775
+ : null,
1776
+ host_echoed: hasReceipt
1777
+ ? receipt.runtime_receipt.echoed_capsule_delivery_digest
1778
+ : null,
1779
+ delivered_capsule_version:
1780
+ hasReceipt || observation === 'not_observed' ? CAPSULE_VERSION : null,
1781
+ host_boundary_comparison: comparison,
1782
+ request_id: hasReceipt || observation === 'not_observed' ? request.request_id : null,
1783
+ },
1784
+ projection_actual: {
1785
+ profile: hasRequest ? request.projection_contract.profile : null,
1786
+ capsule_delivery_digest: p,
1787
+ profile_deviated_from_plan: hasRequest ? false : null,
1788
+ },
1789
+ host_receipt: hasReceipt ? clone(receipt) : null,
1790
+ execution: {
1791
+ delivery_status: matched
1792
+ ? 'correlated_response'
1793
+ : hasReceipt || observation === 'not_observed'
1794
+ ? 'rejected_before_execution'
1795
+ : 'not_delivered',
1796
+ semantic_consumption: matched
1797
+ ? clone(receipt.runtime_receipt.semantic_consumption)
1798
+ : { state: 'not_observed', basis: null },
1799
+ execution_status: matched
1800
+ ? receipt.runtime_receipt.provider_execution_status
1801
+ : 'not_started',
1802
+ conformance_status: 'not_evaluated',
1803
+ model_identity: matched
1804
+ ? clone(receipt.runtime_receipt.model_identity)
1805
+ : { value: null, basis: 'not_observed' },
1806
+ },
1807
+ budget: expectedBudgetEvidence(plan, request, receipt),
1808
+ result_ref:
1809
+ matched && receipt.outcome !== null
1810
+ ? {
1811
+ shape: request.result_contract.shape,
1812
+ result_digest: digestJcs(receipt.outcome),
1813
+ basis: 'kdna-result-jcs-v1',
1814
+ stored: input.result_stored ?? true,
1815
+ }
1816
+ : null,
1817
+ errors: clone(input.errors || []),
1818
+ warnings: clone(input.warnings || []),
1819
+ };
1820
+ } catch {
1821
+ throw new KDNAExecutionContractError(
1822
+ 'KDNA_TRACE_INPUT_INVALID',
1823
+ 'Cannot build JudgmentTrace 1 from the supplied input and independent context.',
1824
+ );
1825
+ }
1826
+
1827
+ const result = validateJudgmentTraceV1(trace, safeContext);
1828
+ throwInvalid(result);
1829
+ return trace;
1830
+ }
1831
+
1832
+ module.exports = {
1833
+ KDNAExecutionContractError,
1834
+ PLAN_DIGEST_PROFILE,
1835
+ HOST_PROTOCOL,
1836
+ DEFAULT_CORE_CAPSULE_VERSIONS,
1837
+ parseExecutionContractJsonV1,
1838
+ computeConsumptionPlanDigestV1,
1839
+ buildConsumptionPlanV1,
1840
+ validateConsumptionPlanV1,
1841
+ negotiateExecutionPairV1,
1842
+ buildAgentHost2RequestV1,
1843
+ validateAgentHost2RequestV1,
1844
+ validateAgentHost2ReceiptV1,
1845
+ deriveBudgetEvidenceV1,
1846
+ buildPreHostBudgetBlockedTraceV1,
1847
+ validatePreHostBudgetBlockedTraceV1,
1848
+ buildJudgmentTraceV1,
1849
+ validateJudgmentTraceV1,
1850
+ };