@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.
@@ -0,0 +1,33 @@
1
+ 'use strict';
2
+
3
+ const wasmHostAbi = require('./wasm-host-abi.js');
4
+ const wasmHostBridge = require('./wasm-host-bridge.js');
5
+ const requestResultHeaders = require('./request-result-headers.js');
6
+ const channelBroadcaster = require('./channel-broadcaster.js');
7
+ const jsonBody = require('./json-body.js');
8
+ const backendCapabilities = require('./backend-capabilities.js');
9
+ const hostRuntimeKernel = require('./host-runtime-kernel.js');
10
+ const streamingPassthrough = require('./streaming-passthrough.js');
11
+ const compiledWasmRuntime = require('./compiled-wasm-runtime.js');
12
+
13
+ module.exports = {
14
+ wasmHostAbi,
15
+ wasmHostBridge,
16
+ requestResultHeaders,
17
+ channelBroadcaster,
18
+ jsonBody,
19
+ backendCapabilities,
20
+ hostRuntimeKernel,
21
+ streamingPassthrough,
22
+ compiledWasmRuntime,
23
+ buildWasmHostAbi: wasmHostAbi.buildWasmHostAbi,
24
+ buildWasmHostBridge: wasmHostBridge.buildWasmHostBridge,
25
+ buildRequestResultHeaders: requestResultHeaders.buildRequestResultHeaders,
26
+ buildChannelBroadcaster: channelBroadcaster.buildChannelBroadcaster,
27
+ buildJsonBodyAbi: jsonBody.buildJsonBodyAbi,
28
+ buildDeploymentPosture: jsonBody.buildDeploymentPosture,
29
+ buildBackendCapabilities: backendCapabilities.buildBackendCapabilities,
30
+ buildHostRuntimeKernel: hostRuntimeKernel.buildHostRuntimeKernel,
31
+ buildStreamingPassthrough: streamingPassthrough.buildStreamingPassthrough,
32
+ buildCompiledWasmRuntime: compiledWasmRuntime.buildCompiledWasmRuntime
33
+ };
@@ -0,0 +1,454 @@
1
+ 'use strict';
2
+
3
+ const { PACKAGE_VERSION, normalizeArtifact, normalizeDiagnostic } = require('@pulse-compute/wasm-contracts/diagnostics');
4
+ const {
5
+ JSON_BODY_VERSION,
6
+ JSON_BODY_PHASE,
7
+ DEPLOYMENT_POSTURE_VERSION,
8
+ JSON_TARGETS,
9
+ ENGINE_TARGETS,
10
+ DEFAULT_JSON_MAX_BYTES,
11
+ JSON_ABI_EXPORTS,
12
+ selectRuntimeEngine,
13
+ selectJsonConfig
14
+ } = loadContractsJsonBody();
15
+
16
+ function loadContractsJsonBody() {
17
+ try {
18
+ return require('@pulse-compute/wasm-contracts/host/json-body');
19
+ } catch (error) {
20
+ if (error && error.code === 'MODULE_NOT_FOUND' && String(error.message).includes('@pulse-compute/wasm-contracts')) {
21
+ return require('../../../contracts/src/host/json-body.js');
22
+ }
23
+ throw error;
24
+ }
25
+ }
26
+
27
+ function configArtifact(options = {}) {
28
+ return options.resolvedConfig?.artifact || options.resolvedConfig || {};
29
+ }
30
+
31
+
32
+ function makeDiagnostic(code, message, hint, details) {
33
+ return normalizeDiagnostic({
34
+ severity: 'error',
35
+ phase: 'json-body',
36
+ code,
37
+ message,
38
+ hint,
39
+ details,
40
+ loc: { file: '<config>' }
41
+ });
42
+ }
43
+
44
+ function makeWarning(code, message, hint, details) {
45
+ return normalizeDiagnostic({
46
+ severity: 'warning',
47
+ phase: 'json-body',
48
+ code,
49
+ message,
50
+ hint,
51
+ details,
52
+ loc: { file: '<config>' }
53
+ });
54
+ }
55
+
56
+ function validateSelections(engine, json) {
57
+ const diagnostics = [];
58
+ if (!ENGINE_TARGETS.has(engine.engine)) {
59
+ diagnostics.push(makeDiagnostic(
60
+ 'PULSEWASM_ENGINE_UNSUPPORTED',
61
+ `Unsupported runtime.engine ${JSON.stringify(engine.engine)}.`,
62
+ 'Use runtime.engine = "wasm" or runtime.engine = "js".',
63
+ { engine: engine.engine }
64
+ ));
65
+ }
66
+ if (!JSON_TARGETS.has(json.target)) {
67
+ diagnostics.push(makeDiagnostic(
68
+ 'PULSEWASM_JSON_UNSUPPORTED_TARGET',
69
+ `Unsupported json.target ${JSON.stringify(json.target)}.`,
70
+ 'Use json.target = "generic", "schema", "auto", "parser", or "none".',
71
+ { jsonTarget: json.target }
72
+ ));
73
+ }
74
+ if (!Number.isFinite(json.maxBytes) || json.maxBytes <= 0 || Math.floor(json.maxBytes) !== json.maxBytes) {
75
+ diagnostics.push(makeDiagnostic(
76
+ 'PULSEWASM_JSON_BODY_LIMIT_INVALID',
77
+ 'json.maxBytes must be a positive finite integer.',
78
+ `Use a positive byte limit such as ${DEFAULT_JSON_MAX_BYTES}.`,
79
+ { maxBytes: json.maxBytes }
80
+ ));
81
+ }
82
+ return diagnostics;
83
+ }
84
+
85
+ function jsonWarnings(engine, json) {
86
+ const warnings = [];
87
+ if (json.target === 'generic' && !json.explicitTarget) {
88
+ warnings.push(makeWarning(
89
+ 'PULSEWASM_JSON_GENERIC_DEFAULT',
90
+ 'Generic JSON parsing is enabled by default for ergonomics.',
91
+ 'This can increase binary size and allocation pressure. Use json.target = "schema" or json.target = "none" for tighter builds.',
92
+ { target: json.target, engine: engine.engine, maxBytes: json.maxBytes }
93
+ ));
94
+ } else if (json.target === 'generic') {
95
+ warnings.push(makeWarning(
96
+ 'PULSEWASM_JSON_GENERIC_SIZE_WARNING',
97
+ 'Generic JSON parsing was selected explicitly.',
98
+ 'Use schema JSON later if payload parsing becomes a size or latency concern.',
99
+ { target: json.target, engine: engine.engine, maxBytes: json.maxBytes }
100
+ ));
101
+ }
102
+ if (json.target === 'schema') {
103
+ warnings.push(makeWarning(
104
+ 'PULSEWASM_JSON_SCHEMA_SELECTED',
105
+ 'Schema JSON target is selected for the deterministic schema sidecar lane.',
106
+ 'Use the schema-json sidecar or compile builders to emit parser artifacts for declared schemas.',
107
+ { target: json.target, schemaCount: json.schemas.length }
108
+ ));
109
+ }
110
+ if (json.target === 'auto') {
111
+ warnings.push(makeWarning(
112
+ 'PULSEWASM_JSON_AUTO_SELECTED',
113
+ 'Schema JSON auto mode is selected.',
114
+ 'Declared schemas use deterministic schema sidecars; schema-less auto mode requires parser = "as-json" to emit the generic AS parser fallback.',
115
+ { target: json.target, schemaCount: json.schemas.length, parser: json.parser }
116
+ ));
117
+ }
118
+ if (json.target === 'parser') {
119
+ warnings.push(makeWarning(
120
+ 'PULSEWASM_JSON_AS_GENERIC_PARSER_SELECTED',
121
+ 'Generic AssemblyScript JSON parser mode is selected explicitly.',
122
+ 'The parser sidecar is emitted by @pulse-compute/wasm-schema-json when schema-json-generic-parser is enabled.',
123
+ { target: json.target, parser: json.parser || 'as-json' }
124
+ ));
125
+ }
126
+ return warnings;
127
+ }
128
+
129
+
130
+
131
+ function buildMarkdown(artifact) {
132
+ const lines = [];
133
+ lines.push('# PulseWasm JSON / Body ABI Contract v1');
134
+ lines.push('');
135
+ lines.push(`Generated by: \`${artifact.generatedBy}\``);
136
+ lines.push(`Phase: \`${artifact.policy.phase}\``);
137
+ lines.push('');
138
+ lines.push('## Locked positions');
139
+ lines.push('');
140
+ for (const item of artifact.lockedPositions) lines.push(`- ${item}`);
141
+ lines.push('');
142
+ lines.push('## JSON target');
143
+ lines.push('');
144
+ lines.push(`- Effective target: \`${artifact.json.target}\``);
145
+ lines.push(`- Target source: \`${artifact.json.targetSource}\``);
146
+ lines.push(`- Max bytes: \`${artifact.json.maxBytes}\``);
147
+ lines.push('');
148
+ lines.push('## Body policy');
149
+ lines.push('');
150
+ lines.push(`- Request body: \`${artifact.bodyPolicy.requestBody}\``);
151
+ lines.push(`- Response body: \`${artifact.bodyPolicy.responseBody}\``);
152
+ lines.push(`- RESULT_JSON: \`${artifact.bodyPolicy.resultJson}\``);
153
+ lines.push(`- RESULT_BINARY: \`${artifact.bodyPolicy.resultBinary}\``);
154
+ lines.push('');
155
+ lines.push('## ABI exports');
156
+ lines.push('');
157
+ for (const item of artifact.abi.exports) lines.push(`- \`${item}\``);
158
+ lines.push('');
159
+ lines.push('## Warnings');
160
+ lines.push('');
161
+ if (artifact.warnings.length === 0) lines.push('- none');
162
+ for (const warning of artifact.warnings) lines.push(`- \`${warning.code}\`: ${warning.message}`);
163
+ lines.push('');
164
+ lines.push('## Schema target');
165
+ lines.push('');
166
+ lines.push('`json.target = "schema"` uses the deterministic schema sidecar lane. `json.target = "parser"` or `json.target = "auto"` without schemas can opt into the Pass 34 generic AssemblyScript parser fallback.');
167
+ lines.push('');
168
+ return `${lines.join('\n')}\n`;
169
+ }
170
+
171
+ function buildJsonBodyAbi(dispatchTable, executionPlan, options = {}) {
172
+ const cwd = options.cwd || process.cwd();
173
+ const generatedBy = options.generatedBy || PACKAGE_VERSION;
174
+ const config = configArtifact(options);
175
+ const engine = selectRuntimeEngine(config);
176
+ const json = selectJsonConfig(config);
177
+ const diagnostics = validateSelections(engine, json);
178
+ const warnings = jsonWarnings(engine, json);
179
+
180
+ const artifact = normalizeArtifact({
181
+ version: JSON_BODY_VERSION,
182
+ generatedBy,
183
+ source: dispatchTable?.source,
184
+ policy: {
185
+ phase: JSON_BODY_PHASE,
186
+ status: 'locked-contract',
187
+ sourceOfTruth: [
188
+ 'pulsewasm JSON/body ABI contract v1',
189
+ 'request-result-headers.json',
190
+ 'wasm-host-abi.json',
191
+ 'deployment-posture.json'
192
+ ],
193
+ payloadFeature: true,
194
+ routingFeature: false,
195
+ bodyLazy: true,
196
+ automaticParseDuringDispatch: false,
197
+ genericDefault: true,
198
+ schemaCompilerImplemented: false,
199
+ binaryBodyAbi: false,
200
+ fallbackAutomatic: false
201
+ },
202
+ lockedPositions: [
203
+ 'Generic JSON is the ergonomic default.',
204
+ 'Request body access is explicit and lazy.',
205
+ 'The routing core never parses JSON automatically.',
206
+ 'JSON parsing returns an explicit parse-result ref; no throw/try-catch semantics are introduced.',
207
+ 'Invalid JSON becomes a structured PulseErrorRef with status 400.',
208
+ 'Oversized JSON becomes a structured PulseErrorRef with status 413.',
209
+ 'RESULT_JSON is enabled through json text response helpers.',
210
+ 'RESULT_BINARY remains reserved.',
211
+ 'json.target = "schema" is implemented by the schema-json sidecar/compile stream; json.target = "parser" uses the generic AS parser fallback.',
212
+ 'json.target = "none" disables JSON helpers and should hard-fail any future JSON helper usage.'
213
+ ],
214
+ runtime: {
215
+ engine: engine.engine,
216
+ source: engine.source,
217
+ explicit: engine.explicit,
218
+ manualEscapeHatch: engine.engine === 'js',
219
+ automaticFallback: false
220
+ },
221
+ json: {
222
+ target: json.target,
223
+ targetSource: json.targetSource,
224
+ explicitTarget: json.explicitTarget,
225
+ maxBytes: json.maxBytes,
226
+ maxBytesSource: json.maxBytesSource,
227
+ schemas: json.schemas,
228
+ targets: {
229
+ generic: {
230
+ default: true,
231
+ parser: 'full generic parser/value model by contract; implementation dependency remains explicit in generated runtime work',
232
+ warning: 'binary size/allocation pressure possible'
233
+ },
234
+ schema: {
235
+ recognized: true,
236
+ implementedIn10F: false,
237
+ implementedBy: '@pulse-compute/wasm-schema-json schema sidecar/compile builders'
238
+ },
239
+ auto: {
240
+ recognized: true,
241
+ schemaDeclarationsPreferred: true,
242
+ schemaLessFallbackRequiresParser: 'as-json'
243
+ },
244
+ parser: {
245
+ recognized: true,
246
+ parser: json.parser || 'as-json',
247
+ implementedBy: '@pulse-compute/wasm-schema-json generic parser builder'
248
+ },
249
+ none: {
250
+ recognized: true,
251
+ jsonHelpersAvailable: false,
252
+ futureUsageDiagnostic: 'PULSEWASM_JSON_DISABLED'
253
+ }
254
+ }
255
+ },
256
+ bodyPolicy: {
257
+ requestBody: 'deferred; no automatic read, no parse-on-dispatch, explicit helper call required',
258
+ responseBody: 'RESULT_TEXT, RESULT_EMPTY, and RESULT_JSON text helper; RESULT_BINARY reserved',
259
+ resultJson: json.target === 'none' ? 'disabled by config' : 'implemented via pulse_result_json_text(ctx,status,jsonText)',
260
+ resultBinary: 'reserved'
261
+ },
262
+ parseResult: {
263
+ shape: {
264
+ ok: 'bool',
265
+ valueRef: 'JsonValueRef | 0',
266
+ errorRef: 'ErrorRef | 0'
267
+ },
268
+ helpers: ['pulse_json_result_ok', 'pulse_json_result_value', 'pulse_json_result_error'],
269
+ invalidJsonError: {
270
+ code: 'PULSEWASM_JSON_PARSE_ERROR',
271
+ message: 'Invalid JSON request body.',
272
+ statusCode: 400
273
+ },
274
+ bodyTooLargeError: {
275
+ code: 'PULSEWASM_JSON_BODY_TOO_LARGE',
276
+ message: 'JSON request body exceeds configured maximum size.',
277
+ statusCode: 413
278
+ }
279
+ },
280
+ valueModel: {
281
+ ref: 'JsonValueRef(i32)',
282
+ kindCodes: {
283
+ JSON_NULL: 0,
284
+ JSON_BOOL: 1,
285
+ JSON_NUMBER: 2,
286
+ JSON_STRING: 3,
287
+ JSON_ARRAY: 4,
288
+ JSON_OBJECT: 5
289
+ },
290
+ opaqueRefs: true,
291
+ noDynamicJsObject: true
292
+ },
293
+ abi: {
294
+ exports: JSON_ABI_EXPORTS,
295
+ implementedImmediately: [
296
+ 'pulse_request_body_text',
297
+ 'pulse_request_body_len',
298
+ 'pulse_request_json_parse',
299
+ 'pulse_json_result_ok',
300
+ 'pulse_json_result_value',
301
+ 'pulse_json_result_error',
302
+ 'pulse_result_json_text'
303
+ ],
304
+ genericValueHelpers: [
305
+ 'pulse_json_kind',
306
+ 'pulse_json_object_has',
307
+ 'pulse_json_object_get',
308
+ 'pulse_json_array_len',
309
+ 'pulse_json_array_get',
310
+ 'pulse_json_as_string',
311
+ 'pulse_json_as_f64',
312
+ 'pulse_json_as_bool'
313
+ ],
314
+ reserved: ['RESULT_BINARY', 'schema-generated parser imports']
315
+ },
316
+ headers: {
317
+ jsonResponseContentType: 'application/json',
318
+ contentTypePolicy: 'pulse_result_json_text overwrites Content-Type in v1',
319
+ usesHeaderAbi: true
320
+ },
321
+ diagnosticsPolicy: {
322
+ warnings: ['PULSEWASM_JSON_GENERIC_DEFAULT', 'PULSEWASM_JSON_GENERIC_SIZE_WARNING', 'PULSEWASM_JSON_SCHEMA_RESERVED'],
323
+ errors: ['PULSEWASM_JSON_DISABLED', 'PULSEWASM_JSON_UNSUPPORTED_TARGET', 'PULSEWASM_JSON_BODY_LIMIT_INVALID', 'PULSEWASM_JSON_SCHEMA_NOT_FOUND'],
324
+ runtimeErrors: ['PULSEWASM_JSON_PARSE_ERROR', 'PULSEWASM_JSON_BODY_TOO_LARGE']
325
+ },
326
+ warnings,
327
+ diagnostics,
328
+ summary: {
329
+ jsonTarget: json.target,
330
+ engine: engine.engine,
331
+ genericDefault: json.target === 'generic' && !json.explicitTarget,
332
+ requestBodyDeferred: true,
333
+ jsonParseExplicit: true,
334
+ resultJsonImplemented: json.target !== 'none',
335
+ resultBinaryReserved: true,
336
+ schemaTargetRecognized: true,
337
+ schemaCompilerImplemented: false,
338
+ maxBytes: json.maxBytes,
339
+ warnings: warnings.length,
340
+ diagnostics: diagnostics.length
341
+ }
342
+ }, cwd);
343
+
344
+ return {
345
+ artifact,
346
+ files: [{ file: 'generated/host/json-body.md', text: buildMarkdown(artifact) }],
347
+ diagnostics
348
+ };
349
+ }
350
+
351
+ function buildDeploymentPosture(dispatchTable, executionPlan, options = {}) {
352
+ const cwd = options.cwd || process.cwd();
353
+ const generatedBy = options.generatedBy || PACKAGE_VERSION;
354
+ const config = configArtifact(options);
355
+ const engine = selectRuntimeEngine(config);
356
+ const json = selectJsonConfig(config);
357
+ const diagnostics = validateSelections(engine, json);
358
+ const debt = [];
359
+ if (engine.engine === 'js') {
360
+ debt.push({
361
+ code: 'PULSEWASM_ENGINE_JS_SELECTED',
362
+ kind: 'manual_escape_hatch',
363
+ feature: json.target === 'generic' ? 'generic_json' : 'runtime_engine',
364
+ severity: 'known_tradeoff',
365
+ message: 'JS engine selected manually. Application can ship, but Wasm optimization is bypassed.',
366
+ why: json.target === 'generic'
367
+ ? 'Generic JSON or dependency ergonomics were prioritized over Wasm compilation.'
368
+ : 'The developer explicitly selected the JS runtime engine.',
369
+ paydown: [
370
+ 'Switch runtime.engine to "wasm"',
371
+ 'Switch json.target to "schema" when generated parsers are available',
372
+ 'Switch json.target to "none" if payload parsing is not required',
373
+ 'Remove AssemblyScript-incompatible dependencies',
374
+ 'Re-run pulse build with runtime.engine = "wasm"'
375
+ ]
376
+ });
377
+ }
378
+
379
+ const backendCapabilities = options.backendCapabilities?.artifact || options.backendCapabilities;
380
+ const fetchUsages = Array.isArray(backendCapabilities?.usages) ? backendCapabilities.usages : [];
381
+ if (engine.engine === 'js' && fetchUsages.length > 0) {
382
+ debt.push({
383
+ code: 'PULSEWASM_FETCH_REQUIRES_JS_ENGINE',
384
+ kind: 'manual_escape_hatch',
385
+ feature: 'ctx.fetch',
386
+ severity: 'known_tradeoff',
387
+ message: 'ctx.fetch was used with the JS engine escape hatch. Application can ship, but Wasm optimization is bypassed for this capability.',
388
+ why: 'Outbound backend requests require a future Wasm fetch-effect runtime that is not enabled yet.',
389
+ usages: fetchUsages.map((usage) => ({ handler: usage.handler, backend: usage.backend, method: usage.method, path: usage.path })),
390
+ paydown: [
391
+ 'Enable the future Wasm fetch-effect runtime',
392
+ 'Move backend call behind a host adaptor',
393
+ 'Remove ctx.fetch usage from Wasm-targeted handlers',
394
+ 'Use runtime.engine = "wasm" once fetch effects are supported'
395
+ ]
396
+ });
397
+ }
398
+
399
+ const artifact = normalizeArtifact({
400
+ version: DEPLOYMENT_POSTURE_VERSION,
401
+ generatedBy,
402
+ source: dispatchTable?.source,
403
+ policy: {
404
+ phase: JSON_BODY_PHASE,
405
+ manualFallbackOnly: true,
406
+ automaticFallback: false,
407
+ silentEngineSwitchForbidden: true,
408
+ decisionLedger: true,
409
+ moralizing: false
410
+ },
411
+ engine: engine.engine,
412
+ engineSource: engine.source,
413
+ optimizedTarget: 'wasm',
414
+ wasmOptimized: engine.engine === 'wasm',
415
+ manualEscapeHatch: engine.engine === 'js',
416
+ fallback: 'manual-only',
417
+ json: {
418
+ target: json.target,
419
+ targetSource: json.targetSource,
420
+ maxBytes: json.maxBytes,
421
+ genericDefault: json.target === 'generic' && !json.explicitTarget
422
+ },
423
+ debt,
424
+ recovery: engine.engine === 'js' ? [
425
+ 'Use runtime.engine = "wasm" when the build is ready for the optimized path.',
426
+ 'Use json.target = "schema" once deterministic schema parsers are generated.',
427
+ 'Use json.target = "none" when JSON payload helpers are unnecessary.',
428
+ 'Keep runtime.engine = "js" only as an explicit manual escape hatch.'
429
+ ] : [],
430
+ summary: {
431
+ engine: engine.engine,
432
+ wasmOptimized: engine.engine === 'wasm',
433
+ manualEscapeHatch: engine.engine === 'js',
434
+ debtCount: debt.length,
435
+ backendFetchUsages: fetchUsages.length,
436
+ jsonTarget: json.target,
437
+ fallbackAutomatic: false,
438
+ diagnostics: diagnostics.length
439
+ },
440
+ diagnostics
441
+ }, cwd);
442
+ return { artifact, diagnostics };
443
+ }
444
+
445
+ module.exports = {
446
+ JSON_BODY_VERSION,
447
+ DEPLOYMENT_POSTURE_VERSION,
448
+ DEFAULT_JSON_MAX_BYTES,
449
+ JSON_ABI_EXPORTS,
450
+ buildJsonBodyAbi,
451
+ buildDeploymentPosture,
452
+ selectRuntimeEngine,
453
+ selectJsonConfig
454
+ };
@@ -0,0 +1,153 @@
1
+ 'use strict';
2
+
3
+ const { PACKAGE_VERSION, normalizeArtifact } = require('@pulse-compute/wasm-contracts/diagnostics');
4
+ const {
5
+ REQUEST_RESULT_HEADERS_VERSION,
6
+ REQUEST_RESULT_HEADERS_PHASE,
7
+ HEADER_ABI_EXPORTS,
8
+ handlerVisibleHeaderAbiExports,
9
+ hostResultHeaderAbiExports
10
+ } = loadContractsRequestResultHeaders();
11
+
12
+ function loadContractsRequestResultHeaders() {
13
+ try {
14
+ return require('@pulse-compute/wasm-contracts/host/request-result-headers');
15
+ } catch (error) {
16
+ if (error && error.code === 'MODULE_NOT_FOUND' && String(error.message).includes('@pulse-compute/wasm-contracts')) {
17
+ return require('../../../contracts/src/host/request-result-headers.js');
18
+ }
19
+ throw error;
20
+ }
21
+ }
22
+
23
+ function buildMarkdown(artifact) {
24
+ const lines = [];
25
+ lines.push('# PulseWasm Request / Result / Headers Adaptor Contract v1');
26
+ lines.push('');
27
+ lines.push(`Generated by: \`${artifact.generatedBy}\``);
28
+ lines.push(`Phase: \`${artifact.policy.phase}\``);
29
+ lines.push('');
30
+ lines.push('## Locked positions');
31
+ lines.push('');
32
+ for (const item of artifact.lockedPositions) lines.push(`- ${item}`);
33
+ lines.push('');
34
+ lines.push('## Header storage');
35
+ lines.push('');
36
+ lines.push('- ordered entries preserve original output order');
37
+ lines.push('- repeated headers are preserved, not collapsed');
38
+ lines.push('- lookup is ASCII case-insensitive through a lowercase index key');
39
+ lines.push('- request headers are read-only to handlers');
40
+ lines.push('- response headers are mutable through set / append / delete');
41
+ lines.push('');
42
+ lines.push('## Handler-visible ABI');
43
+ lines.push('');
44
+ for (const item of artifact.abi.handlerVisibleExports) lines.push(`- \`${item}\``);
45
+ lines.push('');
46
+ lines.push('## Host result header iteration');
47
+ lines.push('');
48
+ for (const item of artifact.abi.hostResultHeaderExports) lines.push(`- \`${item}\``);
49
+ lines.push('');
50
+ lines.push('## Body policy');
51
+ lines.push('');
52
+ lines.push(`- Request body: \`${artifact.bodyPolicy.requestBody}\``);
53
+ lines.push(`- Response body: \`${artifact.bodyPolicy.responseBody}\``);
54
+ lines.push(`- JSON: \`${artifact.bodyPolicy.json}\``);
55
+ lines.push(`- Binary: \`${artifact.bodyPolicy.binary}\``);
56
+ lines.push('');
57
+ lines.push('## GRIP / broadcaster note');
58
+ lines.push('');
59
+ lines.push('GRIP/Fanout framing is expressed through normal response headers. The later broadcaster/GRIP adaptor inspects headers such as `Grip-Hold` and repeated `Grip-Channel` entries.');
60
+ lines.push('');
61
+ return `${lines.join('\n')}\n`;
62
+ }
63
+
64
+ function buildRequestResultHeaders(dispatchTable, executionPlan, options = {}) {
65
+ const cwd = options.cwd || process.cwd();
66
+ const generatedBy = options.generatedBy || PACKAGE_VERSION;
67
+ const artifact = normalizeArtifact({
68
+ version: REQUEST_RESULT_HEADERS_VERSION,
69
+ generatedBy,
70
+ source: dispatchTable?.source,
71
+ policy: {
72
+ phase: REQUEST_RESULT_HEADERS_PHASE,
73
+ status: 'locked-contract-and-local-bridge-proof',
74
+ sourceOfTruth: [
75
+ 'wasm-host-abi.json',
76
+ 'wasm-host-bridge.json',
77
+ 'pulsewasm request/result/headers adaptor contract v1'
78
+ ],
79
+ headersFirstClass: true,
80
+ bodyDeferred: true,
81
+ platformBindings: false,
82
+ witBindings: false,
83
+ jsonBodyAbi: false,
84
+ binaryBodyAbi: false,
85
+ streamBodyAbi: true
86
+ },
87
+ lockedPositions: [
88
+ 'Headers are first-class runtime artifacts.',
89
+ 'Header storage is ordered, repeated, and case-insensitive for lookup.',
90
+ 'Request headers are read-only to handlers.',
91
+ 'Response headers are mutable through set / append / delete.',
92
+ 'Header export preserves repeated headers and original order.',
93
+ 'GRIP/Fanout framing is expressed through normal response headers.',
94
+ 'Request body is lazy/deferred.',
95
+ 'Text, JSON text, empty, and stream response bodies are implemented; binary buffers remain reserved.'
96
+ ],
97
+ headerStorage: {
98
+ entry: { name: 'string', lowerName: 'string', value: 'string' },
99
+ ordered: true,
100
+ repeated: true,
101
+ lookup: 'ASCII case-insensitive lowerName scan; future implementation may add index arrays',
102
+ request: { mutability: 'host-seeded read-only to handlers' },
103
+ response: { mutability: 'handler mutable', operations: ['set', 'append', 'delete'] }
104
+ },
105
+ abi: {
106
+ handlerVisibleExports: handlerVisibleHeaderAbiExports(),
107
+ hostResultHeaderExports: hostResultHeaderAbiExports(),
108
+ allExports: HEADER_ABI_EXPORTS
109
+ },
110
+ grip: {
111
+ modeledAsHeaders: true,
112
+ importantHeaders: ['Grip-Hold', 'Grip-Channel'],
113
+ repeatedGripChannelPreserved: true,
114
+ silentIgnoreForbidden: true
115
+ },
116
+ bodyPolicy: {
117
+ requestBody: 'deferred; no automatic read, no parse-on-dispatch',
118
+ responseBody: 'RESULT_TEXT, RESULT_JSON, RESULT_EMPTY, and RESULT_STREAM; RESULT_BINARY reserved',
119
+ json: 'RESULT_JSON declared/reserved, not lowered in 10D',
120
+ binary: 'RESULT_BINARY declared/reserved; RESULT_STREAM is the primary payload pass-through primitive'
121
+ },
122
+ summary: {
123
+ routes: Array.isArray(dispatchTable?.routes) ? dispatchTable.routes.length : 0,
124
+ executionEntries: Array.isArray(executionPlan?.entries) ? executionPlan.entries.length : 0,
125
+ headerAbiExports: HEADER_ABI_EXPORTS.length,
126
+ requestHeaderReadExports: 3,
127
+ responseHeaderWriteExports: 3,
128
+ resultHeaderIterationExports: 5,
129
+ repeatedHeadersPreserved: true,
130
+ gripHeadersModeled: true,
131
+ requestBodyDeferred: true,
132
+ responseTextAndEmptyOnly: false,
133
+ streamResultImplemented: true,
134
+ binaryReserved: true
135
+ }
136
+ }, cwd);
137
+
138
+ return {
139
+ artifact,
140
+ files: [
141
+ {
142
+ file: 'generated/host/request-result-headers.md',
143
+ text: buildMarkdown(artifact)
144
+ }
145
+ ],
146
+ diagnostics: []
147
+ };
148
+ }
149
+
150
+ module.exports = {
151
+ REQUEST_RESULT_HEADERS_VERSION,
152
+ buildRequestResultHeaders
153
+ };