@pulse-compute/wasm-host-runtime 0.0.0 → 1.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -1
- package/package.json +44 -6
- package/src/compiler/backend-capabilities.js +372 -0
- package/src/compiler/channel-broadcaster.js +194 -0
- package/src/compiler/compiled-wasm-runtime.js +612 -0
- package/src/compiler/host-runtime-kernel.js +690 -0
- package/src/compiler/index.js +33 -0
- package/src/compiler/json-body.js +454 -0
- package/src/compiler/request-result-headers.js +153 -0
- package/src/compiler/streaming-passthrough.js +212 -0
- package/src/compiler/wasm-host-abi.js +314 -0
- package/src/compiler/wasm-host-bridge.js +984 -0
- package/src/diagnostics.js +3 -0
- package/src/index.js +11 -0
- package/src/runtime/canonical-api-runtime.js +1559 -0
- package/src/runtime/canonical-native-host.js +1173 -0
- package/src/runtime/compiled-wasm-host-runtime-kv.js +32 -0
- package/src/runtime/continuation-registry.js +181 -0
- package/src/runtime/index.js +22 -0
- package/src/runtime/kv-provider.js +56 -0
- package/src/runtime/native-crypto-verifier.js +330 -0
|
@@ -0,0 +1,690 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const os = require('node:os');
|
|
5
|
+
const path = require('node:path');
|
|
6
|
+
const { getDefaultArtifactsDir } = require('@pulse-compute/wasm-build-support/artifacts-dir');
|
|
7
|
+
const crypto = require('node:crypto');
|
|
8
|
+
const { PACKAGE_VERSION, normalizeArtifact, normalizeDiagnostic } = require('@pulse-compute/wasm-contracts/diagnostics');
|
|
9
|
+
|
|
10
|
+
const HOST_RUNTIME_KERNEL_VERSION = 'pulsewasm.host-runtime-kernel.v2';
|
|
11
|
+
const HOST_RUNTIME_KERNEL_PHASE = '12D';
|
|
12
|
+
const HOST_RUNTIME_FILE = 'generated/host/host-runtime-kernel.cjs';
|
|
13
|
+
|
|
14
|
+
function sha256Buffer(buffer) { return crypto.createHash('sha256').update(buffer).digest('hex'); }
|
|
15
|
+
function sha256Text(text) { return crypto.createHash('sha256').update(text, 'utf8').digest('hex'); }
|
|
16
|
+
|
|
17
|
+
function diagnosticFromParts(parts) {
|
|
18
|
+
return normalizeDiagnostic({ severity: 'error', loc: { file: '<generated>' }, ...parts });
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function fileRecord(filePath, cwd, kind) {
|
|
22
|
+
const buffer = fs.readFileSync(filePath);
|
|
23
|
+
return { file: path.relative(cwd, filePath).replace(/\\/g, '/'), kind, bytes: buffer.length, sha256: sha256Buffer(buffer) };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function buildHostRuntimeKernelSource() {
|
|
27
|
+
return `/*
|
|
28
|
+
* Generated by PulseWasm Phase 12D.
|
|
29
|
+
* Platform-neutral Wasm host runtime kernel with stream/pass-through result support.
|
|
30
|
+
* Do not edit by hand.
|
|
31
|
+
*/
|
|
32
|
+
'use strict';
|
|
33
|
+
|
|
34
|
+
const fs = require('node:fs');
|
|
35
|
+
const path = require('node:path');
|
|
36
|
+
|
|
37
|
+
const RESULT_NONE = 0;
|
|
38
|
+
const RESULT_TEXT = 1;
|
|
39
|
+
const RESULT_JSON = 2;
|
|
40
|
+
const RESULT_BINARY = 3;
|
|
41
|
+
const RESULT_EMPTY = 4;
|
|
42
|
+
const RESULT_STREAM = 5;
|
|
43
|
+
|
|
44
|
+
const RESULT_KIND_NAMES = new Map([
|
|
45
|
+
[RESULT_NONE, 'none'],
|
|
46
|
+
[RESULT_TEXT, 'text'],
|
|
47
|
+
[RESULT_JSON, 'json'],
|
|
48
|
+
[RESULT_BINARY, 'binary'],
|
|
49
|
+
[RESULT_EMPTY, 'empty'],
|
|
50
|
+
[RESULT_STREAM, 'stream']
|
|
51
|
+
]);
|
|
52
|
+
|
|
53
|
+
const ERROR_BROADCASTER_MISSING = {
|
|
54
|
+
code: 'PULSEWASM_BROADCASTER_MISSING',
|
|
55
|
+
message: 'Channel routing resolved channels, but no broadcaster adaptor was provided.',
|
|
56
|
+
statusCode: 500
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
function normalizeHandlerRegistry(input) {
|
|
60
|
+
if (input instanceof Map) return new Map(input);
|
|
61
|
+
if (!input || typeof input !== 'object') return new Map();
|
|
62
|
+
return new Map(Object.entries(input));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function normalizeHeaders(headers) {
|
|
66
|
+
if (!headers) return [];
|
|
67
|
+
if (Array.isArray(headers)) return headers.map((pair) => Array.isArray(pair) ? [String(pair[0]), String(pair[1])] : [String(pair.name), String(pair.value)]);
|
|
68
|
+
if (typeof headers[Symbol.iterator] === 'function') return Array.from(headers, ([name, value]) => [String(name), String(value)]);
|
|
69
|
+
return Object.entries(headers).map(([name, value]) => [String(name), String(value)]);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function methodNameToCode(exports, method) {
|
|
73
|
+
const upper = String(method || '').toUpperCase();
|
|
74
|
+
if (upper === 'GET') return exports.pulse_bridge_method_get();
|
|
75
|
+
if (upper === 'POST') return exports.pulse_bridge_method_post();
|
|
76
|
+
return 0;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function createSetupError(code, message, details) {
|
|
80
|
+
const error = new Error(message);
|
|
81
|
+
error.code = code;
|
|
82
|
+
error.details = details;
|
|
83
|
+
return error;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function createPulseWasmHostRuntime(options = {}) {
|
|
87
|
+
const wasmBytes = options.wasmBytes || (options.wasmPath ? fs.readFileSync(path.resolve(options.wasmPath)) : undefined);
|
|
88
|
+
if (!wasmBytes) throw createSetupError('PULSEWASM_HOST_RUNTIME_WASM_REQUIRED', 'createPulseWasmHostRuntime requires wasmBytes or wasmPath.');
|
|
89
|
+
|
|
90
|
+
const wasmHostAbi = options.wasmHostAbi || {};
|
|
91
|
+
const deploymentPosture = options.deploymentPosture || {};
|
|
92
|
+
const postureEngine = deploymentPosture.engine || 'wasm';
|
|
93
|
+
if (postureEngine === 'js' && !options.allowJsEngine) {
|
|
94
|
+
throw createSetupError(
|
|
95
|
+
'PULSEWASM_HOST_RUNTIME_ENGINE_MISMATCH',
|
|
96
|
+
'deployment-posture.json selected the JS engine; the Wasm host runtime refuses to run without allowJsEngine override.',
|
|
97
|
+
{ engine: postureEngine }
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const requiredHandlers = (((wasmHostAbi.imports || {}).handlers) || []).map((handler) => ({ ...handler }));
|
|
102
|
+
const handlers = normalizeHandlerRegistry(options.handlers);
|
|
103
|
+
const missingHandlers = requiredHandlers.filter((handler) => !handlers.has(handler.name));
|
|
104
|
+
if (missingHandlers.length > 0) {
|
|
105
|
+
throw createSetupError(
|
|
106
|
+
'PULSEWASM_HOST_RUNTIME_MISSING_HANDLER',
|
|
107
|
+
'Host runtime handler registry is missing one or more generated handler imports.',
|
|
108
|
+
{ missingHandlers: missingHandlers.map((handler) => handler.name) }
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const encoder = new TextEncoder();
|
|
113
|
+
const decoder = new TextDecoder();
|
|
114
|
+
const module = new WebAssembly.Module(wasmBytes);
|
|
115
|
+
let instance;
|
|
116
|
+
const calls = { handlers: [], broadcasts: [], lifecycle: [] };
|
|
117
|
+
const channelRefMap = new Map();
|
|
118
|
+
const hostResponseRefMap = new Map();
|
|
119
|
+
const hostStreamRefMap = new Map();
|
|
120
|
+
let nextSyntheticChannelRef = 0xC000;
|
|
121
|
+
let nextSyntheticHostRef = 0xD000;
|
|
122
|
+
|
|
123
|
+
function memory() { return instance.exports.memory; }
|
|
124
|
+
|
|
125
|
+
function allocBytes(text) {
|
|
126
|
+
const bytes = encoder.encode(String(text ?? ''));
|
|
127
|
+
const ptr = instance.exports.pulse_alloc(bytes.length || 1);
|
|
128
|
+
new Uint8Array(memory().buffer, ptr, bytes.length).set(bytes);
|
|
129
|
+
return { ptr, len: bytes.length, text: String(text ?? '') };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function readBytes(ptr, len) {
|
|
133
|
+
if (!ptr || !len) return '';
|
|
134
|
+
return decoder.decode(new Uint8Array(memory().buffer, ptr, len));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function readStringRef(ref) {
|
|
138
|
+
if (!ref) return undefined;
|
|
139
|
+
return readBytes(instance.exports.pulse_string_ptr(ref), instance.exports.pulse_string_len(ref));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function makeErrorRef(code, message, statusCode) {
|
|
143
|
+
const c = allocBytes(code);
|
|
144
|
+
const m = allocBytes(message);
|
|
145
|
+
return instance.exports.pulse_error(c.ptr, c.len, m.ptr, m.len, statusCode || 500);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function decodeErrorRef(errorRef) {
|
|
149
|
+
if (!errorRef) return undefined;
|
|
150
|
+
return {
|
|
151
|
+
code: readStringRef(instance.exports.pulse_error_code_ref(errorRef)),
|
|
152
|
+
message: readStringRef(instance.exports.pulse_error_message_ref(errorRef)),
|
|
153
|
+
statusCode: instance.exports.pulse_error_status_code(errorRef),
|
|
154
|
+
causeRef: instance.exports.pulse_error_cause_ref(errorRef)
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function readResultHeaders() {
|
|
159
|
+
const headers = [];
|
|
160
|
+
const count = instance.exports.pulse_result_header_count ? instance.exports.pulse_result_header_count() : 0;
|
|
161
|
+
for (let index = 0; index < count; index += 1) {
|
|
162
|
+
headers.push([
|
|
163
|
+
readBytes(instance.exports.pulse_result_header_name_ptr(index), instance.exports.pulse_result_header_name_len(index)),
|
|
164
|
+
readBytes(instance.exports.pulse_result_header_value_ptr(index), instance.exports.pulse_result_header_value_len(index)),
|
|
165
|
+
instance.exports.pulse_result_header_mode ? instance.exports.pulse_result_header_mode(index) : 2
|
|
166
|
+
]);
|
|
167
|
+
}
|
|
168
|
+
return headers;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function readResultHeaderDeletes() {
|
|
172
|
+
const deletes = [];
|
|
173
|
+
const count = instance.exports.pulse_result_header_delete_count ? instance.exports.pulse_result_header_delete_count() : 0;
|
|
174
|
+
for (let index = 0; index < count; index += 1) {
|
|
175
|
+
deletes.push(readBytes(instance.exports.pulse_result_header_delete_name_ptr(index), instance.exports.pulse_result_header_delete_name_len(index)).toLowerCase());
|
|
176
|
+
}
|
|
177
|
+
return deletes;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function registerHostStream(stream) {
|
|
181
|
+
const ref = nextSyntheticHostRef++;
|
|
182
|
+
hostStreamRefMap.set(ref, stream);
|
|
183
|
+
return ref;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function registerHostResponse(response) {
|
|
187
|
+
const ref = nextSyntheticHostRef++;
|
|
188
|
+
const normalized = response && typeof response === 'object' ? response : { status: 200, headers: [], bodyStream: response };
|
|
189
|
+
if (normalized.bodyStream && !normalized.streamRef) normalized.streamRef = registerHostStream(normalized.bodyStream);
|
|
190
|
+
hostResponseRefMap.set(ref, normalized);
|
|
191
|
+
return ref;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function mergeResponseHeaders(baseHeaders, resultHeaders, deletedLowerNames) {
|
|
195
|
+
const deleted = new Set((deletedLowerNames || []).map((value) => String(value).toLowerCase()));
|
|
196
|
+
const merged = [];
|
|
197
|
+
for (const [name, value] of normalizeHeaders(baseHeaders || [])) {
|
|
198
|
+
if (!deleted.has(String(name).toLowerCase())) merged.push([name, value]);
|
|
199
|
+
}
|
|
200
|
+
for (const item of resultHeaders || []) {
|
|
201
|
+
const name = item[0];
|
|
202
|
+
const value = item[1];
|
|
203
|
+
const mode = Number(item[2] || 2);
|
|
204
|
+
const lower = String(name).toLowerCase();
|
|
205
|
+
if (mode === 1) {
|
|
206
|
+
for (let i = merged.length - 1; i >= 0; i -= 1) if (String(merged[i][0]).toLowerCase() === lower) merged.splice(i, 1);
|
|
207
|
+
}
|
|
208
|
+
merged.push([String(name), String(value)]);
|
|
209
|
+
}
|
|
210
|
+
return merged;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function decodeHostResult(status) {
|
|
214
|
+
const resultStatus = instance.exports.pulse_result_status ? instance.exports.pulse_result_status() : status;
|
|
215
|
+
const kindCode = instance.exports.pulse_result_kind ? instance.exports.pulse_result_kind() : RESULT_NONE;
|
|
216
|
+
const resultHeaders = readResultHeaders();
|
|
217
|
+
const deletedHeaders = readResultHeaderDeletes();
|
|
218
|
+
const errorRef = instance.exports.pulse_result_error_ref ? instance.exports.pulse_result_error_ref() : 0;
|
|
219
|
+
const error = decodeErrorRef(errorRef);
|
|
220
|
+
if (kindCode === RESULT_STREAM) {
|
|
221
|
+
const responseRef = instance.exports.pulse_result_response_ref ? instance.exports.pulse_result_response_ref() : 0;
|
|
222
|
+
const streamRef = instance.exports.pulse_result_stream_ref ? instance.exports.pulse_result_stream_ref() : 0;
|
|
223
|
+
const response = responseRef ? hostResponseRefMap.get(responseRef) : undefined;
|
|
224
|
+
const stream = response && response.bodyStream ? response.bodyStream : hostStreamRefMap.get(streamRef);
|
|
225
|
+
const baseHeaders = response && response.headers ? response.headers : [];
|
|
226
|
+
const responseStatus = response && response.status ? Number(response.status) : 0;
|
|
227
|
+
return {
|
|
228
|
+
status: resultStatus || responseStatus || status,
|
|
229
|
+
kind: 'stream',
|
|
230
|
+
kindCode,
|
|
231
|
+
headers: mergeResponseHeaders(baseHeaders, resultHeaders, deletedHeaders),
|
|
232
|
+
bodyStream: stream,
|
|
233
|
+
streamRef,
|
|
234
|
+
responseRef,
|
|
235
|
+
response,
|
|
236
|
+
error,
|
|
237
|
+
diagnostics: error ? [{ code: error.code, message: error.message, statusCode: error.statusCode }] : []
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
const body = instance.exports.pulse_result_body_ptr ? readBytes(instance.exports.pulse_result_body_ptr(), instance.exports.pulse_result_body_len()) : '';
|
|
241
|
+
return {
|
|
242
|
+
status: resultStatus || status,
|
|
243
|
+
kind: RESULT_KIND_NAMES.get(kindCode) || 'unknown',
|
|
244
|
+
kindCode,
|
|
245
|
+
headers: resultHeaders.map(([name, value]) => [name, value]),
|
|
246
|
+
body: body || undefined,
|
|
247
|
+
error,
|
|
248
|
+
diagnostics: error ? [{ code: error.code, message: error.message, statusCode: error.statusCode }] : []
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function requestHeaderClear() { if (instance.exports.pulse_bridge_request_headers_clear) instance.exports.pulse_bridge_request_headers_clear(); }
|
|
253
|
+
function requestHeaderAppend(name, value) {
|
|
254
|
+
if (!instance.exports.pulse_bridge_request_header_append) return;
|
|
255
|
+
const n = allocBytes(name);
|
|
256
|
+
const v = allocBytes(value);
|
|
257
|
+
instance.exports.pulse_bridge_request_header_append(n.ptr, n.len, v.ptr, v.len);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function ctxParam(ctxRef, name) {
|
|
261
|
+
const n = allocBytes(name);
|
|
262
|
+
return readStringRef(instance.exports.pulse_ctx_param(ctxRef, n.ptr, n.len));
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function ctxStateGet(ctxRef, key) {
|
|
266
|
+
const k = allocBytes(key);
|
|
267
|
+
return readStringRef(instance.exports.pulse_ctx_state_get(ctxRef, k.ptr, k.len));
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function ctxStateSet(ctxRef, key, value) {
|
|
271
|
+
const k = allocBytes(key);
|
|
272
|
+
const v = allocBytes(value);
|
|
273
|
+
instance.exports.pulse_ctx_state_set(ctxRef, k.ptr, k.len, v.ptr, v.len);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function requestHeaderFirst(ctxRef, name) {
|
|
277
|
+
const n = allocBytes(name);
|
|
278
|
+
return readStringRef(instance.exports.pulse_request_header_first(ctxRef, n.ptr, n.len));
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function requestHeaderCount(ctxRef, name) {
|
|
282
|
+
const n = allocBytes(name);
|
|
283
|
+
return instance.exports.pulse_request_header_count(ctxRef, n.ptr, n.len);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function requestHeaderAt(ctxRef, name, index) {
|
|
287
|
+
const n = allocBytes(name);
|
|
288
|
+
return readStringRef(instance.exports.pulse_request_header_at(ctxRef, n.ptr, n.len, index));
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function responseHeaderSet(ctxRef, name, value) {
|
|
292
|
+
const n = allocBytes(name);
|
|
293
|
+
const v = allocBytes(value);
|
|
294
|
+
instance.exports.pulse_response_header_set(ctxRef, n.ptr, n.len, v.ptr, v.len);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function responseHeaderAppend(ctxRef, name, value) {
|
|
298
|
+
const n = allocBytes(name);
|
|
299
|
+
const v = allocBytes(value);
|
|
300
|
+
instance.exports.pulse_response_header_append(ctxRef, n.ptr, n.len, v.ptr, v.len);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function responseHeaderDelete(ctxRef, name) {
|
|
304
|
+
const n = allocBytes(name);
|
|
305
|
+
instance.exports.pulse_response_header_delete(ctxRef, n.ptr, n.len);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function resultText(ctxRef, statusCode, body) {
|
|
309
|
+
const b = allocBytes(body);
|
|
310
|
+
instance.exports.pulse_result_text(ctxRef, statusCode, b.ptr, b.len);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function resultEmpty(ctxRef, statusCode) {
|
|
314
|
+
instance.exports.pulse_result_empty(ctxRef, statusCode);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function resultJsonText(ctxRef, statusCode, jsonText) {
|
|
318
|
+
const b = allocBytes(jsonText);
|
|
319
|
+
if (instance.exports.pulse_result_json_text) instance.exports.pulse_result_json_text(ctxRef, statusCode, b.ptr, b.len);
|
|
320
|
+
else {
|
|
321
|
+
responseHeaderSet(ctxRef, 'Content-Type', 'application/json');
|
|
322
|
+
resultText(ctxRef, statusCode, jsonText);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function resultStream(ctxRef, statusCode, stream) {
|
|
327
|
+
const ref = typeof stream === 'number' ? stream : registerHostStream(stream);
|
|
328
|
+
if (instance.exports.pulse_result_stream) instance.exports.pulse_result_stream(ctxRef, statusCode, ref);
|
|
329
|
+
return ref;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function resultFromResponse(ctxRef, response) {
|
|
333
|
+
const ref = typeof response === 'number' ? response : registerHostResponse(response);
|
|
334
|
+
if (instance.exports.pulse_result_from_response) instance.exports.pulse_result_from_response(ctxRef, ref);
|
|
335
|
+
return ref;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function createHandlerContext(ctxRef, handlerMeta) {
|
|
339
|
+
return {
|
|
340
|
+
ctxRef,
|
|
341
|
+
handler: handlerMeta,
|
|
342
|
+
param(name) { return ctxParam(ctxRef, name); },
|
|
343
|
+
state: {
|
|
344
|
+
get(key) { return ctxStateGet(ctxRef, key); },
|
|
345
|
+
set(key, value) { return ctxStateSet(ctxRef, key, value); }
|
|
346
|
+
},
|
|
347
|
+
requestHeader: {
|
|
348
|
+
first(name) { return requestHeaderFirst(ctxRef, name); },
|
|
349
|
+
count(name) { return requestHeaderCount(ctxRef, name); },
|
|
350
|
+
at(name, index) { return requestHeaderAt(ctxRef, name, index); }
|
|
351
|
+
},
|
|
352
|
+
responseHeader: {
|
|
353
|
+
set(name, value) { return responseHeaderSet(ctxRef, name, value); },
|
|
354
|
+
append(name, value) { return responseHeaderAppend(ctxRef, name, value); },
|
|
355
|
+
delete(name) { return responseHeaderDelete(ctxRef, name); }
|
|
356
|
+
},
|
|
357
|
+
text(status, body) { return resultText(ctxRef, status, body); },
|
|
358
|
+
empty(status) { return resultEmpty(ctxRef, status); },
|
|
359
|
+
jsonText(status, jsonText) { return resultJsonText(ctxRef, status, jsonText); },
|
|
360
|
+
stream(status, stream) { return resultStream(ctxRef, status, stream); },
|
|
361
|
+
fromResponse(response) { return resultFromResponse(ctxRef, response); },
|
|
362
|
+
next() { return instance.exports.pulse_next(ctxRef); },
|
|
363
|
+
nextError(errorRef) { return instance.exports.pulse_next_error(ctxRef, errorRef); },
|
|
364
|
+
error(code, message, statusCode) { return makeErrorRef(code, message, statusCode || 500); }
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function normalizeHandlerReturn(ctx, returned) {
|
|
369
|
+
if (!returned) return;
|
|
370
|
+
if (typeof returned === 'string') return ctx.text(200, returned);
|
|
371
|
+
if (typeof returned === 'object') {
|
|
372
|
+
if (returned.kind === 'empty') return ctx.empty(returned.status || 204);
|
|
373
|
+
if (returned.kind === 'stream') return ctx.stream(returned.status || 200, returned.stream || returned.bodyStream || returned.body);
|
|
374
|
+
if (returned.kind === 'response' || returned.response !== undefined) return ctx.fromResponse(returned.response !== undefined ? returned.response : returned);
|
|
375
|
+
if (returned.kind === 'json' || returned.json !== undefined) return ctx.jsonText(returned.status || 200, returned.body !== undefined ? String(returned.body) : JSON.stringify(returned.json));
|
|
376
|
+
if (returned.body !== undefined || returned.status !== undefined) return ctx.text(returned.status || 200, returned.body === undefined ? '' : String(returned.body));
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function readChannels(channelsRef) {
|
|
381
|
+
if (!channelsRef) return [];
|
|
382
|
+
if (channelRefMap.has(channelsRef)) return channelRefMap.get(channelsRef).slice();
|
|
383
|
+
if (!instance.exports.pulse_channel_count || !instance.exports.pulse_channel_ref_at) return [];
|
|
384
|
+
const count = instance.exports.pulse_channel_count(channelsRef);
|
|
385
|
+
const channels = [];
|
|
386
|
+
for (let i = 0; i < count; i += 1) {
|
|
387
|
+
const ref = instance.exports.pulse_channel_ref_at(channelsRef, i);
|
|
388
|
+
const value = readStringRef(ref);
|
|
389
|
+
if (value) channels.push(value);
|
|
390
|
+
}
|
|
391
|
+
return channels;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const handlerByName = new Map(requiredHandlers.map((handler) => [handler.name, handler]));
|
|
395
|
+
const importObject = {
|
|
396
|
+
env: {
|
|
397
|
+
abort(message, file, line, column) { throw new Error('AssemblyScript abort(' + message + ', ' + file + ', ' + line + ', ' + column + ')'); }
|
|
398
|
+
},
|
|
399
|
+
pulsewasm_handlers: {},
|
|
400
|
+
pulsewasm_broadcaster: {
|
|
401
|
+
broadcast(channelsRef, ctxRef) {
|
|
402
|
+
const channels = readChannels(channelsRef);
|
|
403
|
+
if (!options.broadcaster || typeof options.broadcaster.broadcast !== 'function') {
|
|
404
|
+
return makeErrorRef(ERROR_BROADCASTER_MISSING.code, ERROR_BROADCASTER_MISSING.message, ERROR_BROADCASTER_MISSING.statusCode);
|
|
405
|
+
}
|
|
406
|
+
calls.broadcasts.push({ channels, ctxRef });
|
|
407
|
+
const result = options.broadcaster.broadcast(channels, { ctxRef, channels });
|
|
408
|
+
if (result && result.code) return makeErrorRef(result.code, result.message || result.code, result.statusCode || 500);
|
|
409
|
+
return 0;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
for (const handler of requiredHandlers) {
|
|
415
|
+
importObject.pulsewasm_handlers[handler.name] = (a, b, c) => {
|
|
416
|
+
calls.handlers.push({ name: handler.name, role: handler.primaryRole, slot: handler.slot });
|
|
417
|
+
const fn = handlers.get(handler.name);
|
|
418
|
+
if (handler.primaryRole === 'channel') {
|
|
419
|
+
const ctx = createHandlerContext(a, handler);
|
|
420
|
+
const returned = fn(ctx);
|
|
421
|
+
let channels = [];
|
|
422
|
+
if (Array.isArray(returned)) channels = returned.map((value) => String(value));
|
|
423
|
+
else if (typeof returned === 'string') channels = [returned];
|
|
424
|
+
else if (returned && typeof returned.channel === 'string') channels = [returned.channel];
|
|
425
|
+
if (channels.length === 0) return 0;
|
|
426
|
+
const syntheticRef = nextSyntheticChannelRef++;
|
|
427
|
+
channelRefMap.set(syntheticRef, channels);
|
|
428
|
+
return syntheticRef;
|
|
429
|
+
}
|
|
430
|
+
const ctxRef = handler.primaryRole === 'error' ? b : a;
|
|
431
|
+
const errRef = handler.primaryRole === 'error' ? a : 0;
|
|
432
|
+
const ctx = createHandlerContext(ctxRef, handler);
|
|
433
|
+
if (errRef) ctx.activeError = decodeErrorRef(errRef);
|
|
434
|
+
const returned = fn(ctx);
|
|
435
|
+
normalizeHandlerReturn(ctx, returned);
|
|
436
|
+
return 0;
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
instance = new WebAssembly.Instance(module, importObject);
|
|
441
|
+
|
|
442
|
+
function executeRequest(input) {
|
|
443
|
+
requestHeaderClear();
|
|
444
|
+
for (const [name, value] of normalizeHeaders(input.headers)) requestHeaderAppend(name, value);
|
|
445
|
+
const methodCode = methodNameToCode(instance.exports, input.method);
|
|
446
|
+
const requestPath = allocBytes(input.path || '/');
|
|
447
|
+
const status = instance.exports.pulse_execute_request(methodCode, requestPath.ptr, requestPath.len);
|
|
448
|
+
return decodeHostResult(status);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
function executeLifecycle(event, input) {
|
|
452
|
+
const methodCode = methodNameToCode(instance.exports, input.method || 'GET');
|
|
453
|
+
const requestPath = allocBytes(input.path || '/');
|
|
454
|
+
const fn = event === 'connect' ? instance.exports.pulse_execute_connect : instance.exports.pulse_execute_disconnect;
|
|
455
|
+
const invoked = fn(methodCode, requestPath.ptr, requestPath.len);
|
|
456
|
+
calls.lifecycle.push({ event, invoked });
|
|
457
|
+
return { event, invoked, status: 200 };
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
return {
|
|
461
|
+
version: 'pulsewasm.host-runtime-kernel.v2',
|
|
462
|
+
executeRequest,
|
|
463
|
+
executeConnect(input) { return executeLifecycle('connect', input || {}); },
|
|
464
|
+
executeDisconnect(input) { return executeLifecycle('disconnect', input || {}); },
|
|
465
|
+
close() {},
|
|
466
|
+
calls,
|
|
467
|
+
exports: instance.exports,
|
|
468
|
+
readStringRef,
|
|
469
|
+
readResult: decodeHostResult
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
module.exports = {
|
|
474
|
+
RESULT_NONE,
|
|
475
|
+
RESULT_TEXT,
|
|
476
|
+
RESULT_JSON,
|
|
477
|
+
RESULT_BINARY,
|
|
478
|
+
RESULT_EMPTY,
|
|
479
|
+
RESULT_STREAM,
|
|
480
|
+
createPulseWasmHostRuntime,
|
|
481
|
+
normalizeHeaders
|
|
482
|
+
};
|
|
483
|
+
`;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function runtimeSmoke(runtimeModule, wasmPath, wasmHostAbi, deploymentPosture) {
|
|
487
|
+
const observed = { headers: {}, state: {}, lifecycle: {} };
|
|
488
|
+
const handlers = {
|
|
489
|
+
auth(ctx) {
|
|
490
|
+
const first = ctx.requestHeader.first('X-Pulse-Test');
|
|
491
|
+
const multiCount = ctx.requestHeader.count('X-Pulse-Multi');
|
|
492
|
+
const multiSecond = ctx.requestHeader.at('X-Pulse-Multi', 1);
|
|
493
|
+
if (first !== undefined) observed.headers.first = first;
|
|
494
|
+
if (multiCount > (observed.headers.multiCount || 0)) observed.headers.multiCount = multiCount;
|
|
495
|
+
if (multiSecond !== undefined) observed.headers.multiSecond = multiSecond;
|
|
496
|
+
ctx.state.set('auth', '1');
|
|
497
|
+
ctx.next();
|
|
498
|
+
},
|
|
499
|
+
gateway(ctx) {
|
|
500
|
+
observed.state.gatewaySawAuth = ctx.state.get('auth');
|
|
501
|
+
ctx.next();
|
|
502
|
+
},
|
|
503
|
+
realtimeHandler(ctx) {
|
|
504
|
+
const id = ctx.param('id');
|
|
505
|
+
ctx.responseHeader.set('Content-Type', 'text/plain');
|
|
506
|
+
ctx.responseHeader.set('Grip-Hold', 'stream');
|
|
507
|
+
ctx.responseHeader.append('Grip-Channel', 'realtime');
|
|
508
|
+
ctx.responseHeader.append('Grip-Channel', `realtime:${id}`);
|
|
509
|
+
ctx.text(200, `realtime:${id}`);
|
|
510
|
+
},
|
|
511
|
+
computeHandler(ctx) {
|
|
512
|
+
const id = ctx.param('id');
|
|
513
|
+
ctx.responseHeader.set('Content-Type', 'text/plain');
|
|
514
|
+
ctx.text(201, `compute:${id}`);
|
|
515
|
+
},
|
|
516
|
+
bundleChannel() { return 'bundleChannel'; },
|
|
517
|
+
onConnect() { observed.lifecycle.connect = (observed.lifecycle.connect || 0) + 1; },
|
|
518
|
+
onDisconnect() { observed.lifecycle.disconnect = (observed.lifecycle.disconnect || 0) + 1; }
|
|
519
|
+
};
|
|
520
|
+
const broadcasterCalls = [];
|
|
521
|
+
const runtime = runtimeModule.createPulseWasmHostRuntime({
|
|
522
|
+
wasmPath,
|
|
523
|
+
wasmHostAbi,
|
|
524
|
+
deploymentPosture,
|
|
525
|
+
handlers,
|
|
526
|
+
broadcaster: { broadcast(channels, ctx) { broadcasterCalls.push({ channels, ctx }); return undefined; } }
|
|
527
|
+
});
|
|
528
|
+
const realtime = runtime.executeRequest({ method: 'GET', path: '/realtime/abc', headers: [['X-Pulse-Test', 'edge'], ['X-Pulse-Multi', 'one'], ['x-pulse-multi', 'two']] });
|
|
529
|
+
const compute = runtime.executeRequest({ method: 'POST', path: '/compute/42', headers: [['X-Pulse-Test', 'edge']] });
|
|
530
|
+
const missing = runtime.executeRequest({ method: 'GET', path: '/missing' });
|
|
531
|
+
const connect = runtime.executeConnect({ method: 'GET', path: '/realtime/abc' });
|
|
532
|
+
const disconnect = runtime.executeDisconnect({ method: 'GET', path: '/realtime/abc' });
|
|
533
|
+
|
|
534
|
+
const noBroadcasterRuntime = runtimeModule.createPulseWasmHostRuntime({ wasmPath, wasmHostAbi, deploymentPosture, handlers });
|
|
535
|
+
const missingBroadcaster = noBroadcasterRuntime.executeRequest({ method: 'GET', path: '/realtime/abc' });
|
|
536
|
+
|
|
537
|
+
let missingHandlerRejected = false;
|
|
538
|
+
let missingHandlerCode;
|
|
539
|
+
try {
|
|
540
|
+
const incomplete = { ...handlers };
|
|
541
|
+
delete incomplete.auth;
|
|
542
|
+
runtimeModule.createPulseWasmHostRuntime({ wasmPath, wasmHostAbi, deploymentPosture, handlers: incomplete, broadcaster: { broadcast() {} } });
|
|
543
|
+
} catch (error) {
|
|
544
|
+
missingHandlerRejected = true;
|
|
545
|
+
missingHandlerCode = error.code;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
let jsPostureRejected = false;
|
|
549
|
+
let jsPostureCode;
|
|
550
|
+
try {
|
|
551
|
+
runtimeModule.createPulseWasmHostRuntime({ wasmPath, wasmHostAbi, deploymentPosture: { engine: 'js' }, handlers, broadcaster: { broadcast() {} } });
|
|
552
|
+
} catch (error) {
|
|
553
|
+
jsPostureRejected = true;
|
|
554
|
+
jsPostureCode = error.code;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
function headerValues(result, name) {
|
|
558
|
+
const lower = String(name).toLowerCase();
|
|
559
|
+
return result.headers.filter(([key]) => String(key).toLowerCase() === lower).map(([, value]) => value);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
const checks = [
|
|
563
|
+
{ name: 'realtime_status', expected: 200, actual: realtime.status },
|
|
564
|
+
{ name: 'realtime_body', expected: 'realtime:abc', actual: realtime.body },
|
|
565
|
+
{ name: 'realtime_kind', expected: 'text', actual: realtime.kind },
|
|
566
|
+
{ name: 'compute_status', expected: 201, actual: compute.status },
|
|
567
|
+
{ name: 'compute_body', expected: 'compute:42', actual: compute.body },
|
|
568
|
+
{ name: 'missing_status', expected: 404, actual: missing.status },
|
|
569
|
+
{ name: 'request_header_first', expected: 'edge', actual: observed.headers.first },
|
|
570
|
+
{ name: 'request_header_multi_count', expected: 2, actual: observed.headers.multiCount },
|
|
571
|
+
{ name: 'request_header_multi_second', expected: 'two', actual: observed.headers.multiSecond },
|
|
572
|
+
{ name: 'repeated_grip_channel_count', expected: 2, actual: headerValues(realtime, 'Grip-Channel').length },
|
|
573
|
+
{ name: 'repeated_grip_channel_second', expected: 'realtime:abc', actual: headerValues(realtime, 'Grip-Channel')[1] },
|
|
574
|
+
{ name: 'broadcaster_present_called', expected: 1, actual: broadcasterCalls.length >= 1 ? 1 : 0 },
|
|
575
|
+
{ name: 'broadcaster_missing_status', expected: 500, actual: missingBroadcaster.status },
|
|
576
|
+
{ name: 'broadcaster_missing_code', expected: 'PULSEWASM_BROADCASTER_MISSING', actual: missingBroadcaster.error && missingBroadcaster.error.code },
|
|
577
|
+
{ name: 'connect_invoked', expected: 1, actual: connect.invoked },
|
|
578
|
+
{ name: 'disconnect_invoked', expected: 1, actual: disconnect.invoked },
|
|
579
|
+
{ name: 'state_map_visible', expected: '1', actual: observed.state.gatewaySawAuth },
|
|
580
|
+
{ name: 'deployment_posture_engine', expected: 'wasm', actual: deploymentPosture.engine || 'wasm' },
|
|
581
|
+
{ name: 'missing_handler_rejected', expected: 1, actual: missingHandlerRejected ? 1 : 0 },
|
|
582
|
+
{ name: 'missing_handler_code', expected: 'PULSEWASM_HOST_RUNTIME_MISSING_HANDLER', actual: missingHandlerCode },
|
|
583
|
+
{ name: 'js_posture_rejected', expected: 1, actual: jsPostureRejected ? 1 : 0 },
|
|
584
|
+
{ name: 'js_posture_code', expected: 'PULSEWASM_HOST_RUNTIME_ENGINE_MISMATCH', actual: jsPostureCode }
|
|
585
|
+
].map((check) => ({ ...check, status: check.actual === check.expected ? 'ok' : 'error', message: check.actual === check.expected ? undefined : `Expected ${check.expected}, got ${check.actual}.` }));
|
|
586
|
+
|
|
587
|
+
return { checks, results: { realtime, compute, missing, missingBroadcaster, connect, disconnect }, observed, broadcasterCalls, missingHandlerRejected, missingHandlerCode, jsPostureRejected, jsPostureCode, runtimeCalls: runtime.calls };
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
function buildArtifact(options = {}) {
|
|
591
|
+
const cwd = options.cwd || process.cwd();
|
|
592
|
+
const checks = options.smoke?.checks || [];
|
|
593
|
+
const failedChecks = checks.filter((check) => check.status !== 'ok');
|
|
594
|
+
return normalizeArtifact({
|
|
595
|
+
version: HOST_RUNTIME_KERNEL_VERSION,
|
|
596
|
+
generatedBy: options.generatedBy || PACKAGE_VERSION,
|
|
597
|
+
status: options.diagnostics.length || failedChecks.length ? 'error' : 'ok',
|
|
598
|
+
sourceWasmHostAbiVersion: options.wasmHostAbi?.version,
|
|
599
|
+
sourceDeploymentPostureVersion: options.deploymentPosture?.version,
|
|
600
|
+
policy: {
|
|
601
|
+
phase: HOST_RUNTIME_KERNEL_PHASE,
|
|
602
|
+
streamPassThrough: true,
|
|
603
|
+
responseRefPassthrough: true,
|
|
604
|
+
sourceOfTruth: 'wasm-host-abi.json + deployment-posture.json + generated Wasm host bridge',
|
|
605
|
+
platformNeutral: true,
|
|
606
|
+
streamResults: true,
|
|
607
|
+
platformAdapters: false,
|
|
608
|
+
highLevelPulseWrapper: false,
|
|
609
|
+
listen: false,
|
|
610
|
+
witBindings: false,
|
|
611
|
+
wasiHttp: false,
|
|
612
|
+
ctxFetchEffectRuntime: false,
|
|
613
|
+
publicApi: ['executeRequest(input)', 'executeConnect(input)', 'executeDisconnect(input)', 'close()'],
|
|
614
|
+
hostRequestShape: { method: 'string', path: 'string', headers: 'Iterable<[string,string]>', bodyText: 'optional string, deferred' },
|
|
615
|
+
hostResultShape: { status: 'number', kind: 'text | json | empty', headers: 'Array<[string,string]>', body: 'optional string', error: 'optional structured error' },
|
|
616
|
+
setupFailuresThrow: true,
|
|
617
|
+
routingOutcomesReturnHostResult: true
|
|
618
|
+
},
|
|
619
|
+
runtimeFile: { file: HOST_RUNTIME_FILE, bytes: Buffer.byteLength(options.runtimeText || '', 'utf8'), sha256: sha256Text(options.runtimeText || '') },
|
|
620
|
+
wasmInput: options.wasmFile ? fileRecord(options.wasmFile, cwd, 'host-runtime-wasm-input') : undefined,
|
|
621
|
+
smoke: options.smoke,
|
|
622
|
+
diagnostics: options.diagnostics,
|
|
623
|
+
summary: {
|
|
624
|
+
generated: true,
|
|
625
|
+
smokeExecuted: Boolean(options.smoke),
|
|
626
|
+
checks: checks.length,
|
|
627
|
+
failedChecks: failedChecks.length,
|
|
628
|
+
platformNeutral: true,
|
|
629
|
+
streamResults: true,
|
|
630
|
+
wasmInstantiated: Boolean(options.smoke),
|
|
631
|
+
requestsExecuted: options.smoke ? 4 : 0,
|
|
632
|
+
lifecycleExecuted: options.smoke ? 2 : 0,
|
|
633
|
+
broadcasterPresentValidated: Boolean(options.smoke?.checks?.find((check) => check.name === 'broadcaster_present_called' && check.status === 'ok')),
|
|
634
|
+
missingBroadcasterValidated: Boolean(options.smoke?.checks?.find((check) => check.name === 'broadcaster_missing_code' && check.status === 'ok')),
|
|
635
|
+
postureEnforced: Boolean(options.smoke?.checks?.find((check) => check.name === 'js_posture_code' && check.status === 'ok')),
|
|
636
|
+
missingHandlerSetupFailureValidated: Boolean(options.smoke?.checks?.find((check) => check.name === 'missing_handler_code' && check.status === 'ok')),
|
|
637
|
+
diagnostics: options.diagnostics.length,
|
|
638
|
+
platformBindings: false,
|
|
639
|
+
witBindings: false
|
|
640
|
+
}
|
|
641
|
+
}, cwd);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
function buildHostRuntimeKernel(wasmHostAbi, wasmHostBridge, deploymentPosture, options = {}) {
|
|
645
|
+
const cwd = options.cwd || process.cwd();
|
|
646
|
+
const outDir = options.outDir ? path.resolve(cwd, options.outDir) : getDefaultArtifactsDir(cwd);
|
|
647
|
+
const generatedBy = options.generatedBy || PACKAGE_VERSION;
|
|
648
|
+
const runtimeText = buildHostRuntimeKernelSource();
|
|
649
|
+
const diagnostics = [];
|
|
650
|
+
const wasmFile = path.join(outDir, 'generated/wasm-bridge/pulsewasm-host-bridge.wasm');
|
|
651
|
+
let smoke;
|
|
652
|
+
|
|
653
|
+
if (!wasmHostAbi || !wasmHostAbi.artifact) {
|
|
654
|
+
diagnostics.push(diagnosticFromParts({ phase: 'host-runtime-kernel', code: 'PULSEWASM_HOST_RUNTIME_ABI_REQUIRED', message: 'Phase 11A requires wasm-host-abi.json.', hint: 'Enable --emit-wasm-host-abi before --emit-host-runtime-kernel.' }));
|
|
655
|
+
}
|
|
656
|
+
if (!wasmHostBridge || !wasmHostBridge.artifact) {
|
|
657
|
+
diagnostics.push(diagnosticFromParts({ phase: 'host-runtime-kernel', code: 'PULSEWASM_HOST_RUNTIME_BRIDGE_REQUIRED', message: 'Phase 11A requires the local Wasm host bridge proof.', hint: 'Enable --emit-wasm-host-bridge before --emit-host-runtime-kernel.' }));
|
|
658
|
+
}
|
|
659
|
+
if (!deploymentPosture || !deploymentPosture.artifact) {
|
|
660
|
+
diagnostics.push(diagnosticFromParts({ phase: 'host-runtime-kernel', code: 'PULSEWASM_HOST_RUNTIME_POSTURE_REQUIRED', message: 'Phase 11A requires deployment-posture.json.', hint: 'Enable --emit-json-body or deployment posture emission before --emit-host-runtime-kernel.' }));
|
|
661
|
+
}
|
|
662
|
+
if (!fs.existsSync(wasmFile)) {
|
|
663
|
+
diagnostics.push(diagnosticFromParts({ phase: 'host-runtime-kernel', code: 'PULSEWASM_HOST_RUNTIME_WASM_NOT_FOUND', message: 'Phase 11A expected the compiled host bridge Wasm file to exist.', hint: 'Run the host bridge build path before generating the host runtime kernel.', details: { wasmFile } }));
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
if (diagnostics.length === 0) {
|
|
667
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pulsewasm-host-runtime-'));
|
|
668
|
+
const tempFile = path.join(tempDir, 'host-runtime-kernel.cjs');
|
|
669
|
+
fs.writeFileSync(tempFile, runtimeText, 'utf8');
|
|
670
|
+
try {
|
|
671
|
+
const runtimeModule = require(tempFile);
|
|
672
|
+
smoke = runtimeSmoke(runtimeModule, wasmFile, wasmHostAbi.artifact, deploymentPosture.artifact);
|
|
673
|
+
const failedChecks = smoke.checks.filter((check) => check.status !== 'ok');
|
|
674
|
+
if (failedChecks.length > 0) {
|
|
675
|
+
diagnostics.push(diagnosticFromParts({ phase: 'host-runtime-kernel', code: 'PULSEWASM_HOST_RUNTIME_SMOKE_FAILED', message: 'Phase 11A host runtime kernel smoke checks failed.', hint: 'Inspect host-runtime-kernel.json smoke.failedChecks for details.', details: { failedChecks } }));
|
|
676
|
+
}
|
|
677
|
+
} catch (error) {
|
|
678
|
+
diagnostics.push(diagnosticFromParts({ phase: 'host-runtime-kernel', code: 'PULSEWASM_HOST_RUNTIME_EXECUTION_FAILED', message: 'Phase 11A host runtime kernel execution failed.', hint: 'Inspect details for the host-side runtime error.', details: { message: error && error.message ? error.message : String(error), code: error && error.code ? error.code : undefined, stack: error && error.stack ? error.stack : undefined } }));
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
const artifact = buildArtifact({ cwd, generatedBy, wasmHostAbi: wasmHostAbi?.artifact, wasmHostBridge: wasmHostBridge?.artifact, deploymentPosture: deploymentPosture?.artifact, runtimeText, wasmFile: fs.existsSync(wasmFile) ? wasmFile : undefined, smoke, diagnostics });
|
|
683
|
+
return { artifact, files: [{ file: HOST_RUNTIME_FILE, text: runtimeText }], diagnostics };
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
module.exports = {
|
|
687
|
+
HOST_RUNTIME_KERNEL_VERSION,
|
|
688
|
+
buildHostRuntimeKernel,
|
|
689
|
+
buildHostRuntimeKernelSource
|
|
690
|
+
};
|