@pulse-compute/wasm-host-runtime 0.0.0 → 1.0.0-beta.2

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,1569 @@
1
+ 'use strict';
2
+ const portableKv = require('@pulse-compute/runtime/host');
3
+
4
+
5
+ function loadRuntimeApi() {
6
+ try { return require('@pulse-compute/wasm-contracts/handler/runtime-api'); }
7
+ catch (error) {
8
+ if (error && ['MODULE_NOT_FOUND', 'ERR_PACKAGE_PATH_NOT_EXPORTED'].includes(error.code)) return require('../../../contracts/src/handler/runtime-api.js');
9
+ throw error;
10
+ }
11
+ }
12
+
13
+ function loadCanonicalRuntimeContract() {
14
+ try { return require('@pulse-compute/wasm-contracts/handler/canonical-runtime'); }
15
+ catch (error) {
16
+ if (error && ['MODULE_NOT_FOUND', 'ERR_PACKAGE_PATH_NOT_EXPORTED'].includes(error.code)) return require('../../../contracts/src/handler/canonical-runtime.js');
17
+ throw error;
18
+ }
19
+ }
20
+
21
+ function loadStreamingContract() {
22
+ try { return require('@pulse-compute/wasm-contracts/host/streaming-passthrough'); }
23
+ catch (error) {
24
+ if (error && ['MODULE_NOT_FOUND', 'ERR_PACKAGE_PATH_NOT_EXPORTED'].includes(error.code)) return require('../../../contracts/src/host/streaming-passthrough.js');
25
+ throw error;
26
+ }
27
+ }
28
+
29
+ function loadContinuationRegistry() {
30
+ try { return require('@pulse-compute/wasm-host-runtime/runtime/continuation-registry'); }
31
+ catch (error) {
32
+ if (error && ['MODULE_NOT_FOUND', 'ERR_PACKAGE_PATH_NOT_EXPORTED'].includes(error.code)) return require('../../../host-runtime/src/runtime/continuation-registry.js');
33
+ throw error;
34
+ }
35
+ }
36
+
37
+ function loadJsonSemanticTraceContract() {
38
+ try { return require('@pulse-compute/wasm-contracts/schema-json/semantic-trace'); }
39
+ catch (error) {
40
+ if (error && ['MODULE_NOT_FOUND', 'ERR_PACKAGE_PATH_NOT_EXPORTED'].includes(error.code)) return require('../../../contracts/src/schema-json/semantic-trace.js');
41
+ throw error;
42
+ }
43
+ }
44
+
45
+ function loadLoggingContract() {
46
+ try { return require('@pulse-compute/wasm-contracts/logging'); }
47
+ catch (error) {
48
+ if (error && ['MODULE_NOT_FOUND', 'ERR_PACKAGE_PATH_NOT_EXPORTED'].includes(error.code)) return require('../../../contracts/src/logging.js');
49
+ throw error;
50
+ }
51
+ }
52
+
53
+ function loadEventContract() {
54
+ try { return require('@pulse-compute/wasm-contracts/events'); }
55
+ catch (error) {
56
+ if (error && ['MODULE_NOT_FOUND', 'ERR_PACKAGE_PATH_NOT_EXPORTED'].includes(error.code)) return require('../../../contracts/src/events/contracts.js');
57
+ throw error;
58
+ }
59
+ }
60
+
61
+ const runtimeApi = loadRuntimeApi();
62
+ const canonicalRuntimeContract = loadCanonicalRuntimeContract();
63
+ const streamingContract = loadStreamingContract();
64
+ const { createContinuationRegistry } = loadContinuationRegistry();
65
+ const jsonSemanticTrace = loadJsonSemanticTraceContract();
66
+ const loggingContract = loadLoggingContract();
67
+ const eventContract = loadEventContract();
68
+
69
+ const CANONICAL_HOST_RUNTIME_VERSION = 'pulse.canonical-host-runtime.v1';
70
+ const REDACTED_VALUE = '<redacted>';
71
+ const CIRCULAR_VALUE = '<cycle>';
72
+ const BINARY_VALUE = '<binary>';
73
+ const BOUNDED_VALUE = '<bounded>';
74
+ const ACCESSOR_VALUE = '<accessor>';
75
+ const MAX_REDACTION_DEPTH = 32;
76
+ const MAX_REDACTION_ENTRIES = 1024;
77
+ const SENSITIVE_HEADERS = new Set(['authorization', 'proxy-authorization', 'x-api-key', 'cookie', 'set-cookie']);
78
+ const SENSITIVE_FIELDS = new Set([
79
+ ...SENSITIVE_HEADERS,
80
+ 'api-key', 'apikey', 'credential', 'credentials', 'password', 'secret', 'token'
81
+ ]);
82
+ let executionSequence = 0;
83
+
84
+ class CanonicalRuntimeError extends Error {
85
+ constructor(name, code, message, detail = {}) {
86
+ super(message);
87
+ this.name = name;
88
+ this.code = code;
89
+ this.detail = Object.freeze({ ...detail });
90
+ }
91
+ }
92
+
93
+ function assertCanonicalProgramCompatibility(programModule) {
94
+ const metadata = programModule && programModule.metadata;
95
+ const actualProgramVersion = programModule && programModule.version;
96
+ const actualMetadataVersion = metadata && metadata.version;
97
+ const actualProtocolVersion = metadata && metadata.runtimeProtocolVersion;
98
+ const expectedProgramVersion = canonicalRuntimeContract.CANONICAL_PROGRAM_VERSION;
99
+ const expectedProtocolVersion = canonicalRuntimeContract.CANONICAL_RUNTIME_PROTOCOL_VERSION;
100
+ if (actualProgramVersion !== expectedProgramVersion || actualMetadataVersion !== expectedProgramVersion || actualProtocolVersion !== expectedProtocolVersion) {
101
+ throw new CanonicalRuntimeError(
102
+ 'CanonicalProgramCompatibilityError',
103
+ 'PULSE_CANONICAL_PROGRAM_INCOMPATIBLE',
104
+ 'Canonical program and runtime protocol versions are incompatible.',
105
+ {
106
+ expectedProgramVersion,
107
+ actualProgramVersion,
108
+ actualMetadataVersion,
109
+ expectedProtocolVersion,
110
+ actualProtocolVersion
111
+ }
112
+ );
113
+ }
114
+ const schemaIds = metadata && Array.isArray(metadata.schemaIds) ? metadata.schemaIds : [];
115
+ const schemaCodecs = programModule && programModule.schemaCodecs;
116
+ if (schemaIds.length > 0 && (!schemaCodecs || typeof schemaCodecs.decode !== 'function' || typeof schemaCodecs.encode !== 'function')) {
117
+ throw new CanonicalRuntimeError(
118
+ 'CanonicalSchemaCompatibilityError',
119
+ 'PULSE_CANONICAL_SCHEMA_CODECS_MISSING',
120
+ 'Canonical program declares schemas but does not export its compiled schema codecs.',
121
+ { schemaIds }
122
+ );
123
+ }
124
+ return metadata;
125
+ }
126
+
127
+ function normalizeHeaders(headers) {
128
+ return runtimeApi.body.normalizeHeaders(headers).map(([name, value]) => [String(name), String(value)]);
129
+ }
130
+
131
+ function headerValue(headers, name) {
132
+ return runtimeApi.body.headerValue(headers, name);
133
+ }
134
+
135
+ function isSensitiveField(value) {
136
+ const key = String(value || '').toLowerCase();
137
+ if (SENSITIVE_FIELDS.has(key)) return true;
138
+ const compact = key.replace(/[^a-z0-9]/g, '');
139
+ return compact.endsWith('token')
140
+ || compact.endsWith('secret')
141
+ || compact.endsWith('password')
142
+ || compact.endsWith('credential')
143
+ || compact.endsWith('credentials')
144
+ || compact.endsWith('apikey');
145
+ }
146
+
147
+ function ownDataValue(object, key) {
148
+ if (!object || (typeof object !== 'object' && typeof object !== 'function')) return undefined;
149
+ const descriptor = Object.getOwnPropertyDescriptor(object, key);
150
+ return descriptor && Object.prototype.hasOwnProperty.call(descriptor, 'value')
151
+ ? descriptor.value
152
+ : undefined;
153
+ }
154
+
155
+ function initialSensitiveValues(options = {}) {
156
+ const output = new Set();
157
+ const secrets = options.secrets && typeof options.secrets === 'object' ? options.secrets : {};
158
+ for (const key of Reflect.ownKeys(secrets)) {
159
+ if (typeof key !== 'string') continue;
160
+ const value = ownDataValue(secrets, key);
161
+ if (typeof value === 'string' && value.length > 0) output.add(value);
162
+ }
163
+ const redactionValues = Array.isArray(options.redactionValues) ? options.redactionValues : [];
164
+ for (let index = 0; index < redactionValues.length; index += 1) {
165
+ const value = ownDataValue(redactionValues, String(index));
166
+ if (typeof value === 'string' && value.length > 0) output.add(value);
167
+ }
168
+ return output;
169
+ }
170
+
171
+ function redactString(value, sensitiveValues = new Set()) {
172
+ let output = String(value);
173
+ const tokens = [...sensitiveValues].filter((entry) => typeof entry === 'string' && entry.length > 0)
174
+ .sort((left, right) => right.length - left.length);
175
+ for (const secret of tokens) output = output.split(secret).join(REDACTED_VALUE);
176
+ return output;
177
+ }
178
+
179
+ function defineRedactedProperty(target, key, value, enumerable = true) {
180
+ try {
181
+ Object.defineProperty(target, key, {
182
+ enumerable,
183
+ configurable: true,
184
+ writable: false,
185
+ value
186
+ });
187
+ } catch (_) {
188
+ // Redaction must contain hostile evidence rather than execute or preserve it.
189
+ }
190
+ }
191
+
192
+ function redactRuntimeValue(value, sensitiveValues = new Set(), seen = new WeakSet(), keyName = '', depth = 0) {
193
+ if (isSensitiveField(keyName)) return REDACTED_VALUE;
194
+ if (typeof value === 'string') return redactString(value, sensitiveValues);
195
+ if (value === null || value === undefined || typeof value !== 'object') return value;
196
+ if (typeof Buffer !== 'undefined' && Buffer.isBuffer(value)) return BINARY_VALUE;
197
+ if (value instanceof Uint8Array || value instanceof ArrayBuffer) return BINARY_VALUE;
198
+ if (depth >= MAX_REDACTION_DEPTH) return BOUNDED_VALUE;
199
+ if (value instanceof Error) return redactRuntimeError(value, sensitiveValues, seen, depth);
200
+ if (seen.has(value)) return CIRCULAR_VALUE;
201
+ seen.add(value);
202
+
203
+ if (Array.isArray(value)) {
204
+ const output = [];
205
+ const length = Math.min(value.length, MAX_REDACTION_ENTRIES);
206
+ for (let index = 0; index < length; index += 1) {
207
+ const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
208
+ const entry = descriptor && Object.prototype.hasOwnProperty.call(descriptor, 'value')
209
+ ? descriptor.value
210
+ : ACCESSOR_VALUE;
211
+ if (Array.isArray(entry)) {
212
+ const name = ownDataValue(entry, '0');
213
+ if (entry.length >= 2 && isSensitiveField(name)) {
214
+ const pair = [redactString(name, sensitiveValues), REDACTED_VALUE];
215
+ const pairLength = Math.min(entry.length, MAX_REDACTION_ENTRIES);
216
+ for (let pairIndex = 2; pairIndex < pairLength; pairIndex += 1) {
217
+ const pairDescriptor = Object.getOwnPropertyDescriptor(entry, String(pairIndex));
218
+ pair.push(pairDescriptor && Object.prototype.hasOwnProperty.call(pairDescriptor, 'value')
219
+ ? redactRuntimeValue(pairDescriptor.value, sensitiveValues, seen, '', depth + 1)
220
+ : ACCESSOR_VALUE);
221
+ }
222
+ output.push(Object.freeze(pair));
223
+ continue;
224
+ }
225
+ }
226
+ output.push(redactRuntimeValue(entry, sensitiveValues, seen, '', depth + 1));
227
+ }
228
+ return Object.freeze(output);
229
+ }
230
+
231
+ const output = {};
232
+ for (const key of Reflect.ownKeys(value).slice(0, MAX_REDACTION_ENTRIES)) {
233
+ if (typeof key !== 'string') continue;
234
+ const outputKey = redactString(key, sensitiveValues);
235
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
236
+ const entry = descriptor && Object.prototype.hasOwnProperty.call(descriptor, 'value')
237
+ ? redactRuntimeValue(descriptor.value, sensitiveValues, seen, key, depth + 1)
238
+ : (isSensitiveField(key) ? REDACTED_VALUE : ACCESSOR_VALUE);
239
+ defineRedactedProperty(output, outputKey, entry);
240
+ }
241
+ return Object.freeze(output);
242
+ }
243
+
244
+ function redactRuntimeError(error, sensitiveValues = new Set(), seen = new WeakSet(), depth = 0) {
245
+ if (typeof error === 'string') return redactString(error, sensitiveValues);
246
+ if (!error || typeof error !== 'object') return error;
247
+ if (depth >= MAX_REDACTION_DEPTH) {
248
+ const bounded = new Error(BOUNDED_VALUE);
249
+ bounded.name = 'PulseRedactedError';
250
+ return bounded;
251
+ }
252
+ if (seen.has(error)) {
253
+ const circular = new Error(CIRCULAR_VALUE);
254
+ circular.name = 'PulseRedactedError';
255
+ return circular;
256
+ }
257
+ seen.add(error);
258
+
259
+ const rawMessage = ownDataValue(error, 'message');
260
+ const rawName = ownDataValue(error, 'name');
261
+ const rawCode = ownDataValue(error, 'code');
262
+ const rawDetail = ownDataValue(error, 'detail');
263
+ const message = redactString(typeof rawMessage === 'string' ? rawMessage : 'Pulse runtime failure.', sensitiveValues);
264
+ const name = redactString(typeof rawName === 'string' ? rawName : 'Error', sensitiveValues);
265
+ const code = typeof rawCode === 'string' ? redactString(rawCode, sensitiveValues) : undefined;
266
+ const detail = rawDetail === undefined ? undefined : redactRuntimeValue(rawDetail, sensitiveValues, seen, 'detail', depth + 1);
267
+
268
+ const safe = code === undefined
269
+ ? Object.assign(new Error(message), { name })
270
+ : new CanonicalRuntimeError(name, code, message, detail === undefined ? {} : detail);
271
+
272
+ const rawCause = ownDataValue(error, 'cause');
273
+ if (rawCause !== undefined) {
274
+ defineRedactedProperty(safe, 'cause', redactRuntimeError(rawCause, sensitiveValues, seen, depth + 1), false);
275
+ }
276
+
277
+ const reserved = new Set(['name', 'message', 'stack', 'code', 'detail', 'cause']);
278
+ for (const key of Reflect.ownKeys(error).slice(0, MAX_REDACTION_ENTRIES)) {
279
+ if (typeof key !== 'string' || reserved.has(key)) continue;
280
+ const descriptor = Object.getOwnPropertyDescriptor(error, key);
281
+ if (!descriptor || !Object.prototype.hasOwnProperty.call(descriptor, 'value')) continue;
282
+ defineRedactedProperty(
283
+ safe,
284
+ redactString(key, sensitiveValues),
285
+ redactRuntimeValue(descriptor.value, sensitiveValues, seen, key, depth + 1),
286
+ descriptor.enumerable
287
+ );
288
+ }
289
+
290
+ const rawStack = ownDataValue(error, 'stack');
291
+ if (typeof rawStack === 'string') {
292
+ try { Object.defineProperty(safe, 'stack', { configurable: true, writable: true, value: redactString(rawStack, sensitiveValues) }); }
293
+ catch (_) { /* best effort */ }
294
+ }
295
+ return safe;
296
+ }
297
+
298
+ function redactedHeaders(headers, sensitiveValues = new Set()) {
299
+ return normalizeHeaders(headers).map(([name, value]) => [
300
+ redactString(name, sensitiveValues),
301
+ isSensitiveField(name) ? REDACTED_VALUE : redactString(value, sensitiveValues)
302
+ ]);
303
+ }
304
+
305
+ function jsonTrace(trace, input, value) {
306
+ if (trace && typeof trace.json === 'function') trace.json(input, value);
307
+ }
308
+
309
+ function jsonTraceErrorDetail(error) {
310
+ const detail = error && (error.detail || error.details) || {};
311
+ return {
312
+ errorCode: error && error.code || 'PULSE_SCHEMA_OPERATION_FAILED',
313
+ errorPath: detail.path || detail.field || null,
314
+ expected: detail.expected || null,
315
+ actualKind: detail.actualKind || detail.actual || null
316
+ };
317
+ }
318
+
319
+ function strictSchemaPolicy(options, schemaCodecs) {
320
+ return options && options.strict === true
321
+ && Boolean(schemaCodecs && Array.isArray(schemaCodecs.ids) && schemaCodecs.ids.length > 0);
322
+ }
323
+
324
+ function requireSchemaCodecs(schemaCodecs, schemaId, source) {
325
+ if (
326
+ !schemaCodecs
327
+ || typeof schemaCodecs.has !== 'function'
328
+ || typeof schemaCodecs.decodeJsonText !== 'function'
329
+ || typeof schemaCodecs.encodeJsonText !== 'function'
330
+ ) {
331
+ throw new CanonicalRuntimeError(
332
+ 'SchemaReferenceError',
333
+ 'PULSE_SCHEMA_CODECS_UNAVAILABLE',
334
+ `Pulse cannot use schema ${JSON.stringify(String(schemaId))} because the project codec registry is unavailable.`,
335
+ { schemaId: String(schemaId), source }
336
+ );
337
+ }
338
+ return schemaCodecs;
339
+ }
340
+
341
+ function requireSchemaId(schemaCodecs, schemaId, source) {
342
+ if (typeof schemaId !== 'string' || schemaId.length === 0) {
343
+ throw new CanonicalRuntimeError(
344
+ 'SchemaReferenceError',
345
+ 'PULSE_SCHEMA_ID_INVALID',
346
+ 'Pulse schema IDs must be non-empty strings.',
347
+ { schemaIdType: typeof schemaId, source }
348
+ );
349
+ }
350
+ const codecs = requireSchemaCodecs(schemaCodecs, schemaId, source);
351
+ if (!codecs.has(schemaId)) {
352
+ throw new CanonicalRuntimeError(
353
+ 'SchemaReferenceError',
354
+ 'PULSE_SCHEMA_REFERENCE',
355
+ `Unknown compiled schema ${schemaId}.`,
356
+ { schemaId, source }
357
+ );
358
+ }
359
+ return schemaId;
360
+ }
361
+
362
+ function assertSchemaJsonContentType(schemaCodecs, headers, schemaId, source) {
363
+ const policy = schemaCodecs && schemaCodecs.registry && schemaCodecs.registry.contentTypePolicy || 'accept-json-or-missing';
364
+ const contentType = String(headerValue(headers, 'content-type') || '').trim().toLowerCase();
365
+ const mediaType = contentType.split(';')[0].trim();
366
+ const json = mediaType === 'application/json' || mediaType.endsWith('+json');
367
+ const accepted = json || (!contentType && policy === 'accept-json-or-missing');
368
+ if (accepted) return;
369
+ throw new CanonicalRuntimeError(
370
+ 'SchemaDecodeError',
371
+ 'PULSE_SCHEMA_CONTENT_TYPE',
372
+ `Schema ${String(schemaId)} requires a JSON content type.`,
373
+ { schemaId: String(schemaId), source, contentType, contentTypePolicy: policy }
374
+ );
375
+ }
376
+
377
+ function assertSchemaBodySize(schemaCodecs, text, schemaId, source) {
378
+ const configured = Number(schemaCodecs && schemaCodecs.registry && schemaCodecs.registry.maxBytes);
379
+ const maxBytes = Number.isSafeInteger(configured) && configured > 0 ? configured : 65536;
380
+ const bytes = Buffer.byteLength(String(text || ''), 'utf8');
381
+ if (bytes <= maxBytes) return;
382
+ throw new CanonicalRuntimeError(
383
+ 'BodyTooLargeError',
384
+ 'PULSE_BODY_TOO_LARGE',
385
+ `Structured schema body exceeds the ${maxBytes} byte limit.`,
386
+ { schemaId: String(schemaId), source, bytes, maxBytes }
387
+ );
388
+ }
389
+
390
+ const runtimeValueErrorCodes = Object.freeze({
391
+ BodyTooLargeError: 'PULSE_BODY_TOO_LARGE',
392
+ BodyDecodeError: 'PULSE_BODY_DECODE',
393
+ BodyUnavailableError: 'PULSE_BODY_UNAVAILABLE',
394
+ OpaqueBodyInspectionError: 'PULSE_OPAQUE_BODY_INSPECTION',
395
+ ResponseEncodeError: 'PULSE_RESPONSE_ENCODE',
396
+ FetchNetworkError: 'PULSE_FETCH_NETWORK',
397
+ FetchTimeoutError: 'PULSE_FETCH_TIMEOUT',
398
+ ContinuationExpiredError: 'PULSE_CONTINUATION_EXPIRED',
399
+ ContinuationDoubleResumeError: 'PULSE_CONTINUATION_DOUBLE_RESUME',
400
+ ProviderCapabilityMissingError: 'PULSE_PROVIDER_CAPABILITY_MISSING'
401
+ });
402
+
403
+ function unwrap(result) {
404
+ if (result && result.ok === false) {
405
+ const name = result.error || 'CanonicalRuntimeError';
406
+ const code = runtimeValueErrorCodes[name]
407
+ || `PULSE_${String(name || 'RUNTIME').replace(/Error$/i, '').replace(/([a-z0-9])([A-Z])/g, '$1_$2').replace(/[^A-Z0-9]+/gi, '_').toUpperCase()}`;
408
+ const error = new CanonicalRuntimeError(name, code, result.message || name || 'Canonical runtime operation failed.', result.detail || {});
409
+ throw error;
410
+ }
411
+ return result && Object.prototype.hasOwnProperty.call(result, 'value') ? result.value : result;
412
+ }
413
+
414
+ function normalizeRequest(options = {}) {
415
+ const input = options.request || {};
416
+ const pathValue = String(input.path || (() => {
417
+ try { return new URL(String(input.url || 'https://app.example.test/')).pathname; }
418
+ catch (_) { return '/'; }
419
+ })());
420
+ const url = String(input.url || `https://app.example.test${pathValue}`);
421
+ const headers = normalizeHeaders(input.headers || (input.contentType ? [['content-type', input.contentType]] : []));
422
+ return Object.freeze({
423
+ method: String(input.method || 'GET').toUpperCase(),
424
+ url,
425
+ path: pathValue,
426
+ headers,
427
+ body: Object.prototype.hasOwnProperty.call(input, 'body') ? input.body : undefined
428
+ });
429
+ }
430
+
431
+ function createRequestSurface(request, options = {}, schemaCodecs, trace) {
432
+ const body = runtimeApi.body.createValue(
433
+ { body: request.body, headers: request.headers },
434
+ { maxBytes: options.maxRequestBodyBytes || options.maxBodyBytes || options.maxStructuredBodyBytes }
435
+ );
436
+ const schemaMemo = new Map();
437
+ return Object.freeze({
438
+ surface: Object.freeze({
439
+ method: request.method,
440
+ url: request.url,
441
+ path: request.path,
442
+ headers: request.headers,
443
+ header(name) { return headerValue(request.headers, name); },
444
+ text() { return unwrap(body.text()); },
445
+ json(schemaId) {
446
+ if (schemaId === undefined) {
447
+ if (strictSchemaPolicy(options, schemaCodecs)) {
448
+ throw new CanonicalRuntimeError(
449
+ 'SchemaReferenceError',
450
+ 'PULSE_SCHEMA_REQUIRED',
451
+ 'Strict Pulse request JSON requires a registered schema ID.',
452
+ { source: 'request', strict: true }
453
+ );
454
+ }
455
+ return unwrap(body.json());
456
+ }
457
+ const key = requireSchemaId(schemaCodecs, schemaId, 'request');
458
+ if (!schemaMemo.has(key)) {
459
+ const event = {
460
+ boundary: 'request',
461
+ schemaId: key,
462
+ responseCaseId: null,
463
+ operationId: `request:${key}`,
464
+ status: null,
465
+ contentType: headerValue(request.headers, 'content-type') || null,
466
+ headers: request.headers,
467
+ bodyOwnership: 'request-snapshot',
468
+ target: options.target || null,
469
+ provider: options.provider || null,
470
+ effectId: null,
471
+ groupId: null
472
+ };
473
+ try {
474
+ assertSchemaJsonContentType(schemaCodecs, request.headers, key, 'request');
475
+ const text = unwrap(body.text());
476
+ assertSchemaBodySize(schemaCodecs, text, key, 'request');
477
+ const decoded = schemaCodecs.decodeJsonText(key, text, 'request');
478
+ schemaMemo.set(key, decoded);
479
+ jsonTrace(trace, { ...event, kind: 'json.decode.request' }, decoded);
480
+ } catch (error) {
481
+ jsonTrace(trace, { ...event, kind: 'json.decode.error', ...jsonTraceErrorDetail(error) });
482
+ throw error;
483
+ }
484
+ }
485
+ return schemaMemo.get(key);
486
+ }
487
+ }),
488
+ body
489
+ });
490
+ }
491
+
492
+ function createResponseBuilders(schemaCodecs, options = {}, trace) {
493
+ const builders = runtimeApi.body.createResponseBuilders();
494
+ function checked(result) {
495
+ if (result && result.ok === false) return unwrap(result);
496
+ return result;
497
+ }
498
+ return Object.freeze({
499
+ json(value, descriptor) {
500
+ let schemaId;
501
+ let responseCaseId;
502
+ let responseOptions;
503
+ if (typeof descriptor === 'string') {
504
+ const codecs = requireSchemaCodecs(schemaCodecs, descriptor, 'application-response');
505
+ const responseCase = typeof codecs.responseCase === 'function'
506
+ ? codecs.responseCase(descriptor)
507
+ : undefined;
508
+ if (!responseCase) {
509
+ throw new CanonicalRuntimeError(
510
+ 'SchemaReferenceError',
511
+ 'PULSE_RESPONSE_CASE_REFERENCE',
512
+ `Unknown compiled response case ${descriptor}.`,
513
+ { responseCaseId: descriptor, source: 'application-response' }
514
+ );
515
+ }
516
+ responseCaseId = descriptor;
517
+ schemaId = requireSchemaId(schemaCodecs, responseCase.schemaId, 'application-response');
518
+ responseOptions = { status: responseCase.status };
519
+ } else {
520
+ if (descriptor !== undefined && (!descriptor || typeof descriptor !== 'object' || Array.isArray(descriptor))) {
521
+ throw new CanonicalRuntimeError(
522
+ 'SchemaReferenceError',
523
+ 'PULSE_RESPONSE_DESCRIPTOR_INVALID',
524
+ 'Pulse JSON response metadata must be an object or registered response-case ID.',
525
+ { source: 'application-response', valueType: Array.isArray(descriptor) ? 'array' : typeof descriptor }
526
+ );
527
+ }
528
+ const valueOptions = descriptor || {};
529
+ schemaId = Object.prototype.hasOwnProperty.call(valueOptions, 'schema')
530
+ ? requireSchemaId(schemaCodecs, valueOptions.schema, 'application-response')
531
+ : undefined;
532
+ const { schema: _schema, ...rest } = valueOptions;
533
+ responseOptions = rest;
534
+ }
535
+ if (schemaId === undefined) {
536
+ if (strictSchemaPolicy(options, schemaCodecs)) {
537
+ throw new CanonicalRuntimeError(
538
+ 'SchemaReferenceError',
539
+ 'PULSE_SCHEMA_REQUIRED',
540
+ 'Strict Pulse JSON responses require a schema descriptor or registered response-case ID.',
541
+ { source: 'application-response', strict: true }
542
+ );
543
+ }
544
+ return checked(builders.json(value, responseOptions));
545
+ }
546
+ const key = String(schemaId);
547
+ const status = responseOptions.status === undefined ? 200 : Number(responseOptions.status);
548
+ const traceStatus = Number.isSafeInteger(status) && status >= 100 && status <= 599 ? status : null;
549
+ const headers = normalizeHeaders(responseOptions.headers);
550
+ if (!headerValue(headers, 'content-type')) headers.push(['content-type', 'application/json; charset=utf-8']);
551
+ const event = {
552
+ boundary: 'application-response',
553
+ schemaId: key,
554
+ responseCaseId: responseCaseId || null,
555
+ operationId: `application-response:${responseCaseId || key}`,
556
+ status: traceStatus,
557
+ contentType: headerValue(headers, 'content-type') || null,
558
+ headers,
559
+ bodyOwnership: 'application-owned',
560
+ target: options.target || null,
561
+ provider: options.provider || null,
562
+ effectId: null,
563
+ groupId: null
564
+ };
565
+ try {
566
+ const text = schemaCodecs.encodeJsonText(key, value, 'application-response');
567
+ const normalized = schemaCodecs.decodeJsonText(key, text, 'application-response-trace');
568
+ const result = checked(builders.jsonText(text, responseOptions, normalized));
569
+ jsonTrace(trace, { ...event, kind: 'json.encode.response' }, normalized);
570
+ return result;
571
+ } catch (error) {
572
+ jsonTrace(trace, { ...event, kind: 'json.encode.error', ...jsonTraceErrorDetail(error) });
573
+ throw error;
574
+ }
575
+ },
576
+ text(value, options) { return checked(builders.text(value, options)); },
577
+ response(value) { return checked(builders.response(value)); }
578
+ });
579
+ }
580
+
581
+ function createCanonicalContext(options, trace) {
582
+ const request = normalizeRequest(options);
583
+ const schemaCodecs = options.schemaCodecs;
584
+ const requestSurface = createRequestSurface(request, options, schemaCodecs, trace);
585
+ const builders = createResponseBuilders(schemaCodecs, options, trace);
586
+ const configValues = Object.freeze({ ...(options.config || {}) });
587
+ const secretValues = Object.freeze({ ...(options.secrets || {}) });
588
+ const kvStores = new Map(Object.entries(options.kv || {}).map(([name, values]) => [String(name), new Map(Object.entries(values || {}))]));
589
+ const requestState = new Map();
590
+ const capabilities = options.capabilities && typeof options.capabilities === 'object' ? options.capabilities : {};
591
+ const configCapability = capabilities.config && typeof capabilities.config.get === 'function' ? capabilities.config : undefined;
592
+ const secretCapability = capabilities.secret && typeof capabilities.secret.get === 'function' ? capabilities.secret : undefined;
593
+ const kvCapability = typeof capabilities.kv === 'function' ? capabilities.kv : undefined;
594
+ const reporting = loggingContract.reportingDescriptor(options.reporting);
595
+ const providerLog = typeof options.log === 'function' ? options.log : undefined;
596
+ const redactLogMessage = typeof options.redactLogMessage === 'function'
597
+ ? options.redactLogMessage
598
+ : String;
599
+ const log = {};
600
+ for (const [name, level] of Object.entries(loggingContract.LOG_METHOD_LEVELS)) {
601
+ log[name] = (message) => {
602
+ if (typeof message !== 'string') throw new TypeError(`ctx.log.${name} requires one string message.`);
603
+ if (!loggingContract.logStatementEnabled(level, reporting.level)) return;
604
+ let safeMessage;
605
+ try { safeMessage = String(redactLogMessage(message)); }
606
+ catch (_) { safeMessage = REDACTED_VALUE; }
607
+ const event = Object.freeze({
608
+ version: 'pulse.log-event.v1',
609
+ type: 'log',
610
+ level,
611
+ name,
612
+ message: safeMessage,
613
+ target: options.target || null,
614
+ provider: options.provider || null
615
+ });
616
+ try { trace.push(event); }
617
+ catch (_) { /* Logging evidence is best effort. */ }
618
+ if (providerLog) {
619
+ try { providerLog(level, safeMessage, event); }
620
+ catch (_) { /* Provider logging failures never fail the request. */ }
621
+ }
622
+ };
623
+ }
624
+ try {
625
+ trace.push(Object.freeze({
626
+ version: 'pulse.log-event.v1',
627
+ type: 'logging-config',
628
+ reporting: Object.freeze({ name: reporting.name, level: reporting.level }),
629
+ target: options.target || null,
630
+ provider: options.provider || null
631
+ }));
632
+ } catch (_) {
633
+ // Logging evidence is best effort.
634
+ }
635
+
636
+ const ctx = Object.freeze({
637
+ req: requestSurface.surface,
638
+ log: Object.freeze(log),
639
+ fetch() {
640
+ throw new CanonicalRuntimeError('CanonicalUnloweredFetchError', 'PULSE_CANONICAL_UNLOWERED_FETCH', 'ctx.fetch reached the runtime without compiler lowering.');
641
+ },
642
+ json: builders.json,
643
+ text: builders.text,
644
+ response: builders.response,
645
+ state: Object.freeze({
646
+ get(key) {
647
+ if (typeof key !== 'string') {
648
+ throw new CanonicalRuntimeError('CanonicalStateKeyError', 'PULSE_STATE_KEY_STRING_REQUIRED', 'ctx.state.get requires a string key.', { keyType: typeof key });
649
+ }
650
+ return requestState.has(key) ? requestState.get(key) : undefined;
651
+ },
652
+ set(key, value) {
653
+ if (typeof key !== 'string') {
654
+ throw new CanonicalRuntimeError('CanonicalStateKeyError', 'PULSE_STATE_KEY_STRING_REQUIRED', 'ctx.state.set requires a string key.', { keyType: typeof key });
655
+ }
656
+ if (typeof value !== 'string') {
657
+ throw new CanonicalRuntimeError('CanonicalStateValueError', 'PULSE_STATE_VALUE_STRING_REQUIRED', 'ctx.state.set requires a string value.', { key, valueType: typeof value });
658
+ }
659
+ requestState.set(key, value);
660
+ }
661
+ }),
662
+ config: Object.freeze({
663
+ get(name) {
664
+ const key = String(name);
665
+ trace.push(Object.freeze({ type: 'config-read', name: key, source: configCapability ? 'provider-capability' : 'runtime-values' }));
666
+ return configCapability ? configCapability.get(key) : configValues[key];
667
+ }
668
+ }),
669
+ secret: Object.freeze({
670
+ get(name) {
671
+ const key = String(name);
672
+ trace.push(Object.freeze({ type: 'secret-read', name: key, value: '<redacted>', source: secretCapability ? 'provider-capability' : 'runtime-values' }));
673
+ return secretCapability ? secretCapability.get(key) : secretValues[key];
674
+ }
675
+ }),
676
+ kv(name) {
677
+ const storeName = String(name);
678
+ const externalStore = kvCapability ? kvCapability(storeName) : undefined;
679
+ if (externalStore !== undefined && (!externalStore || typeof externalStore.get !== 'function' || typeof externalStore.put !== 'function')) {
680
+ throw new CanonicalRuntimeError('ProviderCapabilityError', 'PULSE_PROVIDER_CAPABILITY_INVALID', `Provider KV capability ${storeName} must expose get and put operations.`, { capability: 'kv', store: storeName });
681
+ }
682
+ if (!externalStore && !kvStores.has(storeName)) kvStores.set(storeName, new Map());
683
+ const store = externalStore || kvStores.get(storeName);
684
+ return Object.freeze({
685
+ get(key) {
686
+ const normalized = String(key);
687
+ trace.push(Object.freeze({ type: 'kv-get', store: storeName, key: normalized, source: externalStore ? 'provider-capability' : 'runtime-values' }));
688
+ return store.get(normalized);
689
+ },
690
+ put(key, value) {
691
+ const normalized = String(key);
692
+ const result = store.put ? store.put(normalized, value) : store.set(normalized, value);
693
+ trace.push(Object.freeze({ type: 'kv-put', store: storeName, key: normalized, source: externalStore ? 'provider-capability' : 'runtime-values' }));
694
+ return externalStore ? result : true;
695
+ }
696
+ });
697
+ }
698
+ });
699
+
700
+ return Object.freeze({ ctx, request, requestBody: requestSurface.body, kvStores, requestState });
701
+ }
702
+
703
+ function normalizedInit(init = {}, schemaCodecs, options = {}) {
704
+ if (init !== undefined && (!init || typeof init !== 'object' || Array.isArray(init))) {
705
+ throw new CanonicalRuntimeError('FetchRequestError', 'PULSE_FETCH_INIT_INVALID', 'Canonical fetch init must be an object.');
706
+ }
707
+ const value = init || {};
708
+ const supportedFields = new Set(['method', 'headers', 'body', 'json', 'schema', 'timeoutMs']);
709
+ for (const field of Object.keys(value)) {
710
+ if (!supportedFields.has(field)) {
711
+ throw new CanonicalRuntimeError(
712
+ 'FetchRequestError',
713
+ 'PULSE_FETCH_INIT_FIELD_UNSUPPORTED',
714
+ `Canonical fetch init field ${JSON.stringify(field)} is outside the portable contract.`,
715
+ { field }
716
+ );
717
+ }
718
+ }
719
+ const method = String(value.method || 'GET').toUpperCase();
720
+ if (!['GET', 'HEAD', 'POST'].includes(method)) {
721
+ throw new CanonicalRuntimeError(
722
+ 'FetchRequestError',
723
+ 'PULSE_FETCH_METHOD_UNSUPPORTED',
724
+ `Canonical fetch method ${method} is outside the portable GET, HEAD, and POST contract.`,
725
+ { method }
726
+ );
727
+ }
728
+ const headers = normalizeHeaders(value.headers);
729
+ const hasBody = Object.prototype.hasOwnProperty.call(value, 'body');
730
+ const hasJson = Object.prototype.hasOwnProperty.call(value, 'json');
731
+ const hasSchema = Object.prototype.hasOwnProperty.call(value, 'schema');
732
+ if (hasBody && hasJson) {
733
+ throw new CanonicalRuntimeError('FetchRequestError', 'PULSE_FETCH_BODY_AMBIGUOUS', 'Canonical fetch init accepts either body or json, not both.');
734
+ }
735
+ if (hasSchema && !hasJson) {
736
+ throw new CanonicalRuntimeError('FetchRequestError', 'PULSE_FETCH_SCHEMA_WITHOUT_JSON', 'Canonical fetch schema is only valid together with the semantic json field.');
737
+ }
738
+ const schemaId = hasSchema
739
+ ? requireSchemaId(schemaCodecs, value.schema, 'fetch-request')
740
+ : undefined;
741
+ let body;
742
+ let bodyMode = 'none';
743
+ if (hasBody) {
744
+ if (typeof value.body !== 'string') {
745
+ throw new CanonicalRuntimeError('FetchRequestError', 'PULSE_FETCH_BODY_INVALID', 'Canonical fetch body must be a string.', { valueType: typeof value.body });
746
+ }
747
+ body = value.body;
748
+ bodyMode = 'text';
749
+ } else if (hasJson) {
750
+ if (!headerValue(headers, 'content-type')) headers.push(['content-type', 'application/json; charset=utf-8']);
751
+ if (hasSchema) {
752
+ const event = {
753
+ boundary: 'fetch-request',
754
+ schemaId,
755
+ responseCaseId: null,
756
+ operationId: `fetch-request:${options.effectId || schemaId}`,
757
+ status: null,
758
+ contentType: headerValue(headers, 'content-type') || 'application/json; charset=utf-8',
759
+ headers,
760
+ bodyOwnership: 'application-owned',
761
+ target: options.target || null,
762
+ provider: options.provider || null,
763
+ effectId: options.effectId || null,
764
+ groupId: options.groupId || null
765
+ };
766
+ try {
767
+ body = schemaCodecs.encodeJsonText(schemaId, value.json, 'fetch-request');
768
+ const normalized = schemaCodecs.decodeJsonText(schemaId, body, 'fetch-request-trace');
769
+ jsonTrace(options.trace, { ...event, kind: 'json.encode.fetch' }, normalized);
770
+ } catch (error) {
771
+ jsonTrace(options.trace, { ...event, kind: 'json.encode.error', ...jsonTraceErrorDetail(error) });
772
+ throw error;
773
+ }
774
+ } else {
775
+ if (strictSchemaPolicy(options, schemaCodecs)) {
776
+ throw new CanonicalRuntimeError(
777
+ 'SchemaReferenceError',
778
+ 'PULSE_SCHEMA_REQUIRED',
779
+ 'Strict outbound fetch JSON requires a registered schema ID.',
780
+ { source: 'fetch-request', strict: true }
781
+ );
782
+ }
783
+ try { body = JSON.stringify(value.json); }
784
+ catch (error) {
785
+ throw new CanonicalRuntimeError('FetchRequestError', 'PULSE_FETCH_JSON_ENCODE', 'Canonical fetch json could not be serialized.', { message: error.message });
786
+ }
787
+ if (body === undefined) {
788
+ throw new CanonicalRuntimeError('FetchRequestError', 'PULSE_FETCH_JSON_ENCODE', 'Canonical fetch json must serialize to a JSON value.');
789
+ }
790
+ }
791
+ bodyMode = 'json';
792
+ }
793
+ if ((method === 'GET' || method === 'HEAD') && bodyMode !== 'none') {
794
+ throw new CanonicalRuntimeError(
795
+ 'FetchRequestError',
796
+ 'PULSE_FETCH_BODY_METHOD_UNSUPPORTED',
797
+ `Canonical ${method} fetch operations cannot include a body.`,
798
+ { method, bodyMode }
799
+ );
800
+ }
801
+ let timeoutMs;
802
+ if (value.timeoutMs !== undefined) {
803
+ timeoutMs = Number(value.timeoutMs);
804
+ if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0) {
805
+ throw new CanonicalRuntimeError('FetchRequestError', 'PULSE_FETCH_TIMEOUT_INVALID', 'Canonical fetch timeoutMs must be a positive safe integer.', { timeoutMs: value.timeoutMs });
806
+ }
807
+ }
808
+ return Object.freeze({
809
+ method,
810
+ headers,
811
+ body,
812
+ bodyMode,
813
+ ...(hasSchema ? { schema: schemaId } : {}),
814
+ timeoutMs
815
+ });
816
+ }
817
+
818
+ function urlParts(value) {
819
+ if (typeof value !== 'string') {
820
+ throw new CanonicalRuntimeError(
821
+ 'FetchRequestError',
822
+ 'PULSE_FETCH_URL_INVALID',
823
+ 'Canonical fetch URLs must be absolute HTTP or HTTPS strings.',
824
+ { valueType: typeof value }
825
+ );
826
+ }
827
+ let parsed;
828
+ try { parsed = new URL(value); }
829
+ catch (error) { throw new CanonicalRuntimeError('FetchRequestError', 'PULSE_FETCH_URL_INVALID', `Invalid fetch URL ${value}.`, { url: value, message: error.message }); }
830
+ if (!['http:', 'https:'].includes(parsed.protocol)) {
831
+ throw new CanonicalRuntimeError(
832
+ 'FetchRequestError',
833
+ 'PULSE_FETCH_URL_INVALID',
834
+ `Canonical fetch only supports absolute HTTP and HTTPS URLs; received ${parsed.protocol || '<none>'}.`,
835
+ { url: parsed.toString(), protocol: parsed.protocol }
836
+ );
837
+ }
838
+ return Object.freeze({ url: parsed.toString(), origin: parsed.origin, path: `${parsed.pathname}${parsed.search}` || '/' });
839
+ }
840
+
841
+ function normalizeFetchEffect(effect, schemaCodecs, options = {}) {
842
+ const responseMode = effect.responseMode === undefined ? 'auto' : String(effect.responseMode);
843
+ if (!['auto', 'structured', 'opaque'].includes(responseMode)) {
844
+ throw new CanonicalRuntimeError(
845
+ 'CanonicalEffectProtocolError',
846
+ 'PULSE_FETCH_RESPONSE_MODE_INVALID',
847
+ `Canonical fetch response mode ${JSON.stringify(responseMode)} is unsupported.`,
848
+ { effectId: String(effect.id), responseMode }
849
+ );
850
+ }
851
+ const projection = String(effect.projection || (responseMode === 'structured' ? 'text' : 'response'));
852
+ if (!['text', 'json', 'response'].includes(projection)) {
853
+ throw new CanonicalRuntimeError(
854
+ 'CanonicalEffectProtocolError',
855
+ 'PULSE_FETCH_PROJECTION_INVALID',
856
+ `Canonical fetch projection ${JSON.stringify(projection)} is unsupported.`,
857
+ { effectId: String(effect.id), responseMode, projection }
858
+ );
859
+ }
860
+ if ((responseMode === 'structured' && projection === 'response')
861
+ || (responseMode === 'opaque' && projection !== 'response')) {
862
+ throw new CanonicalRuntimeError(
863
+ 'CanonicalEffectProtocolError',
864
+ 'PULSE_FETCH_PROJECTION_MODE_CONFLICT',
865
+ `Canonical fetch projection ${JSON.stringify(projection)} conflicts with response mode ${JSON.stringify(responseMode)}.`,
866
+ { effectId: String(effect.id), responseMode, projection }
867
+ );
868
+ }
869
+ return Object.freeze({
870
+ id: String(effect.id),
871
+ kind: 'fetch',
872
+ parts: urlParts(effect.url),
873
+ init: normalizedInit(effect.init, schemaCodecs, {
874
+ ...options,
875
+ effectId: String(effect.id),
876
+ groupId: effect.groupKey === undefined ? null : String(effect.groupKey)
877
+ }),
878
+ responseMode,
879
+ projection,
880
+ ...(effect.groupKey === undefined ? {} : { groupKey: String(effect.groupKey) }),
881
+ source: effect.source
882
+ });
883
+ }
884
+
885
+
886
+ function normalizeProviderEffect(effect, schemaCodecs, options = {}) {
887
+ if (!effect || typeof effect !== 'object') {
888
+ throw new CanonicalRuntimeError('CanonicalEffectProtocolError', 'PULSE_CANONICAL_EFFECT_PROTOCOL', 'Canonical provider effect must be an object.', { effect });
889
+ }
890
+ if (portableKv.isConditionalKv(effect.kind)) return Object.freeze({ ...portableKv.admitConditionalKv(effect, options), id: effect.id, groupKey: effect.groupKey, source: effect.source });
891
+ if (effect.kind === 'fetch') return normalizeFetchEffect(effect, schemaCodecs, options);
892
+ const id = String(effect.id || '');
893
+ if (!id) throw new CanonicalRuntimeError('CanonicalEffectProtocolError', 'PULSE_CANONICAL_EFFECT_PROTOCOL', 'Canonical provider effect requires an id.', { effect });
894
+ if (effect.kind === 'config.get' || effect.kind === 'secret.get') {
895
+ return Object.freeze({ id, kind: effect.kind, name: String(effect.name), ...(effect.groupKey === undefined ? {} : { groupKey: String(effect.groupKey) }), source: effect.source });
896
+ }
897
+ if (effect.kind === 'kv.get' || effect.kind === 'kv.put') {
898
+ return Object.freeze({ id, kind: effect.kind, store: String(effect.store), key: String(effect.key), value: effect.kind === 'kv.put' ? effect.value : undefined, ...(effect.groupKey === undefined ? {} : { groupKey: String(effect.groupKey) }), source: effect.source });
899
+ }
900
+ if (effect.kind === 'event.emit') {
901
+ const emission = effect.emission;
902
+ if (!emission || typeof emission !== 'object' || Array.isArray(emission)) {
903
+ throw new CanonicalRuntimeError(
904
+ 'CanonicalEffectProtocolError',
905
+ 'PULSE_RUNTIME_EVENT_EMIT_INPUT_INVALID',
906
+ 'Canonical event.emit requires a data emission descriptor.',
907
+ { effectId: id }
908
+ );
909
+ }
910
+ const prototype = Object.getPrototypeOf(emission);
911
+ const symbols = Object.getOwnPropertySymbols(emission);
912
+ const unknown = Object.keys(emission).filter((key) => key !== 'schema' && key !== 'payload').sort();
913
+ if ((prototype !== Object.prototype && prototype !== null)
914
+ || symbols.length > 0
915
+ || unknown.length > 0
916
+ || !Object.prototype.hasOwnProperty.call(emission, 'schema')
917
+ || Object.keys(emission).some((key) => {
918
+ const descriptor = Object.getOwnPropertyDescriptor(emission, key);
919
+ return !descriptor || !Object.prototype.hasOwnProperty.call(descriptor, 'value');
920
+ })) {
921
+ throw new CanonicalRuntimeError(
922
+ 'CanonicalEffectProtocolError',
923
+ 'PULSE_RUNTIME_EVENT_EMIT_INPUT_INVALID',
924
+ 'Canonical event.emit requires exactly schema and the schema-dependent payload field.',
925
+ { effectId: id, unknown }
926
+ );
927
+ }
928
+ let frame = eventContract.normalizeEventFrame({
929
+ version: eventContract.EVENT_FRAME_VERSION,
930
+ type: effect.type,
931
+ schemaId: Object.getOwnPropertyDescriptor(emission, 'schema').value,
932
+ ...(Object.prototype.hasOwnProperty.call(emission, 'payload')
933
+ ? { payload: Object.getOwnPropertyDescriptor(emission, 'payload').value }
934
+ : {})
935
+ }, options.eventLimits);
936
+ if (frame.schemaId !== null) {
937
+ const payload = requireSchemaCodecs(schemaCodecs, frame.schemaId, 'event-emit')
938
+ .decode(frame.schemaId, frame.payload, 'event-emit');
939
+ frame = eventContract.normalizeEventFrame({ ...frame, payload }, options.eventLimits);
940
+ }
941
+ return Object.freeze({
942
+ id,
943
+ kind: 'event.emit',
944
+ providerKind: 'event',
945
+ operation: 'emit',
946
+ capability: 'event.emit',
947
+ frame,
948
+ ...(effect.groupKey === undefined ? {} : { groupKey: String(effect.groupKey) }),
949
+ source: effect.source
950
+ });
951
+ }
952
+ if (effect.package && effect.contractId && effect.operation) {
953
+ const invocation = effect.invocation && typeof effect.invocation === 'object'
954
+ ? effect.invocation
955
+ : {};
956
+ return Object.freeze({
957
+ id,
958
+ kind: String(effect.kind),
959
+ package: String(effect.package),
960
+ contractId: String(effect.contractId),
961
+ providerKind: String(effect.providerKind || 'package'),
962
+ operation: String(effect.operation),
963
+ capability: String(effect.capability || effect.kind),
964
+ result: String(effect.result || 'value'),
965
+ resource: Object.freeze({ ...(effect.resource || {}) }),
966
+ payload: Object.freeze({ ...(effect.payload || {}), ...invocation }),
967
+ ...(effect.groupKey === undefined ? {} : { groupKey: String(effect.groupKey) }),
968
+ source: effect.source
969
+ });
970
+ }
971
+ throw new CanonicalRuntimeError('ProviderCapabilityError', 'PULSE_PROVIDER_CAPABILITY_UNSUPPORTED', `Canonical provider effect ${String(effect.kind)} is unsupported.`, { effectId: id, kind: effect.kind });
972
+ }
973
+
974
+ function effectTraceStart(adapterId, executionId, normalized, sensitiveValues) {
975
+ const base = { type: 'effect-start', provider: adapterId, executionId, effectId: normalized.id, kind: normalized.kind, ...(normalized.groupKey === undefined ? {} : { groupKey: normalized.groupKey }) };
976
+ if (normalized.kind === 'fetch') return Object.freeze({
977
+ ...base,
978
+ url: redactString(normalized.parts.url, sensitiveValues),
979
+ method: normalized.init.method,
980
+ responseMode: normalized.responseMode,
981
+ projection: normalized.projection,
982
+ headers: redactedHeaders(normalized.init.headers, sensitiveValues)
983
+ });
984
+ if (normalized.kind === 'config.get' || normalized.kind === 'secret.get') return Object.freeze({ ...base, name: redactString(normalized.name, sensitiveValues) });
985
+ if (portableKv.isConditionalKv(normalized.kind)) return Object.freeze({ ...base, key: '<redacted>', generation: '<redacted>', value: '<redacted>' });
986
+ if (normalized.kind === 'kv.get' || normalized.kind === 'kv.put') return Object.freeze({ ...base, store: redactString(normalized.store, sensitiveValues), key: redactString(normalized.key, sensitiveValues) });
987
+ if (normalized.kind === 'event.emit') return Object.freeze({ ...base, eventType: redactString(normalized.frame.type, sensitiveValues), schemaId: normalized.frame.schemaId });
988
+ if (normalized.package) return Object.freeze({ ...base, package: normalized.package, contractId: normalized.contractId, operation: normalized.operation });
989
+ return Object.freeze(base);
990
+ }
991
+
992
+ function effectTraceResolved(adapterId, executionId, normalized, value) {
993
+ const base = { type: 'effect-resolved', provider: adapterId, executionId, effectId: normalized.id, kind: normalized.kind, ...(normalized.groupKey === undefined ? {} : { groupKey: normalized.groupKey }) };
994
+ if (normalized.kind === 'fetch') return Object.freeze({ ...base, status: value.status, bodyClass: value.bodyClass });
995
+ if (normalized.kind === 'secret.get') return Object.freeze({ ...base, hit: value !== undefined, value: '<redacted>' });
996
+ if (normalized.kind === 'config.get') return Object.freeze({ ...base, hit: value !== undefined });
997
+ if (portableKv.isConditionalKv(normalized.kind)) return Object.freeze({ ...base, status: value.status });
998
+ if (normalized.kind === 'kv.get') return Object.freeze({ ...base, hit: value !== undefined });
999
+ if (normalized.kind === 'kv.put') return Object.freeze({ ...base, stored: Boolean(value) });
1000
+ if (normalized.kind === 'event.emit') return Object.freeze({ ...base, accepted: value === undefined });
1001
+ if (normalized.package) return Object.freeze({ ...base, result: normalized.result, status: value && value.status, bodyClass: value && value.bodyClass });
1002
+ return Object.freeze(base);
1003
+ }
1004
+
1005
+ function createStructuredFetchResponse(snapshot, schemaCodecs, options = {}) {
1006
+ const schemaMemo = new Map();
1007
+ const headers = normalizeHeaders(snapshot.headers);
1008
+ const body = runtimeApi.body.createValue(
1009
+ { body: snapshot.body, headers },
1010
+ { maxBytes: options.maxBytes, forceStructured: true }
1011
+ );
1012
+ function text() { return unwrap(body.text()); }
1013
+ const responseStatus = Number(snapshot.status);
1014
+ const traceStatus = Number.isSafeInteger(responseStatus) && responseStatus >= 100 && responseStatus <= 599
1015
+ ? responseStatus
1016
+ : null;
1017
+ return Object.freeze({
1018
+ status: Number(snapshot.status || 0),
1019
+ ok: Number(snapshot.status || 0) >= 200 && Number(snapshot.status || 0) < 300,
1020
+ kind: String(snapshot.kind || 'text'),
1021
+ bodyClass: 'structured',
1022
+ get body() { return text(); },
1023
+ headers,
1024
+ header(name) { return headerValue(headers, name); },
1025
+ text,
1026
+ json(schemaId) {
1027
+ if (schemaId !== undefined) {
1028
+ const key = requireSchemaId(schemaCodecs, schemaId, 'fetch-response');
1029
+ if (!schemaMemo.has(key)) {
1030
+ const event = {
1031
+ boundary: 'fetch-response',
1032
+ schemaId: key,
1033
+ responseCaseId: null,
1034
+ operationId: `fetch-response:${options.effectId || key}`,
1035
+ status: traceStatus,
1036
+ contentType: headerValue(headers, 'content-type') || null,
1037
+ headers,
1038
+ bodyOwnership: 'fetched-response-snapshot',
1039
+ target: options.target || null,
1040
+ provider: options.provider || null,
1041
+ effectId: options.effectId || null,
1042
+ groupId: options.groupId || null
1043
+ };
1044
+ try {
1045
+ const value = text();
1046
+ assertSchemaJsonContentType(schemaCodecs, headers, key, 'fetch-response');
1047
+ assertSchemaBodySize(schemaCodecs, value, key, 'fetch-response');
1048
+ const decoded = schemaCodecs.decodeJsonText(key, value, 'fetch-response');
1049
+ schemaMemo.set(key, decoded);
1050
+ jsonTrace(options.trace, { ...event, kind: 'json.decode.fetch' }, decoded);
1051
+ } catch (error) {
1052
+ jsonTrace(options.trace, { ...event, kind: 'json.decode.error', ...jsonTraceErrorDetail(error) });
1053
+ throw error;
1054
+ }
1055
+ }
1056
+ return schemaMemo.get(key);
1057
+ }
1058
+ if (strictSchemaPolicy(options, schemaCodecs)) {
1059
+ throw new CanonicalRuntimeError(
1060
+ 'SchemaReferenceError',
1061
+ 'PULSE_SCHEMA_REQUIRED',
1062
+ 'Strict fetched-response JSON requires a registered schema ID.',
1063
+ { source: 'fetch-response', strict: true }
1064
+ );
1065
+ }
1066
+ return unwrap(body.json());
1067
+ },
1068
+ bodyStats() { return body.stats(); },
1069
+ toJSON() { return { status: Number(snapshot.status || 0), ok: this.ok, kind: this.kind, bodyClass: 'structured', headers, body: text() }; }
1070
+ });
1071
+ }
1072
+
1073
+ function opaqueInspectionError(handle, helper) {
1074
+ return new CanonicalRuntimeError('OpaqueBodyInspectionError', 'PULSE_OPAQUE_BODY_INSPECTION', `Opaque ${handle.bodyKind || 'stream'} response bodies are pass-through only and cannot be inspected with ${helper}().`, { helper, handleId: handle.handleId, bodyKind: handle.bodyKind, bodyCopiedIntoWasm: false });
1075
+ }
1076
+
1077
+ function createOpaqueFetchResponse(snapshot, effectId, providerSource = 'canonical-provider') {
1078
+ const bodyHandle = runtimeApi.opaque.createBodyHandle({
1079
+ id: snapshot.bodyHandle && snapshot.bodyHandle.handleId || `${effectId}-body`,
1080
+ bodyKind: snapshot.bodyHandle && snapshot.bodyHandle.bodyKind || 'stream',
1081
+ headers: snapshot.headers,
1082
+ status: snapshot.status,
1083
+ source: String(snapshot.providerSource || snapshot.source || providerSource),
1084
+ responseRef: snapshot.responseRef,
1085
+ streamRef: snapshot.streamRef
1086
+ });
1087
+ const headers = normalizeHeaders(snapshot.headers);
1088
+ return Object.freeze({
1089
+ status: Number(snapshot.status || 0),
1090
+ ok: Number(snapshot.status || 0) >= 200 && Number(snapshot.status || 0) < 300,
1091
+ kind: 'stream',
1092
+ bodyClass: 'opaque',
1093
+ headers,
1094
+ header(name) { return headerValue(headers, name); },
1095
+ bodyHandle,
1096
+ bodyStream: snapshot.bodyStream,
1097
+ responseRef: snapshot.responseRef,
1098
+ streamRef: snapshot.streamRef,
1099
+ hostOwnsStream: true,
1100
+ wasmOwnsBytes: false,
1101
+ streamResultAbiVersion: streamingContract.STREAM_RESULT_ABI_VERSION,
1102
+ json() { throw opaqueInspectionError(bodyHandle, 'json'); },
1103
+ text() { throw opaqueInspectionError(bodyHandle, 'text'); },
1104
+ bytes() { throw opaqueInspectionError(bodyHandle, 'bytes'); },
1105
+ toJSON() { return { status: this.status, ok: this.ok, kind: 'stream', bodyClass: 'opaque', headers, bodyHandle, responseRef: this.responseRef, streamRef: this.streamRef, hostOwnsStream: true, wasmOwnsBytes: false }; }
1106
+ });
1107
+ }
1108
+
1109
+ function responseSnapshot(handle) {
1110
+ if (!handle || typeof handle !== 'object') return handle;
1111
+ if (typeof handle.toJSON !== 'function') return handle;
1112
+ const snapshot = handle.toJSON();
1113
+ return Object.freeze({
1114
+ ...snapshot,
1115
+ bodyStream: handle.bodyStream === undefined ? snapshot.bodyStream : handle.bodyStream,
1116
+ body: handle.body === undefined ? snapshot.body : handle.body,
1117
+ responseRef: handle.responseRef === undefined ? snapshot.responseRef : handle.responseRef,
1118
+ streamRef: handle.streamRef === undefined ? snapshot.streamRef : handle.streamRef
1119
+ });
1120
+ }
1121
+
1122
+ function opaqueSnapshotFromStructured(snapshot) {
1123
+ const status = Number(snapshot.status || 0);
1124
+ const allowsBody = status !== 204 && status !== 205 && status !== 304 && !(status >= 100 && status < 200);
1125
+ const body = allowsBody && snapshot.body !== undefined && snapshot.body !== null ? snapshot.body : undefined;
1126
+ return Object.freeze({
1127
+ ...snapshot,
1128
+ kind: 'stream',
1129
+ bodyClass: 'opaque',
1130
+ body: undefined,
1131
+ bodyStream: snapshot.bodyStream === undefined
1132
+ ? (body === undefined ? undefined : Object.freeze({ chunks: Object.freeze([body]) }))
1133
+ : snapshot.bodyStream
1134
+ });
1135
+ }
1136
+
1137
+ function bodyStreamChunks(snapshot) {
1138
+ const stream = snapshot && snapshot.bodyStream;
1139
+ if (stream && Array.isArray(stream.chunks)) return stream.chunks;
1140
+ if (Array.isArray(snapshot && snapshot.chunks)) return snapshot.chunks;
1141
+ if (typeof stream === 'string' || Buffer.isBuffer(stream) || stream instanceof Uint8Array) return [stream];
1142
+ return undefined;
1143
+ }
1144
+
1145
+ function structuredSnapshotFromOpaque(snapshot, effectId, maxBytes) {
1146
+ const chunks = bodyStreamChunks(snapshot);
1147
+ const status = Number(snapshot.status || 0);
1148
+ const allowsBody = status !== 204 && status !== 205 && status !== 304 && !(status >= 100 && status < 200);
1149
+ if (!allowsBody) return Object.freeze({ ...snapshot, kind: 'text', bodyClass: 'structured', body: '' });
1150
+ if (!chunks) {
1151
+ throw new CanonicalRuntimeError(
1152
+ 'OpaqueBodyInspectionError',
1153
+ 'PULSE_OPAQUE_BODY_INSPECTION',
1154
+ 'The provider returned a pass-through-only response that cannot satisfy an explicit structured fetch projection.',
1155
+ { effectId, bodyCopiedIntoWasm: false }
1156
+ );
1157
+ }
1158
+ const buffers = chunks.map((chunk) => Buffer.isBuffer(chunk)
1159
+ ? chunk
1160
+ : chunk instanceof Uint8Array
1161
+ ? Buffer.from(chunk.buffer, chunk.byteOffset, chunk.byteLength)
1162
+ : Buffer.from(String(chunk)));
1163
+ const bytes = buffers.reduce((total, chunk) => total + chunk.byteLength, 0);
1164
+ const limit = Number.isSafeInteger(Number(maxBytes)) && Number(maxBytes) > 0 ? Number(maxBytes) : 65_536;
1165
+ if (bytes > limit) {
1166
+ throw new CanonicalRuntimeError(
1167
+ 'BodyTooLargeError',
1168
+ 'PULSE_BODY_TOO_LARGE',
1169
+ `Fetched response exceeds the ${limit} byte structured-body limit.`,
1170
+ { effectId, bytes, maxBytes: limit }
1171
+ );
1172
+ }
1173
+ return Object.freeze({
1174
+ ...snapshot,
1175
+ kind: 'text',
1176
+ bodyClass: 'structured',
1177
+ body: Buffer.concat(buffers).toString('utf8'),
1178
+ bodyStream: undefined,
1179
+ bodyHandle: undefined
1180
+ });
1181
+ }
1182
+
1183
+ function normalizeFetchResponse(handle, effectId, providerSource, schemaCodecs, options = {}) {
1184
+ const responseMode = options.responseMode || 'auto';
1185
+ if (responseMode === 'structured' && handle && handle.bodyClass === 'structured' && typeof handle.text === 'function') return handle;
1186
+ if (responseMode === 'opaque' && handle && handle.bodyClass === 'opaque' && handle.bodyHandle) return handle;
1187
+ const snapshot = responseSnapshot(handle);
1188
+ if (!snapshot || typeof snapshot !== 'object') throw new CanonicalRuntimeError('FetchNetworkError', 'PULSE_FETCH_NETWORK', 'Provider returned no fetch response.', { effectId });
1189
+ const opaque = snapshot.kind === 'stream' || snapshot.bodyClass === 'opaque' || snapshot.bodyStream !== undefined || snapshot.bodyHandle !== undefined;
1190
+ if (responseMode === 'opaque') {
1191
+ return createOpaqueFetchResponse(opaque ? snapshot : opaqueSnapshotFromStructured(snapshot), effectId, providerSource);
1192
+ }
1193
+ if (responseMode === 'structured') {
1194
+ const structured = opaque
1195
+ ? structuredSnapshotFromOpaque(snapshot, effectId, options.maxBytes)
1196
+ : snapshot;
1197
+ return createStructuredFetchResponse(structured, schemaCodecs, options);
1198
+ }
1199
+ return opaque
1200
+ ? createOpaqueFetchResponse(snapshot, effectId, providerSource)
1201
+ : createStructuredFetchResponse(snapshot, schemaCodecs, options);
1202
+ }
1203
+
1204
+ function responseAllowsBody(status, requestMethod = 'GET') {
1205
+ const normalizedStatus = Number(status);
1206
+ return String(requestMethod || 'GET').toUpperCase() !== 'HEAD'
1207
+ && normalizedStatus !== 204
1208
+ && normalizedStatus !== 205
1209
+ && normalizedStatus !== 304
1210
+ && !(normalizedStatus >= 100 && normalizedStatus < 200);
1211
+ }
1212
+
1213
+ function releaseDiscardedStream(stream) {
1214
+ if (!stream) return;
1215
+ try {
1216
+ if (typeof stream.destroy === 'function') stream.destroy();
1217
+ else if (typeof stream.cancel === 'function') Promise.resolve(stream.cancel()).catch(() => undefined);
1218
+ } catch (_) { /* bodyless response cleanup is best effort */ }
1219
+ }
1220
+
1221
+ function finalResponse(value, requestMethod = 'GET') {
1222
+ if (value === undefined || value === null) return Object.freeze({ status: 204, kind: 'empty', bodyClass: 'structured', headers: [], body: '' });
1223
+ if (value && value.bodyClass === 'opaque') {
1224
+ const allowsBody = responseAllowsBody(value.status, requestMethod);
1225
+ if (!allowsBody) releaseDiscardedStream(value.bodyStream);
1226
+ const detached = runtimeApi.opaque.detach(value.bodyHandle, 'terminal-response-handoff');
1227
+ return Object.freeze({
1228
+ status: Number(value.status || 200),
1229
+ kind: 'stream',
1230
+ bodyClass: 'opaque',
1231
+ headers: normalizeHeaders(value.headers),
1232
+ bodyHandle: detached,
1233
+ bodyStream: allowsBody ? value.bodyStream : undefined,
1234
+ responseRef: value.responseRef,
1235
+ streamRef: value.streamRef,
1236
+ hostOwnsStream: true,
1237
+ wasmOwnsBytes: false,
1238
+ streamResultAbiVersion: streamingContract.STREAM_RESULT_ABI_VERSION
1239
+ });
1240
+ }
1241
+ if (value && value.bodyClass === 'structured' && Object.prototype.hasOwnProperty.call(value, 'status')) {
1242
+ const body = responseAllowsBody(value.status, requestMethod)
1243
+ ? (Object.prototype.hasOwnProperty.call(value, 'body') ? value.body : (typeof value.text === 'function' ? value.text() : ''))
1244
+ : '';
1245
+ return Object.freeze({
1246
+ status: Number(value.status || 200),
1247
+ kind: String(value.kind || 'text'),
1248
+ bodyClass: 'structured',
1249
+ headers: normalizeHeaders(value.headers),
1250
+ body: body === undefined || body === null ? '' : String(body)
1251
+ });
1252
+ }
1253
+ if (value && typeof value === 'object' && Object.prototype.hasOwnProperty.call(value, 'status')) {
1254
+ const allowsBody = responseAllowsBody(value.status, requestMethod);
1255
+ if (!allowsBody) releaseDiscardedStream(value.bodyStream);
1256
+ return Object.freeze({
1257
+ ...value,
1258
+ headers: normalizeHeaders(value.headers),
1259
+ body: allowsBody ? value.body : '',
1260
+ bodyStream: allowsBody ? value.bodyStream : undefined
1261
+ });
1262
+ }
1263
+ throw new CanonicalRuntimeError('ResponseEncodeError', 'PULSE_RESPONSE_ENCODE', 'Canonical handler returned a non-response value. Use ctx.json, ctx.text, ctx.response, or return ctx.fetch for opaque pass-through.', { valueType: typeof value });
1264
+ }
1265
+
1266
+ function responseIsStructured(method, headers) {
1267
+ if (String(method).toUpperCase() === 'HEAD') return true;
1268
+ const contentType = String(headerValue(headers, 'content-type') || '').toLowerCase();
1269
+ return contentType.startsWith('text/')
1270
+ || contentType.includes('application/json')
1271
+ || contentType.includes('+json')
1272
+ || contentType.includes('application/xml')
1273
+ || contentType.includes('+xml')
1274
+ || contentType.includes('application/x-www-form-urlencoded');
1275
+ }
1276
+
1277
+ function normalizeProviderAdapter(input = {}) {
1278
+ if (!input || typeof input !== 'object') throw new TypeError('Canonical host runtime requires a provider adapter.');
1279
+ const id = String(input.id || input.name || '').trim();
1280
+ if (!id) throw new TypeError('Canonical provider adapter requires an id.');
1281
+ const dispatchEffect = typeof input.dispatchEffect === 'function'
1282
+ ? input.dispatchEffect
1283
+ : (typeof input.dispatchFetch === 'function'
1284
+ ? (effect, context) => {
1285
+ if (effect.kind !== 'fetch') throw new CanonicalRuntimeError('ProviderCapabilityError', 'PULSE_PROVIDER_CAPABILITY_UNSUPPORTED', `Canonical provider ${id} does not implement ${effect.kind}.`, { provider: id, kind: effect.kind, effectId: effect.id });
1286
+ return input.dispatchFetch(effect, context);
1287
+ }
1288
+ : undefined);
1289
+ if (!dispatchEffect) throw new TypeError(`Canonical provider ${id} must implement dispatchEffect() or dispatchFetch().`);
1290
+ return Object.freeze({
1291
+ id,
1292
+ version: String(input.version || `pulse.canonical-${id}-provider.v1`),
1293
+ createCapabilities: typeof input.createCapabilities === 'function' ? input.createCapabilities : () => ({}),
1294
+ dispatchEffect,
1295
+ prepareConditionalKv: typeof input.prepareConditionalKv === 'function' ? input.prepareConditionalKv.bind(input) : undefined,
1296
+ resultMetadata: typeof input.resultMetadata === 'function' ? input.resultMetadata : () => undefined,
1297
+ disposeExecution: typeof input.disposeExecution === 'function' ? input.disposeExecution : () => undefined
1298
+ });
1299
+ }
1300
+
1301
+ function createCanonicalHostRuntime(options = {}) {
1302
+ const adapter = normalizeProviderAdapter(options.providerAdapter || options.provider);
1303
+ const runtimeTrace = [];
1304
+ const registry = options.continuationRegistry || createContinuationRegistry({ ttlMs: options.continuationTtlMs, clock: options.clock });
1305
+
1306
+ async function execute(programModule, executionOptions = {}) {
1307
+ if (!programModule || typeof programModule.createHandler !== 'function') throw new TypeError('Canonical runtime requires a compiled canonical module.');
1308
+ const metadata = assertCanonicalProgramCompatibility(programModule);
1309
+ executionSequence += 1;
1310
+ const executionId = String(executionOptions.executionId || `canonical-${adapter.id}-${executionSequence}`);
1311
+ const sensitiveValues = initialSensitiveValues({ ...options, ...executionOptions });
1312
+ let executionStatus = 'failed';
1313
+ let executionFailure;
1314
+ try {
1315
+ const trace = [];
1316
+ function recordTrace(...events) {
1317
+ for (const entry of events) {
1318
+ const safeEntry = redactRuntimeValue(entry, sensitiveValues);
1319
+ trace.push(safeEntry);
1320
+ runtimeTrace.push(safeEntry);
1321
+ }
1322
+ return trace.length;
1323
+ }
1324
+ const traceSink = Object.freeze({
1325
+ push: recordTrace,
1326
+ json(input, value) {
1327
+ const safeValue = value === undefined
1328
+ ? undefined
1329
+ : redactRuntimeValue(value, sensitiveValues);
1330
+ const safeHeaders = input.headers === undefined
1331
+ ? undefined
1332
+ : redactRuntimeValue(input.headers, sensitiveValues);
1333
+ recordTrace(jsonSemanticTrace.normalizeJsonTraceEvent({
1334
+ ...input,
1335
+ ...(safeHeaders === undefined ? {} : { headers: safeHeaders }),
1336
+ valueDigest: safeValue === undefined
1337
+ ? null
1338
+ : jsonSemanticTrace.semanticValueDigest(safeValue)
1339
+ }));
1340
+ }
1341
+ });
1342
+ const providerCapabilities = adapter.createCapabilities({ ...options, ...executionOptions, metadata, executionId, trace: traceSink });
1343
+ const schemaCodecs = programModule.schemaCodecs;
1344
+ const runtimeOptions = {
1345
+ ...options,
1346
+ ...executionOptions,
1347
+ strict: executionOptions.strict === undefined
1348
+ ? (metadata.json ? metadata.json.strict === true : options.strict === true)
1349
+ : executionOptions.strict === true,
1350
+ schemaCodecs,
1351
+ trace: traceSink,
1352
+ target: executionOptions.target || metadata.target || options.target || 'javascript',
1353
+ provider: adapter.id,
1354
+ reporting: executionOptions.reporting || options.reporting || loggingContract.DEFAULT_REPORTING_LEVEL,
1355
+ redactLogMessage(message) { return redactString(message, sensitiveValues); },
1356
+ capabilities: executionOptions.capabilities || providerCapabilities
1357
+ };
1358
+ const context = createCanonicalContext(runtimeOptions, traceSink);
1359
+ const resolutionOrder = [];
1360
+ let dispatchedEffects = 0;
1361
+
1362
+ function executionContinuations() {
1363
+ const prefix = `${executionId}:`;
1364
+ return registry.list().filter((entry) => entry.id.startsWith(prefix));
1365
+ }
1366
+
1367
+ async function dispatchOne(effect) {
1368
+ const normalized = normalizeProviderEffect(effect, schemaCodecs, runtimeOptions);
1369
+ const conditional = portableKv.isConditionalKv(normalized.kind);
1370
+ if (conditional) portableKv.registerKvRedactions(normalized, (value) => sensitiveValues.add(value));
1371
+ recordTrace(effectTraceStart(adapter.id, executionId, normalized, sensitiveValues));
1372
+ dispatchedEffects += 1;
1373
+ try {
1374
+ const effectExecution = { ...options, ...executionOptions, metadata, executionId, trace: traceSink, registerRedactionValue: (value) => sensitiveValues.add(value), onKvObservation: recordTrace };
1375
+ let snapshot = conditional
1376
+ ? await portableKv.executeConditionalKv(normalized, adapter.prepareConditionalKv || ((_admitted, kvExecution) => () => adapter.dispatchEffect(normalized, kvExecution)), effectExecution, runtimeOptions)
1377
+ : await adapter.dispatchEffect(normalized, effectExecution);
1378
+ if (normalized.kind === 'config.get' || normalized.kind === 'secret.get') {
1379
+ if (snapshot !== undefined && typeof snapshot !== 'string') {
1380
+ throw new CanonicalRuntimeError(
1381
+ 'BindingValueError',
1382
+ 'PULSE_BINDING_VALUE_INVALID',
1383
+ `Pulse ${normalized.kind} must resolve to a string or undefined.`,
1384
+ { capability: normalized.kind, name: normalized.name, receivedType: Array.isArray(snapshot) ? 'array' : snapshot === null ? 'null' : typeof snapshot }
1385
+ );
1386
+ }
1387
+ if (normalized.kind === 'secret.get' && typeof snapshot === 'string') sensitiveValues.add(snapshot);
1388
+ }
1389
+ if (normalized.kind === 'kv.get') snapshot = runtimeApi.body.immutableClone(snapshot);
1390
+ if (normalized.kind === 'kv.put' && typeof snapshot !== 'boolean') {
1391
+ throw new CanonicalRuntimeError(
1392
+ 'KvAcknowledgementError',
1393
+ 'PULSE_KV_ACK_INVALID',
1394
+ 'Pulse KV put providers must resolve to a boolean acknowledgement.',
1395
+ { receivedType: Array.isArray(snapshot) ? 'array' : snapshot === null ? 'null' : typeof snapshot }
1396
+ );
1397
+ }
1398
+ if (normalized.kind === 'event.emit' && snapshot !== undefined) {
1399
+ throw new CanonicalRuntimeError(
1400
+ 'EventAcceptanceError',
1401
+ 'PULSE_RUNTIME_EVENT_EMIT_ACCEPTANCE_INVALID',
1402
+ 'Pulse event emit providers must acknowledge host acceptance with undefined.',
1403
+ { receivedType: Array.isArray(snapshot) ? 'array' : snapshot === null ? 'null' : typeof snapshot }
1404
+ );
1405
+ }
1406
+ const value = normalized.kind === 'fetch' || normalized.result === 'opaque-response'
1407
+ ? normalizeFetchResponse(snapshot, effect.id, adapter.id, schemaCodecs, {
1408
+ maxBytes: executionOptions.maxFetchBodyBytes
1409
+ || executionOptions.maxStructuredBodyBytes
1410
+ || executionOptions.maxBodyBytes
1411
+ || options.maxFetchBodyBytes
1412
+ || options.maxStructuredBodyBytes
1413
+ || options.maxBodyBytes,
1414
+ strict: runtimeOptions.strict,
1415
+ responseMode: normalized.kind === 'fetch' ? normalized.responseMode : normalized.result === 'opaque-response' ? 'opaque' : 'auto',
1416
+ trace: traceSink,
1417
+ target: runtimeOptions.target,
1418
+ provider: adapter.id,
1419
+ effectId: normalized.id,
1420
+ groupId: normalized.groupKey
1421
+ })
1422
+ : snapshot;
1423
+ resolutionOrder.push(effect.id);
1424
+ recordTrace(effectTraceResolved(adapter.id, executionId, normalized, value));
1425
+ return value;
1426
+ } catch (error) {
1427
+ recordTrace(Object.freeze({ type: 'effect-failed', provider: adapter.id, executionId, effectId: effect.id, kind: normalized.kind, error: error.name || 'Error', code: error.code }));
1428
+ throw redactRuntimeError(error, sensitiveValues);
1429
+ }
1430
+ }
1431
+
1432
+ async function dispatchGroup(effects) {
1433
+ const settled = await Promise.allSettled(effects.map(dispatchOne));
1434
+ const failures = settled
1435
+ .map((entry, index) => entry.status === 'rejected' ? Object.freeze({
1436
+ index,
1437
+ effectId: effects[index].id,
1438
+ kind: effects[index].kind,
1439
+ groupKey: effects[index].groupKey,
1440
+ error: entry.reason
1441
+ }) : undefined)
1442
+ .filter(Boolean);
1443
+ if (failures.length > 0) {
1444
+ const rawPrimary = failures[0].error;
1445
+ const primary = rawPrimary instanceof Error ? rawPrimary : new Error(String(rawPrimary));
1446
+ const keyed = failures.every((entry) => entry.groupKey !== undefined);
1447
+ primary.effectFailures = Object.freeze(failures.map((entry) => Object.freeze(keyed
1448
+ ? {
1449
+ key: String(entry.groupKey),
1450
+ index: entry.index,
1451
+ effectId: entry.effectId,
1452
+ kind: entry.kind,
1453
+ name: entry.error && entry.error.name,
1454
+ code: entry.error && entry.error.code
1455
+ }
1456
+ : { effectId: entry.effectId, name: entry.error && entry.error.name, code: entry.error && entry.error.code })));
1457
+ throw primary;
1458
+ }
1459
+ return settled.map((entry) => entry.value);
1460
+ }
1461
+
1462
+ const pulse = Object.freeze({
1463
+ effect(effect, continuationId) { return Object.freeze({ protocol: canonicalRuntimeContract.CANONICAL_RUNTIME_PROTOCOL_VERSION, kind: 'effect', continuationId: String(continuationId), effect }); },
1464
+ group(effects, continuationId) { return Object.freeze({ protocol: canonicalRuntimeContract.CANONICAL_RUNTIME_PROTOCOL_VERSION, kind: 'group', continuationId: String(continuationId), effects: Object.freeze([...effects]) }); }
1465
+ });
1466
+
1467
+ const generator = programModule.createHandler()(context.ctx, pulse);
1468
+ if (!generator || typeof generator.next !== 'function') throw new CanonicalRuntimeError('CanonicalHandlerProtocolError', 'PULSE_CANONICAL_HANDLER_PROTOCOL', 'Compiled canonical handler did not return a generator.');
1469
+ let step;
1470
+ try { step = generator.next(); }
1471
+ catch (error) { error.execution = { executionId, trace: [...trace], continuations: executionContinuations() }; throw error; }
1472
+
1473
+ while (!step.done) {
1474
+ const marker = step.value;
1475
+ if (!marker || marker.protocol !== canonicalRuntimeContract.CANONICAL_RUNTIME_PROTOCOL_VERSION || !canonicalRuntimeContract.CANONICAL_EFFECT_MARKER_KINDS.includes(marker.kind)) throw new CanonicalRuntimeError('CanonicalEffectProtocolError', 'PULSE_CANONICAL_EFFECT_PROTOCOL', 'Canonical handler yielded an unsupported runtime marker.', { marker });
1476
+ const effects = marker.kind === 'group' ? marker.effects : [marker.effect];
1477
+ const continuationId = `${executionId}:${marker.continuationId}`;
1478
+ registry.create({ id: continuationId, branchPoint: marker.continuationId, effectIds: effects.map((effect) => effect.id), ttlMs: executionOptions.continuationTtlMs || options.continuationTtlMs });
1479
+ registry.wait(continuationId);
1480
+ recordTrace(Object.freeze({ type: 'continuation-waiting', provider: adapter.id, executionId, continuationId, effectIds: effects.map((effect) => effect.id) }));
1481
+ let value;
1482
+ try {
1483
+ value = marker.kind === 'group' ? await dispatchGroup(effects) : await dispatchOne(effects[0]);
1484
+ registry.resume(continuationId);
1485
+ recordTrace(Object.freeze({ type: 'continuation-resumed', provider: adapter.id, executionId, continuationId, branchPoint: marker.continuationId }));
1486
+ step = generator.next(value);
1487
+ registry.complete(continuationId);
1488
+ } catch (error) {
1489
+ const current = registry.get(continuationId);
1490
+ if (!['failed', 'expired', 'completed', 'cancelled'].includes(current.state)) {
1491
+ try { registry.fail(continuationId, error); } catch (_) { /* preserve primary failure */ }
1492
+ }
1493
+ recordTrace(Object.freeze({ type: 'continuation-failed', provider: adapter.id, executionId, continuationId, error: error.name || 'Error' }));
1494
+ error.execution = Object.freeze({ executionId, trace: Object.freeze([...trace]), continuations: Object.freeze(executionContinuations()), resolutionOrder: Object.freeze([...resolutionOrder]) });
1495
+ throw error;
1496
+ }
1497
+ }
1498
+
1499
+ let response;
1500
+ try { response = finalResponse(step.value, context.request.method); }
1501
+ catch (error) {
1502
+ error.execution = Object.freeze({ executionId, trace: Object.freeze([...trace]), continuations: Object.freeze(executionContinuations()), resolutionOrder: Object.freeze([...resolutionOrder]) });
1503
+ throw error;
1504
+ }
1505
+ recordTrace(Object.freeze({ type: 'execution-completed', provider: adapter.id, executionId, status: response.status, bodyClass: response.bodyClass }));
1506
+ executionStatus = 'completed';
1507
+ return Object.freeze({
1508
+ status: 'completed',
1509
+ version: String(options.runtimeVersion || CANONICAL_HOST_RUNTIME_VERSION),
1510
+ protocol: canonicalRuntimeContract.CANONICAL_RUNTIME_PROTOCOL_VERSION,
1511
+ provider: adapter.id,
1512
+ providerVersion: adapter.version,
1513
+ providerMetadata: adapter.resultMetadata({ ...options, ...executionOptions, metadata, executionId }),
1514
+ executionId,
1515
+ metadata,
1516
+ response,
1517
+ requestBodyStats: context.requestBody.stats(),
1518
+ effectCount: dispatchedEffects,
1519
+ resolutionOrder: Object.freeze([...resolutionOrder]),
1520
+ continuations: Object.freeze(executionContinuations()),
1521
+ trace: Object.freeze([...trace])
1522
+ });
1523
+ } catch (error) {
1524
+ executionFailure = redactRuntimeError(error, sensitiveValues);
1525
+ throw executionFailure;
1526
+ } finally {
1527
+ try { adapter.disposeExecution(Object.freeze({ executionId, metadata, status: executionStatus, error: executionFailure })); }
1528
+ catch (_) { /* provider cleanup must not mask execution results */ }
1529
+ }
1530
+ }
1531
+
1532
+ return Object.freeze({
1533
+ version: String(options.runtimeVersion || CANONICAL_HOST_RUNTIME_VERSION),
1534
+ protocol: canonicalRuntimeContract.CANONICAL_RUNTIME_PROTOCOL_VERSION,
1535
+ provider: adapter.id,
1536
+ execute,
1537
+ registry,
1538
+ trace() { return Object.freeze([...runtimeTrace]); }
1539
+ });
1540
+ }
1541
+
1542
+ async function executeCanonicalProgram(programModule, options = {}) {
1543
+ return createCanonicalHostRuntime(options).execute(programModule, options);
1544
+ }
1545
+
1546
+ module.exports = {
1547
+ CANONICAL_HOST_RUNTIME_VERSION,
1548
+ CANONICAL_RUNTIME_PROTOCOL_VERSION: canonicalRuntimeContract.CANONICAL_RUNTIME_PROTOCOL_VERSION,
1549
+ CANONICAL_EFFECT_PROTOCOL_VERSION: canonicalRuntimeContract.CANONICAL_RUNTIME_PROTOCOL_VERSION,
1550
+ CanonicalRuntimeError,
1551
+ assertCanonicalProgramCompatibility,
1552
+ createCanonicalHostRuntime,
1553
+ executeCanonicalProgram,
1554
+ createCanonicalContext,
1555
+ normalizeFetchResponse,
1556
+ normalizeFetchEffect,
1557
+ normalizeProviderEffect,
1558
+ normalizedInit,
1559
+ urlParts,
1560
+ finalResponse,
1561
+ redactedHeaders,
1562
+ redactRuntimeError,
1563
+ redactRuntimeValue,
1564
+ responseIsStructured,
1565
+ createStructuredFetchResponse,
1566
+ createOpaqueFetchResponse,
1567
+ normalizeHeaders,
1568
+ headerValue
1569
+ };